"""
Application settings loaded from environment variables / .env file.

All sensitive values (SECRET_KEY, DATABASE_URL, etc.) are read from the
environment — never hardcoded. Pydantic validators enforce minimum security
requirements at startup so misconfigured deployments fail fast.
"""

from __future__ import annotations

import base64
import hashlib
import os
from functools import lru_cache

from pydantic import field_validator
from pydantic_settings import BaseSettings, SettingsConfigDict



class Settings(BaseSettings):
    # -------------------------------------------------------------------
    # JWT  (Caller Type 1 — human users)
    # -------------------------------------------------------------------
    secret_key: str  # min 32 chars enforced by validator
    access_token_ttl: int = int(os.getenv("ACCESS_TOKEN_TTL", 900))       # 15 min
    refresh_token_ttl: int = int(os.getenv("REFRESH_TOKEN_TTL", 604800))   # 7 days

    # -------------------------------------------------------------------
    # Database
    # -------------------------------------------------------------------
    database_url: str  # postgresql+asyncpg://user:pass@host/db

    # -------------------------------------------------------------------
    # Redis  (rate limiting, distributed locks, token revocation cache)
    # -------------------------------------------------------------------
    redis_url: str = os.getenv("REDIS_URL", "redis://127.0.0.1:6379/0")

    # -------------------------------------------------------------------
    # Refresh-token cookie
    # -------------------------------------------------------------------
    cookie_domain: str = os.getenv("COOKIE_DOMAIN", "localhost")
    cookie_samesite: str = os.getenv("COOKIE_SAMESITE", "strict")
    cookie_secure: bool = True  # False in local HTTP dev only

    # -------------------------------------------------------------------
    # Environment
    # -------------------------------------------------------------------
    environment: str = os.getenv("ENVIRONMENT", "development")  # development | staging | production

    # -------------------------------------------------------------------
    # Internal cron key  (Caller Type 3)
    # -------------------------------------------------------------------
    # bcrypt hash of the static cron API key.
    # Generate:  python -c "import bcrypt,secrets; k=secrets.token_hex(32);
    #   print(bcrypt.hashpw(k.encode(), bcrypt.gensalt(12)).decode())"
    job_key_hash: str = ""

    # -------------------------------------------------------------------
    # Rate limits
    # -------------------------------------------------------------------
    login_rate_limit_max: int = int(os.getenv("LOGIN_RATE_LIMIT_MAX", 15))
    login_rate_limit_window_seconds: int = int(os.getenv("LOGIN_RATE_LIMIT_WINDOW_SECONDS", 900))   # 15 min per IP

    user_api_rate_limit_max: int = int(os.getenv("USER_API_RATE_LIMIT_MAX", 1000))
    user_api_rate_limit_window_seconds: int = int(os.getenv("USER_API_RATE_LIMIT_WINDOW_SECONDS", 60))

    # -------------------------------------------------------------------
    # Brute-force lockout
    # -------------------------------------------------------------------
    max_login_attempts: int = int(os.getenv("MAX_LOGIN_ATTEMPTS", 5))
    account_lockout_seconds: int = int(os.getenv("ACCOUNT_LOCKOUT_SECONDS", 900))  # 15-min lockout after N failures

    # -------------------------------------------------------------------
    # HMAC replay protection  (Caller Type 2)
    # -------------------------------------------------------------------
    hmac_timestamp_tolerance_seconds: int = int(os.getenv("HMAC_TIMESTAMP_TOLERANCE_SECONDS", 300))  # ±5 min clock skew

    # -------------------------------------------------------------------
    # Bcrypt cost — NEVER go below 12
    # -------------------------------------------------------------------
    bcrypt_rounds: int = int(os.getenv("BCRYPT_ROUNDS", 12))

    # -------------------------------------------------------------------
    # WebSocket push feed
    # -------------------------------------------------------------------
    ws_heartbeat_interval: int = int(os.getenv("WS_HEARTBEAT_INTERVAL", 30))  # seconds; key revocation checked here

    # -------------------------------------------------------------------
    # CORS
    # -------------------------------------------------------------------
    cors_origins: str = os.getenv("CORS_ORIGINS", "http://localhost:3000")  # comma-separated

    # -------------------------------------------------------------------
    # DSE (Dhaka Stock Exchange) remote MySQL data source
    # -------------------------------------------------------------------
    dse_host: str = os.getenv("dse_host", "")
    dse_port: int = int(os.getenv("dse_port", "3306"))
    dse_user: str = os.getenv("dse_user", "")
    dse_password: str = os.getenv("dse_password", "")
    dse_database: str = os.getenv("dse_database", "")

    # -------------------------------------------------------------------
    # Scheduler
    # -------------------------------------------------------------------
    # IANA timezone name used for job active-window checks.
    # Full list: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
    timezone: str = os.getenv("TIMEZONE", "UTC")

    # -------------------------------------------------------------------
    # Logging
    # -------------------------------------------------------------------
    log_level: str = "INFO"
    log_format: str = "json"  # json | text

    model_config = SettingsConfigDict(
        env_file=".env",
        env_file_encoding="utf-8",
        case_sensitive=False,
        extra="ignore",  # ignore undeclared vars in .env silently
    )

    # -------------------------------------------------------------------
    # Startup validators — fail fast before accepting any traffic
    # -------------------------------------------------------------------

    @field_validator("secret_key")
    @classmethod
    def _secret_key_min_length(cls, v: str) -> str:
        if len(v) < 32:
            raise ValueError("SECRET_KEY must be at least 32 characters")
        return v

    @field_validator("bcrypt_rounds")
    @classmethod
    def _bcrypt_rounds_minimum(cls, v: int) -> int:
        if v < 12:
            raise ValueError("BCRYPT_ROUNDS must be >= 12 per security policy")
        return v

    @field_validator("database_url")
    @classmethod
    def _database_url_driver(cls, v: str) -> str:
        """
        Normalise the database URL to use asyncpg regardless of what the user set.
        Rewrite any sync / psycopg driver prefix to postgresql+asyncpg://
        so that alembic and the FastAPI app always use the same async driver.
        """
        replacements = [
            ("postgresql+psycopg2://", "postgresql+asyncpg://"),
            ("postgresql+psycopg://",  "postgresql+asyncpg://"),
            ("postgresql+pg8000://",   "postgresql+asyncpg://"),
            ("postgresql://",          "postgresql+asyncpg://"),
            ("postgres://",            "postgresql+asyncpg://"),
        ]
        for old, new in replacements:
            if v.startswith(old):
                return v.replace(old, new, 1)
        return v

    # -------------------------------------------------------------------
    # Derived properties
    # -------------------------------------------------------------------

    @property
    def cors_origins_list(self) -> list[str]:
        return [o.strip() for o in self.cors_origins.split(",") if o.strip()]

    @property
    def is_production(self) -> bool:
        return self.environment == "production"

    @property
    def fernet_key(self) -> bytes:
        """
        Derive a 32-byte Fernet key from SECRET_KEY using SHA-256.

        Used to symmetrically encrypt API key secrets for HMAC callers
        (Caller Type 2). bcrypt is one-way and cannot be used for HMAC
        recomputation — Fernet encryption allows secret recovery server-side
        while never exposing plain secrets at rest.
        """
        digest = hashlib.sha256(self.secret_key.encode()).digest()  # 32 bytes
        return base64.urlsafe_b64encode(digest)                     # Fernet format


@lru_cache
def get_settings() -> Settings:
    """Return a cached Settings singleton. Safe to call anywhere."""
    return Settings()
