from functools import lru_cache
from typing import List, Literal

from pydantic_settings import BaseSettings, SettingsConfigDict

INSECURE_DEFAULT_JWT_SECRET = "dev-secret-change-me"


class Settings(BaseSettings):
    model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8", extra="ignore")

    app_name: str = "Jozie AI Platform"
    environment: Literal["local", "production"] = "local"
    api_v1_prefix: str = "/api/v1"
    enable_docs: bool = True

    database_url: str = "postgresql+psycopg://jozie_app:jozie_dev_local@localhost:5432/jozie_ai"

    redis_url: str = "redis://localhost:6379/0"
    celery_broker_url: str = "redis://localhost:6379/0"
    celery_result_backend: str = "redis://localhost:6379/1"

    ollama_host: str = "http://localhost:11434"
    ollama_default_model: str = "llama3.1:8b"
    ollama_embedding_model: str = "embeddinggemma"
    # Without an explicit num_ctx, Ollama loads some models (e.g. llama3.1's 128k max)
    # at their full trained context length, which can balloon the KV cache to 20GB+ and
    # cause severe slowdowns/timeouts under memory pressure. 8192 is plenty for the
    # document excerpts and examples this app sends.
    ollama_num_ctx: int = 8192

    jwt_secret: str = INSECURE_DEFAULT_JWT_SECRET
    jwt_algorithm: str = "HS256"
    access_token_expire_minutes: int = 60 * 24

    cors_origins: List[str] = ["http://localhost:5173"]

    upload_dir: str = "./data/uploads"
    max_upload_mb: int = 100

    log_dir: str = "./logs"
    log_json: bool = False

    auth_rate_limit: str = "10/minute"

    # Fine-tuning (Module 8): real LoRA fine-tuning, backend picked per-machine at
    # runtime (see app/services/finetune/detect.py) — Apple's MLX on Apple Silicon, or
    # Unsloth on an NVIDIA GPU (CUDA). Either way the trained adapter is fused into the
    # base model and converted to GGUF using llama.cpp's conversion script, which needs
    # its own isolated venv (older numpy/transformers pins that would conflict with the
    # main app). See README "Fine-Tuning setup" for the one-time setup commands.
    finetune_data_dir: str = "./data/finetuning"
    finetune_base_models_mlx: List[str] = [
        "mlx-community/Llama-3.2-1B-Instruct-4bit",
        "mlx-community/Llama-3.2-3B-Instruct-4bit",
        "mlx-community/Qwen2.5-1.5B-Instruct-4bit",
        "mlx-community/Qwen2.5-3B-Instruct-4bit",
    ]
    finetune_base_models_cuda: List[str] = [
        "unsloth/Llama-3.2-1B-Instruct-bnb-4bit",
        "unsloth/Llama-3.2-3B-Instruct-bnb-4bit",
        "unsloth/Qwen2.5-1.5B-Instruct-bnb-4bit",
        "unsloth/Qwen2.5-3B-Instruct-bnb-4bit",
        "unsloth/Meta-Llama-3.1-8B-Instruct-bnb-4bit",
    ]
    gguf_convert_python: str = "./.venv-gguf/bin/python3"
    gguf_convert_script: str = "./vendor-llama-cpp-convert/convert_hf_to_gguf.py"

    def validate_production_ready(self) -> None:
        if self.environment != "production":
            return
        problems = []
        if self.jwt_secret == INSECURE_DEFAULT_JWT_SECRET:
            problems.append("JWT_SECRET is still the insecure default — set a real secret (openssl rand -hex 32).")
        if any(o.startswith("http://") and "localhost" not in o and "127.0.0.1" not in o for o in self.cors_origins):
            problems.append("CORS_ORIGINS includes a non-local http:// origin — use https:// in production.")
        if problems:
            raise RuntimeError(
                "Refusing to start in production with insecure configuration:\n- " + "\n- ".join(problems)
            )


@lru_cache
def get_settings() -> Settings:
    settings = Settings()
    settings.validate_production_ready()
    return settings
