import logging
import os

from flask import Blueprint, request, render_template, current_app, jsonify
from flask_login import login_required

from ...configuration_list import BrickConfigurationList
from ...config_manager import ConfigManager
from ...config import CONFIG
from ..exceptions import exception_handler
from ...instructions_list import BrickInstructionsList
from ...rebrickable_image import RebrickableImage
from ...retired_list import BrickRetiredList
from ...set_owner import BrickSetOwner
from ...set_owner_list import BrickSetOwnerList
from ...set_purchase_location import BrickSetPurchaseLocation
from ...set_purchase_location_list import BrickSetPurchaseLocationList
from ...set_storage import BrickSetStorage
from ...set_storage_list import BrickSetStorageList
from ...set_status import BrickSetStatus
from ...set_status_list import BrickSetStatusList
from ...set_tag import BrickSetTag
from ...set_tag_list import BrickSetTagList
from ...sql_counter import BrickCounter
from ...sql import BrickSQL
from ...theme_list import BrickThemeList

logger = logging.getLogger(__name__)

admin_page = Blueprint('admin', __name__, url_prefix='/admin')


def get_env_values():
    """Get current environment values, using defaults from config when not set"""
    import json
    from pathlib import Path

    env_values = {}
    config_defaults = {}
    env_explicit_values = {}  # Track which values are explicitly set
    env_locked_values = {}  # Track which values are set via Docker environment (locked)

    # Get list of variables that were set via Docker environment (before .env was loaded)
    # This was stored by load_env_file() in app.py
    docker_env_vars = set()
    if '_BK_DOCKER_ENV_VARS' in os.environ:
        try:
            docker_env_vars = set(json.loads(os.environ['_BK_DOCKER_ENV_VARS']))
        except (json.JSONDecodeError, TypeError):
            pass

    # Read .env file if it exists (check both locations)
    env_file = None
    if Path('data/.env').exists():
        env_file = Path('data/.env')
    elif Path('.env').exists():
        env_file = Path('.env')

    env_from_file = {}
    if env_file:
        with open(env_file, 'r', encoding='utf-8') as f:
            for line in f:
                line = line.strip()
                if line and not line.startswith('#') and '=' in line:
                    key, value = line.split('=', 1)
                    # Strip quotes from value when reading
                    value = value.strip()
                    if value.startswith('"') and value.endswith('"'):
                        value = value[1:-1]
                    elif value.startswith("'") and value.endswith("'"):
                        value = value[1:-1]
                    env_from_file[key] = value

    # Process each config item
    for config_item in CONFIG:
        env_name = f"BK_{config_item['n']}"

        # Store default value (with casting applied)
        default_value = config_item.get('d', '')
        if 'c' in config_item and default_value is not None:
            cast_type = config_item['c']
            if cast_type == bool and default_value == '':
                default_value = False  # Default for booleans is False only if no default specified
            elif cast_type == list and isinstance(default_value, str):
                default_value = [item.strip() for item in default_value.split(',') if item.strip()]
            # For int/other types, keep the original default value
        config_defaults[env_name] = default_value

        # Check if value is set via Docker environment directive
        # A variable is "locked" if it was in os.environ BEFORE our app loaded .env
        is_locked = env_name in docker_env_vars
        env_locked_values[env_name] = is_locked

        # Check if value is explicitly set in .env file or environment
        is_explicitly_set = env_name in env_from_file or env_name in os.environ
        env_explicit_values[env_name] = is_explicitly_set

        # Get value from .env file, environment, or default
        value = env_from_file.get(env_name) or os.environ.get(env_name)
        if value is None:
            value = default_value
        else:
            # Apply casting if specified
            if 'c' in config_item and value is not None:
                cast_type = config_item['c']
                if cast_type == bool and isinstance(value, str):
                    value = value.lower() in ('true', '1', 'yes', 'on')
                elif cast_type == int and value != '':
                    try:
                        value = int(value)
                    except (ValueError, TypeError):
                        value = config_item.get('d', 0)
                elif cast_type == list and isinstance(value, str):
                    value = [item.strip() for item in value.split(',') if item.strip()]

        env_values[env_name] = value

    return env_values, config_defaults, env_explicit_values, env_locked_values


