"""Server configuration management for web app startup.

This module handles server configuration that needs to be available before
Flask app context is established. All settings are read from environment
variables (LDR_* naming convention) with sensible defaults.

During the deprecation period, legacy ``server_config.json`` files are read
as a read-only fallback (env var > legacy file > default).  No data is
written back to the JSON file.
"""

import json
import os
from pathlib import Path
from typing import Any, Dict, Optional

from loguru import logger

from ..config.paths import get_data_dir
from ..settings.manager import get_typed_setting_value

# Maps legacy JSON keys → (setting_key, env_var_name, is_security_critical)
_LEGACY_KEY_MAP: Dict[str, tuple] = {
    "host": ("web.host", "LDR_WEB_HOST", False),
    "port": ("web.port", "LDR_WEB_PORT", False),
    "debug": ("app.debug", "LDR_APP_DEBUG", False),
    "use_https": ("web.use_https", "LDR_WEB_USE_HTTPS", False),
    "allow_registrations": (
        "app.allow_registrations",
        "LDR_APP_ALLOW_REGISTRATIONS",
        True,
    ),
    "rate_limit_default": (
        "security.rate_limit_default",
        "LDR_SECURITY_RATE_LIMIT_DEFAULT",
        False,
    ),
    "rate_limit_login": (
        "security.rate_limit_login",
        "LDR_SECURITY_RATE_LIMIT_LOGIN",
        False,
    ),
    "rate_limit_registration": (
        "security.rate_limit_registration",
        "LDR_SECURITY_RATE_LIMIT_REGISTRATION",
        False,
    ),
    "rate_limit_settings": (
        "security.rate_limit_settings",
        "LDR_SECURITY_RATE_LIMIT_SETTINGS",
        False,
    ),
}


_DEFAULTS: Dict[str, Any] = {
    "host": "0.0.0.0",
    "port": 5000,
    "debug": False,
    "use_https": True,
    "allow_registrations": True,
    "rate_limit_default": "5000 per hour;50000 per day",
    "rate_limit_login": "5 per 15 minutes",
    "rate_limit_registration": "3 per hour",
    "rate_limit_settings": "30 per minute",
}


def get_server_config_path() -> Path:
    """Return the path to the legacy server_config.json file."""
    return Path(get_data_dir()) / "server_config.json"


def has_legacy_customizations(config_path: Optional[Path] = None) -> bool:
    """Return True if server_config.json exists with non-default values.

    Parameters
    ----------
    config_path : Path, optional
        Path to the legacy JSON file. Defaults to get_server_config_path().
    """
    path = config_path or get_server_config_path()
    if not path.exists():
        return False
    try:
        data = json.loads(path.read_text(encoding="utf-8-sig"))
    except (json.JSONDecodeError, OSError, UnicodeDecodeError):
        return False
    if not isinstance(data, dict):
        return False
    for json_key in _LEGACY_KEY_MAP:
        if json_key in data and data[json_key] != _DEFAULTS.get(json_key):
            return True
    unrecognized = set(data.keys()) - set(_LEGACY_KEY_MAP.keys())
    return bool(unrecognized)


def _load_legacy_config() -> Dict[str, Any]:
    """Read legacy server_config.json as a read-only migration fallback.

    Returns a dict of ``{json_key: value}`` for recognized keys found in the
    file.  Returns ``{}`` when the file does not exist or is malformed.

    Logs deprecation warnings so users know to migrate to env vars.
    """
    config_path = get_server_config_path()
    if not config_path.exists():
        return {}

    try:
        data = json.loads(config_path.read_text(encoding="utf-8-sig"))
    except (json.JSONDecodeError, OSError, UnicodeDecodeError):
        logger.warning(
            f"Could not read legacy server_config.json at {config_path}: "
            f"Ignoring file."
        )
        return {}

    if not isinstance(data, dict):
        logger.warning(
            f"Legacy server_config.json at {config_path} does not contain a "
            f"JSON object. Ignoring file."
        )
        return {}

    saved: Dict[str, Any] = {}
    for json_key in _LEGACY_KEY_MAP:
        if json_key in data:
            saved[json_key] = data[json_key]

    # Warn about unrecognized keys (likely typos)
    unrecognized = set(data.keys()) - set(_LEGACY_KEY_MAP.keys())
    if unrecognized:
        logger.warning(
            f"Legacy server_config.json contains unrecognized keys: "
            f"{sorted(unrecognized)}. These will be ignored. "
            f"Recognized keys: {sorted(_LEGACY_KEY_MAP.keys())}."
        )

    if saved:
        logger.info(
            f"server_config.json detected at {config_path}. "
            f"Environment variables are the preferred configuration method."
        )

    return saved


