"""Immutable application configuration and fail-closed capability gates."""

from __future__ import annotations

import os
from dataclasses import dataclass
from types import MappingProxyType
from typing import Final, Mapping

from .errors import InvalidConfiguration

_TRUE_VALUES: Final = frozenset({"1", "true", "yes", "on"})
_FALSE_VALUES: Final = frozenset({"0", "false", "no", "off"})
_LOG_LEVELS: Final = frozenset({"DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"})

ENVIRONMENT_VARIABLES: Mapping[str, str] = MappingProxyType(
    {
        "environment": "OSM_LEAD_SOURCE_ENVIRONMENT",
        "log_level": "OSM_LEAD_SOURCE_LOG_LEVEL",
        "read_only": "OSM_LEAD_SOURCE_READ_ONLY",
        "noco_write_enabled": "OSM_LEAD_SOURCE_NOCO_WRITE_ENABLED",
        "stale_deletion_enabled": "OSM_LEAD_SOURCE_STALE_DELETION_ENABLED",
        "mcp_enabled": "OSM_LEAD_SOURCE_MCP_ENABLED",
        "scheduler_enabled": "OSM_LEAD_SOURCE_SCHEDULER_ENABLED",
        "scheduler_approved": "OSM_LEAD_SOURCE_SCHEDULER_APPROVED",
    }
)

PROHIBITED_CAPABILITIES: Final = (
    "noco_write_enabled",
    "stale_deletion_enabled",
    "mcp_enabled",
)


def _parse_bool(name: str, value: str) -> bool:
    normalized = value.strip().lower()
    if normalized in _TRUE_VALUES:
        return True
    if normalized in _FALSE_VALUES:
        return False
    raise InvalidConfiguration(
        f"{name} must be a boolean value ({', '.join(sorted(_TRUE_VALUES | _FALSE_VALUES))})"
    )


def _read_bool(environ: Mapping[str, str], field_name: str, default: bool) -> bool:
    variable_name = ENVIRONMENT_VARIABLES[field_name]
    raw_value = environ.get(variable_name)
    if raw_value is None:
        return default
    return _parse_bool(variable_name, raw_value)


@dataclass(frozen=True, slots=True)
class AppConfig:
    """Known non-secret configuration for the read-only runtime.

    Lead creation, stale deletion and MCP execution remain prohibited. The
    scheduler is effective only when both its request and separate approval
    flags are enabled.
    """

    environment: str = "local"
    log_level: str = "INFO"
    read_only: bool = True
    noco_write_enabled: bool = False
    stale_deletion_enabled: bool = False
    mcp_enabled: bool = False
    scheduler_enabled: bool = False

    def __post_init__(self) -> None:
        if not isinstance(self.environment, str) or not self.environment.strip():
            raise InvalidConfiguration("environment must be a nonblank string")
        if not isinstance(self.log_level, str):
            raise InvalidConfiguration("log_level must be a string")
        normalized_log_level = self.log_level.strip().upper()
        if normalized_log_level not in _LOG_LEVELS:
            raise InvalidConfiguration(f"log_level must be one of {', '.join(sorted(_LOG_LEVELS))}")
        object.__setattr__(self, "environment", self.environment.strip())
        object.__setattr__(self, "log_level", normalized_log_level)

        if self.read_only is not True:
            raise InvalidConfiguration("read_only must remain true")
        for capability in PROHIBITED_CAPABILITIES:
            if getattr(self, capability) is not False:
                raise InvalidConfiguration(f"{capability} cannot be enabled")
        if not isinstance(self.scheduler_enabled, bool):
            raise InvalidConfiguration("scheduler_enabled must be boolean")

    @classmethod
    def from_env(cls, environ: Mapping[str, str] | None = None) -> "AppConfig":
        """Build and validate configuration from known, non-secret variables only."""

        source = os.environ if environ is None else environ
        values = {
            variable_name: source[variable_name]
            for variable_name in ENVIRONMENT_VARIABLES.values()
            if variable_name in source
        }
        return cls(
            environment=values.get(ENVIRONMENT_VARIABLES["environment"], "local"),
            log_level=values.get(ENVIRONMENT_VARIABLES["log_level"], "INFO"),
            read_only=_read_bool(values, "read_only", True),
            noco_write_enabled=_read_bool(values, "noco_write_enabled", False),
            stale_deletion_enabled=_read_bool(values, "stale_deletion_enabled", False),
            mcp_enabled=_read_bool(values, "mcp_enabled", False),
            scheduler_enabled=(
                _read_bool(values, "scheduler_enabled", False)
                and _read_bool(values, "scheduler_approved", False)
            ),
        )

    def validate(self) -> "AppConfig":
        """Return this already-validated immutable configuration."""

        return self


Configuration = AppConfig