# Admin
@admin_page.route('/', methods=['GET'])
@login_required
@exception_handler(__file__)
def admin() -> str:
    database_counters: list[BrickCounter] = []
    database_exception: Exception | None = None
    database_upgrade_needed: bool = False
    database_version: int = -1
    instructions: BrickInstructionsList | None = None
    metadata_owners: list[BrickSetOwner] = []
    metadata_purchase_locations: list[BrickSetPurchaseLocation] = []
    metadata_statuses: list[BrickSetStatus] = []
    metadata_storages: list[BrickSetStorage] = []
    metadata_tags: list[BrickSetTag] = []
    nil_minifigure_name: str = ''
    nil_minifigure_url: str = ''
    nil_part_name: str = ''
    nil_part_url: str = ''

    # This view needs to be protected against SQL errors
    try:
        database = BrickSQL(failsafe=True)
        database_upgrade_needed = database.upgrade_needed()
        database_version = database.version
        database_counters = BrickSQL().count_records()

        instructions = BrickInstructionsList()

        metadata_owners = BrickSetOwnerList.list()
        metadata_purchase_locations = BrickSetPurchaseLocationList.list()
        metadata_statuses = BrickSetStatusList.list(all=True)
        metadata_storages = BrickSetStorageList.list()
        metadata_tags = BrickSetTagList.list()
    except Exception as e:
        database_exception = e

        # Warning
        logger.warning('A database exception occured while loading the admin page: {exception}'.format(  # noqa: E501
            exception=str(e),
        ))

    nil_minifigure_name = RebrickableImage.nil_minifigure_name()
    nil_minifigure_url = RebrickableImage.static_url(
        nil_minifigure_name,
        'MINIFIGURES_FOLDER'
    )

    nil_part_name = RebrickableImage.nil_name()
    nil_part_url = RebrickableImage.static_url(
        nil_part_name,
        'PARTS_FOLDER'
    )

    open_image = request.args.get('open_image', None)
    open_instructions = request.args.get('open_instructions', None)
    open_logout = request.args.get('open_logout', None)
    open_metadata = request.args.get('open_metadata', None)
    open_owner = request.args.get('open_owner', None)
    open_purchase_location = request.args.get('open_purchase_location', None)
    open_retired = request.args.get('open_retired', None)
    open_status = request.args.get('open_status', None)
    open_storage = request.args.get('open_storage', None)
    open_tag = request.args.get('open_tag', None)
    open_theme = request.args.get('open_theme', None)

    open_metadata = (
        open_metadata or
        open_owner or
        open_purchase_location or
        open_status or
        open_storage or
        open_tag
    )

    # Get configurable default expanded sections
    default_expanded_sections = current_app.config.get('ADMIN_DEFAULT_EXPANDED_SECTIONS', [])

    # Helper function to check if section should be expanded
    def should_expand(section_name, url_param):
        # URL parameter takes priority over default config
        if url_param is not None:
            return url_param
        # Check if section is in default expanded list
        return section_name in default_expanded_sections

    # Apply configurable default expansion logic
    open_database = should_expand('database', request.args.get('open_database', None))
    open_image = should_expand('image', open_image)
    open_instructions = should_expand('instructions', open_instructions)
    open_logout = should_expand('authentication', open_logout)
    open_retired = should_expand('retired', open_retired)
    open_theme = should_expand('theme', open_theme)

    # Metadata sub-sections
    open_owner = should_expand('owner', open_owner)
    open_purchase_location = should_expand('purchase_location', open_purchase_location)
    open_status = should_expand('status', open_status)
    open_storage = should_expand('storage', open_storage)
    open_tag = should_expand('tag', open_tag)

    # Recalculate metadata section based on sub-sections or direct config
    open_metadata = (
        should_expand('metadata', open_metadata) or
        open_owner or
        open_purchase_location or
        open_status or
        open_storage or
        open_tag
    )

    env_values, config_defaults, env_explicit_values, env_locked_values = get_env_values()

    # Check .env file location and set warnings
    env_file_location = None
    env_file_warning = False
    env_file_missing = False

    if os.path.exists('data/.env'):
        env_file_location = 'data/.env'
        env_file_warning = False
        env_file_missing = False
    elif os.path.exists('.env'):
        env_file_location = '.env'
        env_file_warning = True  # Warn: changes won't persist without volume mount
        env_file_missing = False
    else:
        env_file_location = None
        env_file_warning = False
        env_file_missing = True  # Warn: no .env file found

    return render_template(
        'admin.html',
        configuration=BrickConfigurationList.list(),
        env_values=env_values,
        config_defaults=config_defaults,
        env_explicit_values=env_explicit_values,
        env_locked_values=env_locked_values,
        env_file_location=env_file_location,
        env_file_warning=env_file_warning,
        env_file_missing=env_file_missing,
        database_counters=database_counters,
        database_error=request.args.get('database_error'),
        database_exception=database_exception,
        database_upgrade_needed=database_upgrade_needed,
        database_version=database_version,
        instructions=instructions,
        metadata_owners=metadata_owners,
        metadata_purchase_locations=metadata_purchase_locations,
        metadata_statuses=metadata_statuses,
        metadata_storages=metadata_storages,
        metadata_tags=metadata_tags,
        nil_minifigure_name=nil_minifigure_name,
        nil_minifigure_url=nil_minifigure_url,
        nil_part_name=nil_part_name,
        nil_part_url=nil_part_url,
        open_database=open_database,
        open_image=open_image,
        open_instructions=open_instructions,
        open_logout=open_logout,
        open_metadata=open_metadata,
        open_owner=open_owner,
        open_purchase_location=open_purchase_location,
        open_retired=open_retired,
        open_status=open_status,
        open_storage=open_storage,
        open_tag=open_tag,
        open_theme=open_theme,
        owner_error=request.args.get('owner_error'),
        purchase_location_error=request.args.get('purchase_location_error'),
        retired=BrickRetiredList(),
        status_error=request.args.get('status_error'),
        storage_error=request.args.get('storage_error'),
        tag_error=request.args.get('tag_error'),
        theme=BrickThemeList(),
    )


