"""Application configuration loaded from environment variables and .env file.

Follows pydantic-settings patterns: all values are prefixed with BANGUI_
and validated at startup via the Settings singleton.
"""

from typing import Literal

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

from app.utils.constants import (
    DEFAULT_DATABASE_PATH,
    DEFAULT_FAIL2BAN_SOCKET,
    DEFAULT_SESSION_DURATION_MINUTES,
)


class Settings(BaseSettings):
    """BanGUI runtime configuration.

    All fields are loaded from environment variables prefixed with ``BANGUI_``
    or from a ``.env`` file located next to the process working directory.
    The application will raise a :class:`pydantic.ValidationError` on startup
    if any required field is missing or has an invalid value.
    """

    database_path: str = Field(
        default=DEFAULT_DATABASE_PATH,
        description="Filesystem path to the BanGUI SQLite application database.",
    )
    fail2ban_socket: str = Field(
        default=DEFAULT_FAIL2BAN_SOCKET,
        description="Path to the fail2ban Unix domain socket.",
    )
    session_secret: str = Field(
        ...,
        description=(
            "Secret key used when generating session tokens. "
            "Must be unique and never committed to source control."
        ),
    )
    session_duration_minutes: int = Field(
        default=DEFAULT_SESSION_DURATION_MINUTES,
        ge=1,
        description="Number of minutes a session token remains valid after creation.",
    )
    session_cache_enabled: bool = Field(
        default=False,
        description=(
            "Enable the in-memory session validation cache. "
            "Disable it in multi-worker deployments to avoid stale revoked sessions."
        ),
    )
    session_cache_ttl_seconds: float = Field(
        default=10.0,
        ge=0.0,
        description=(
            "How long (seconds) a cached session validation entry remains fresh. "
            "Ignored when session_cache_enabled is false."
        ),
    )
    http_request_timeout_seconds: float = Field(
        default=20.0,
        ge=0.0,
        description="Maximum total time in seconds for outbound external HTTP requests.",
    )
    http_connect_timeout_seconds: float = Field(
        default=5.0,
        ge=0.0,
        description="Maximum time in seconds to establish outbound external HTTP connections.",
    )
    http_max_connections: int = Field(
        default=10,
        ge=1,
        description="Maximum number of concurrent outbound HTTP connections.",
    )
    http_keepalive_timeout_seconds: float = Field(
        default=15.0,
        ge=0.0,
        description="How long idle keepalive connections are retained by the HTTP connector.",
    )
    timezone: str = Field(
        default="UTC",
        description="IANA timezone name used when displaying timestamps in the UI.",
    )
    session_cookie_httponly: bool = Field(
        default=True,
        description=(
            "Mark the session cookie as HttpOnly so browser scripts cannot access it."
        ),
    )
    session_cookie_samesite: Literal["lax", "strict", "none"] = Field(
        default="lax",
        description=(
            "SameSite policy for the session cookie. "
            "Use 'lax', 'strict', or 'none' depending on deployment requirements."
        ),
    )
    session_cookie_secure: bool = Field(
        default=False,
        description=(
            "Set the session cookie Secure flag when the backend is served over HTTPS."
        ),
    )
    cors_allowed_origins: str | list[str] = Field(
        default_factory=list,
        description=(
            "Comma-separated list of allowed CORS origins when the frontend is "
            "served from a different origin than the backend. "
            "Leave empty to disable cross-origin requests in production."
        ),
    )

    @field_validator("cors_allowed_origins", mode="before")
    @classmethod
    def _normalize_cors_origins(cls, value: str | list[str] | None) -> list[str]:
        if value is None:
            return []
        if isinstance(value, str):
            return [origin.strip() for origin in value.split(",") if origin.strip()]
        return value

    log_level: str = Field(
        default="info",
        description="Application log level: debug | info | warning | error | critical.",
    )
    geoip_db_path: str | None = Field(
        default=None,
        description=(
            "Optional path to a MaxMind GeoLite2-Country .mmdb file. "
            "When set, failed ip-api.com lookups fall back to local resolution."
        ),
    )
    fail2ban_config_dir: str = Field(
        default="/config/fail2ban",
        description=(
            "Path to the fail2ban configuration directory. "
            "Must contain subdirectories jail.d/, filter.d/, and action.d/. "
            "Used for listing, viewing, and editing configuration files through the web UI."
        ),
    )
    fail2ban_start_command: str = Field(
        default="fail2ban-client start",
        description=(
            "Shell command used to start (not reload) the fail2ban daemon during "
            "recovery rollback. Split by whitespace to build the argument list — "
            "no shell interpretation is performed. "
            "Example: 'systemctl start fail2ban' or 'fail2ban-client start'."
        ),
    )

    model_config = SettingsConfigDict(
        env_prefix="BANGUI_",
        env_file=".env",
        env_file_encoding="utf-8",
        case_sensitive=False,
    )


def get_settings() -> Settings:
    """Return a fresh :class:`Settings` instance loaded from the environment.

    Returns:
        A validated :class:`Settings` object. Raises :class:`pydantic.ValidationError`
        if required keys are absent or values fail validation.
    """
    return Settings()  # type: ignore[call-arg]  # pydantic-settings populates required fields from env vars
