"""Environment configuration -- loaded from .env / environment variables.

Models:
    ServerConfig, LLMConfig, PersistenceConfig, AlbusConfig

Enterprise settings:
    Timeouts, rate limits, CORS, auth, body limits, concurrency caps.
    All configurable via environment variables with safe defaults.
"""

from __future__ import annotations

import logging
import os
from pathlib import Path
from typing import Any, Literal

from dotenv import load_dotenv
from pydantic import BaseModel, Field

from server.config._helpers import env_bool, env_int

logger = logging.getLogger(__name__)


class ServerConfig(BaseModel):
    """Server configuration."""

    host: str = "127.0.0.1"
    port: int = 8080
    debug: bool = False
    log_level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] = "INFO"

    # --- Timeouts (seconds) ---
    request_timeout: int = 300
    """Max seconds for any single HTTP request before 504."""

    shutdown_timeout: int = 30
    """Seconds to wait for in-flight requests during graceful shutdown."""

    # --- Body / connection limits ---
    max_body_size: int = 10 * 1024 * 1024
    """Max request body size in bytes (default 10 MB)."""

    max_concurrent_turns: int = 32
    """Max agent turns executing concurrently (each hits an LLM)."""

    # --- CORS ---
    cors_allowed_origins: list[str] = Field(default_factory=lambda: ["*"])
    """Allowed CORS origins.  ['*'] permits all (dev default)."""

    cors_allowed_methods: str = "GET, POST, DELETE, PATCH, OPTIONS"
    cors_allowed_headers: str = "Content-Type, Authorization, X-Request-ID, X-API-Key"

    # --- Auth ---
    api_keys: list[str] = Field(default_factory=list)
    """If non-empty, every request must carry a valid key in X-API-Key header."""

    # --- Rate limiting ---
    rate_limit_rpm: int = 0
    """Requests per minute per client IP. 0 = unlimited."""


class LLMConfig(BaseModel):
    """LLM provider configuration."""

    openai_api_key: str | None = None
    anthropic_api_key: str | None = None
    google_api_key: str | None = None
    ollama_host: str | None = None
    battery_pack: str = "balanced"

    def model_dump_safe(self) -> dict[str, Any]:
        """Dump with secrets redacted."""
        return {
            "openai_api_key": "***" if self.openai_api_key else None,
            "anthropic_api_key": "***" if self.anthropic_api_key else None,
            "google_api_key": "***" if self.google_api_key else None,
            "ollama_host": self.ollama_host,
            "battery_pack": self.battery_pack,
        }

    @property
    def has_any_provider(self) -> bool:
        """True if at least one LLM provider is configured."""
        return bool(
            self.openai_api_key
            or self.anthropic_api_key
            or self.google_api_key
            or self.ollama_host
        )


class PersistenceConfig(BaseModel):
    """Persistence configuration."""

    database_url: str | None = None
    persist_dir: str = ".albus_store"

    def model_dump_safe(self) -> dict[str, Any]:
        """Dump with secrets redacted."""
        return {
            "database_url": "***" if self.database_url else None,
            "persist_dir": self.persist_dir,
        }