# API Endpoints for Configuration Management

@admin_page.route('/api/config/update', methods=['POST'])
@login_required
@exception_handler(__file__)
def update_config() -> str:
    """Update live configuration variables"""
    try:
        data = request.get_json()
        if not data:
            return jsonify({
                'status': 'error',
                'message': 'No JSON data provided'
            }), 400

        updates = data.get('updates', {})
        if not updates:
            return jsonify({
                'status': 'error',
                'message': 'No updates provided'
            }), 400

        # Use ConfigManager to update live configuration
        config_manager = ConfigManager()
        results = config_manager.update_config(updates)

        # Check if all updates were successful
        successful_updates = {k: v for k, v in results.items() if "successfully" in v}
        failed_updates = {k: v for k, v in results.items() if "successfully" not in v}

        logger.info(f"Configuration update: {len(successful_updates)} successful, {len(failed_updates)} failed")

        if failed_updates:
            logger.warning(f"Failed updates: {failed_updates}")

        return jsonify({
            'status': 'success' if not failed_updates else 'partial',
            'results': results,
            'successful_count': len(successful_updates),
            'failed_count': len(failed_updates)
        })

    except Exception as e:
        logger.error(f"Error updating configuration: {e}")
        return jsonify({
            'status': 'error',
            'message': str(e)
        }), 500


@admin_page.route('/api/config/update-static', methods=['POST'])
@login_required
@exception_handler(__file__)
def update_static_config() -> str:
    """Update static configuration variables (requires restart)"""
    try:
        data = request.get_json()
        if not data:
            return jsonify({
                'status': 'error',
                'message': 'No JSON data provided'
            }), 400

        updates = data.get('updates', {})
        if not updates:
            return jsonify({
                'status': 'error',
                'message': 'No updates provided'
            }), 400

        # Use ConfigManager to update .env file
        config_manager = ConfigManager()

        # Update each variable in the .env file
        updated_count = 0
        for var_name, value in updates.items():
            try:
                config_manager._update_env_file(var_name, value)
                updated_count += 1
                logger.info(f"Updated static config: {var_name}")
            except Exception as e:
                logger.error(f"Failed to update static config {var_name}: {e}")
                raise e

        logger.info(f"Updated {updated_count} static configuration variables")

        return jsonify({
            'status': 'success',
            'message': f'Successfully updated {updated_count} static configuration variables to .env file',
            'updated_count': updated_count
        })

    except Exception as e:
        logger.error(f"Error updating static configuration: {e}")
        return jsonify({
            'status': 'error',
            'message': str(e)
        }), 500
