"""Centralized settings loaded from environment variables and .env files.

Priority (highest first):
  1. Explicit environment variables
  2. .env file at project root (loaded via python-dotenv on module import)
  3. Defaults defined below

All secrets and deployment-specific values must be configured here rather than
hardcoded in source files.
"""

from functools import lru_cache
from pathlib import Path
from typing import Optional

from pydantic import Field, model_validator
from pydantic_settings import BaseSettings, SettingsConfigDict

ROOT_DIR = Path(__file__).resolve().parent.parent.parent.parent
ENV_FILE = ROOT_DIR / ".env"


class Settings(BaseSettings):
    model_config = SettingsConfigDict(
        env_file=str(ENV_FILE),
        env_file_encoding="utf-8",
        env_prefix="AGENTEVAL_",
        extra="ignore",
    )

    # ── API security ──────────────────────────────────────────────
    api_key: Optional[str] = Field(
        default=None,
        description="If set, all /api/* endpoints require the X-API-Key header to match.",
    )
    admin_password: Optional[str] = Field(
        default=None,
        description=(
            "If set, the web UI requires login with this password. "
            "Leave empty to disable the login gate (dev default)."
        ),
    )
    secret_key: Optional[str] = Field(
        default=None,
        description="Fernet key used to encrypt model provider API keys at rest.",
    )
    allowed_origins: list[str] = Field(
        default_factory=lambda: ["*"],
        description="CORS allow_origins list. Use explicit URLs in production.",
    )

    # ── OpenClaw proxy ─────────────────────────────────────────────
    openclaw_upstream: str = "http://openclaw-eval:18789"
    openclaw_ws_upstream: str = "ws://openclaw-eval:18789"
    openclaw_proxy_user: str = "agenteval"
    openclaw_auth_token: str = "change-me-in-production"
    openclaw_ws_origin: Optional[str] = Field(
        default=None,
        description=(
            "Explicit Origin header sent to OpenClaw during WS handshake. "
            "If unset, auto-derived from allowed_origins so the OpenClaw "
            "gateway's allowedOrigins check passes."
        ),
    )

    # ── Frontend ───────────────────────────────────────────────────
    frontend_dist_path: Optional[str] = None

    # ── File Management ────────────────────────────────────────────
    max_upload_size_mb: int = Field(
        default=50,
        description="Maximum single file upload size in megabytes.",
    )
    allowed_extensions: str = Field(
        default="txt,md,json,yaml,yml,csv,xml,xlsx,xls,png,jpg,jpeg,gif,svg,zip,py,js,ts",
        description="Comma-separated list of allowed file extensions for upload.",
    )

    # ── Evaluation ─────────────────────────────────────────────────
    poll_reply_timeout: float = Field(
        default=30.0,
        description="Seconds to wait for the target's reply per turn before recording no-reply.",
    )

    # ── Webhook ────────────────────────────────────────────────────
    webhook_url: Optional[str] = Field(
        default=None,
        description="If set, POST run completion summaries to this URL.",
    )
    webhook_secret: Optional[str] = Field(
        default=None,
        description="If set, included as X-Webhook-Secret header for verification.",
    )

    @model_validator(mode="after")
    def _derive_openclaw_ws_origin(self) -> "Settings":
        """Auto-derive openclaw_ws_origin from allowed_origins.

        Picks the first non-wildcard, non-localhost origin from allowed_origins
        (the typical "public URL" of this deployment). Falls back to
        http://localhost:8000 if nothing suitable is found.
        """
        if self.openclaw_ws_origin:
            return self

        for origin in self.allowed_origins:
            if not origin or origin == "*" or "localhost" in origin or "127.0.0.1" in origin:
                continue
            self.openclaw_ws_origin = origin
            return self

        self.openclaw_ws_origin = "http://localhost:8000"
        return self


@lru_cache(maxsize=1)
def get_settings() -> Settings:
    """Return the cached Settings instance."""
    return Settings()