class AlbusConfig(BaseModel):
    """Complete Albus configuration from environment."""

    server: ServerConfig = Field(default_factory=ServerConfig)
    llm: LLMConfig = Field(default_factory=LLMConfig)
    persistence: PersistenceConfig = Field(default_factory=PersistenceConfig)
    env: str = "development"

    @property
    def is_production(self) -> bool:
        return self.env in ("production", "prod")

    @classmethod
    def from_env(cls, validate_required: bool = True) -> AlbusConfig:
        """Load configuration from environment variables."""
        # config/ -> server/ -> src/ -> project_root
        project_root = Path(__file__).parent.parent.parent.parent
        env_file = project_root / ".env"
        env_local = project_root / ".env.local"

        if env_file.exists():
            load_dotenv(env_file)
        if env_local.exists():
            load_dotenv(env_local, override=True)

        # Parse comma-separated API keys
        raw_keys = os.getenv("ALBUS_API_KEYS", "").strip()
        api_keys = [k.strip() for k in raw_keys.split(",") if k.strip()] if raw_keys else []

        # Parse comma-separated CORS origins
        raw_origins = os.getenv("ALBUS_CORS_ORIGINS", "*").strip()
        cors_origins = [o.strip() for o in raw_origins.split(",") if o.strip()]

        config = cls(
            server=ServerConfig(
                host=os.getenv("ALBUS_HOST", "127.0.0.1"),
                port=env_int("ALBUS_PORT", 8080) or 8080,
                debug=env_bool("ALBUS_DEBUG", False),
                log_level=os.getenv("ALBUS_LOG_LEVEL", "INFO"),  # type: ignore[arg-type]  # validated by Pydantic
                request_timeout=env_int("ALBUS_REQUEST_TIMEOUT", 300) or 300,
                shutdown_timeout=env_int("ALBUS_SHUTDOWN_TIMEOUT", 30) or 30,
                max_body_size=env_int("ALBUS_MAX_BODY_SIZE", 10 * 1024 * 1024) or 10 * 1024 * 1024,
                max_concurrent_turns=env_int("ALBUS_MAX_CONCURRENT_TURNS", 32) or 32,
                cors_allowed_origins=cors_origins,
                api_keys=api_keys,
                rate_limit_rpm=env_int("ALBUS_RATE_LIMIT_RPM", 0) or 0,
            ),
            llm=LLMConfig(
                openai_api_key=os.getenv("OPENAI_API_KEY"),
                anthropic_api_key=os.getenv("ANTHROPIC_API_KEY"),
                google_api_key=os.getenv("GOOGLE_API_KEY"),
                ollama_host=os.getenv("OLLAMA_HOST"),
                battery_pack=os.getenv("AGENT_STDLIB_BATTERY_PACK", "balanced"),
            ),
            persistence=PersistenceConfig(
                database_url=os.getenv("DATABASE_URL"),
                persist_dir=os.getenv("ALBUS_PERSIST_DIR", ".albus_store"),
            ),
            env=os.getenv("ALBUS_ENV", "development").lower(),
        )

        if validate_required:
            config.validate_config()

        return config

    def validate_config(self) -> None:
        """Validate configuration, raise ValueError if invalid.

        Fails fast on clearly broken config so the server doesn't start
        in a silently degraded state.
        """
        errors: list[str] = []

        # Port range
        if not (1 <= self.server.port <= 65535):
            errors.append(f"Port {self.server.port} out of range [1, 65535]")

        # Timeouts must be positive
        if self.server.request_timeout < 1:
            errors.append("request_timeout must be >= 1 second")
        if self.server.shutdown_timeout < 1:
            errors.append("shutdown_timeout must be >= 1 second")

        # Body size must be positive
        if self.server.max_body_size < 1024:
            errors.append("max_body_size must be >= 1024 bytes")

        # Concurrency cap must be positive
        if self.server.max_concurrent_turns < 1:
            errors.append("max_concurrent_turns must be >= 1")

        # Production-specific checks
        if self.is_production:
            if "*" in self.server.cors_allowed_origins:
                errors.append("Wildcard CORS origin ('*') is not allowed in production")
            if not self.server.api_keys:
                logger.warning(
                    "No API keys configured in production — server is unauthenticated"
                )

        if errors:
            raise ValueError(f"Config validation failed: {'; '.join(errors)}")

    def model_dump_safe(self) -> dict[str, Any]:
        """Dump with secrets redacted for logging."""
        server_dump = self.server.model_dump()
        # Redact API keys
        if server_dump.get("api_keys"):
            server_dump["api_keys"] = [f"***{k[-4:]}" if len(k) > 4 else "***" for k in server_dump["api_keys"]]
        return {
            "server": server_dump,
            "llm": self.llm.model_dump_safe(),
            "persistence": self.persistence.model_dump_safe(),
            "env": self.env,
        }


# Global config singleton
_config: AlbusConfig | None = None


def get_config() -> AlbusConfig:
    """Get global config instance (lazy-loaded)."""
    global _config
    if _config is None:
        _config = AlbusConfig.from_env()
    return _config