def load_server_config() -> Dict[str, Any]:
    """Load server configuration from environment variables.

    During the deprecation period, values from a legacy ``server_config.json``
    are used as fallbacks when the corresponding env var is not set.

    Priority: environment variable > legacy JSON file > built-in default.

    Returns:
        dict: Server configuration with keys: host, port, debug, use_https,
              allow_registrations, rate_limit_default, rate_limit_login,
              rate_limit_registration, rate_limit_settings
    """
    saved = _load_legacy_config()

    config = {
        "host": get_typed_setting_value(
            "web.host", saved.get("host"), "text", default=_DEFAULTS["host"]
        ),
        "port": get_typed_setting_value(
            "web.port", saved.get("port"), "number", default=_DEFAULTS["port"]
        ),
        "debug": get_typed_setting_value(
            "app.debug",
            saved.get("debug"),
            "checkbox",
            default=_DEFAULTS["debug"],
        ),
        "use_https": get_typed_setting_value(
            "web.use_https",
            saved.get("use_https"),
            "checkbox",
            default=_DEFAULTS["use_https"],
        ),
        "allow_registrations": get_typed_setting_value(
            "app.allow_registrations",
            saved.get("allow_registrations"),
            "checkbox",
            default=_DEFAULTS["allow_registrations"],
        ),
        "rate_limit_default": get_typed_setting_value(
            "security.rate_limit_default",
            saved.get("rate_limit_default"),
            "text",
            default=_DEFAULTS["rate_limit_default"],
        ),
        "rate_limit_login": get_typed_setting_value(
            "security.rate_limit_login",
            saved.get("rate_limit_login"),
            "text",
            default=_DEFAULTS["rate_limit_login"],
        ),
        "rate_limit_registration": get_typed_setting_value(
            "security.rate_limit_registration",
            saved.get("rate_limit_registration"),
            "text",
            default=_DEFAULTS["rate_limit_registration"],
        ),
        "rate_limit_settings": get_typed_setting_value(
            "security.rate_limit_settings",
            saved.get("rate_limit_settings"),
            "text",
            default=_DEFAULTS["rate_limit_settings"],
        ),
    }

    # Log per-key messages for legacy values that differ from defaults
    if saved:
        for json_key in saved:
            setting_key, env_var, is_critical = _LEGACY_KEY_MAP[json_key]
            typed_value = config[json_key]
            default_value = _DEFAULTS[json_key]
            if typed_value == default_value:
                continue  # matches default — no noise
            if is_critical:
                logger.warning(
                    f"SECURITY: server_config.json sets '{json_key}' to a non-default value. "
                    f"Consider migrating to environment variable {env_var}."
                )
            else:
                logger.info(
                    f"server_config.json sets '{json_key}' to a non-default value. "
                    f"Environment variable {env_var} is the preferred configuration method."
                )

    # Security: if allow_registrations is set to an unrecognized string value
    # (e.g. "disabled", "nein", typos like "flase"), default to False
    # (registrations disabled) rather than True.  This "fail closed" approach
    # prevents accidental open registration when an admin clearly intended to
    # restrict it but used a non-standard boolean string.
    _RECOGNIZED_BOOL_VALUES = {
        "true",
        "false",
        "1",
        "0",
        "yes",
        "no",
        "on",
        "off",
    }

    # Guard for env var path
    raw_reg_env = os.getenv("LDR_APP_ALLOW_REGISTRATIONS")
    if raw_reg_env is not None:
        normalized = raw_reg_env.lower().strip()
        if normalized not in _RECOGNIZED_BOOL_VALUES:
            logger.warning(
                f"LDR_APP_ALLOW_REGISTRATIONS='{raw_reg_env}' is not a "
                f"recognized boolean value. Defaulting to FALSE "
                f"(registrations disabled) for security. Use "
                f"'true'/'false', '1'/'0', 'yes'/'no', or 'on'/'off'."
            )
            config["allow_registrations"] = False

    # Guard for legacy JSON path: if legacy JSON had a string value for
    # allow_registrations (e.g. "disabled") and no env var overrides it,
    # parse_boolean would treat any non-empty string as True (HTML checkbox
    # semantics).  Fail closed to prevent accidental open registration.
    elif (
        "allow_registrations" in saved
        and isinstance(saved["allow_registrations"], str)
        and saved["allow_registrations"].lower().strip()
        not in _RECOGNIZED_BOOL_VALUES
    ):
        logger.warning(
            f"Legacy server_config.json has allow_registrations="
            f"'{saved['allow_registrations']}' which is not a recognized "
            f"boolean value. Defaulting to FALSE (registrations disabled) "
            f"for security."
        )
        config["allow_registrations"] = False

    return config
