from __future__ import annotations

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

LOCAL_ENVIRONMENTS = frozenset({"local", "development", "dev"})
SUPPORTED_INTENT_CLASSIFIERS = frozenset({"tier1", "tier1+tier2"})


class Settings(BaseSettings):
    """Runtime configuration for the RAG orchestrator."""

    model_config = SettingsConfigDict(env_file=".env", extra="ignore")

    # Application
    APP_NAME: str = "RAG Service"
    ENVIRONMENT: str = Field(
        default="development",
        validation_alias=AliasChoices("ENVIRONMENT", "ENV"),
    )
    PORT: int = 6005
    LOG_LEVEL: str = "info"
    SERVICE_VERSION: str = "1.0.0"

    # MongoDB — own collections (query_logs); cross-DB read for /repos
    MONGO_URI: str = "mongodb://localhost:27017"
    DATABASE_NAME: str = "adpilot_rag"
    INDEXING_RUNS_DATABASE: str = "adpilot_repo_sync"

    # Embedding + retrieval service (Track 1)
    EMBEDDING_RETRIEVAL_SERVICE_URL: str = "http://localhost:6004"
    EMBEDDING_RETRIEVAL_TIMEOUT_SEC: int = 30
    RETRIEVAL_CLIENT_MOCK: bool = False
    RETRIEVAL_MOCK_SCENARIO: str = ""
    QUERY_LOGS_ENABLED: bool = True
    DEFAULT_REPO_ID: str = ""
    REPO_RESOLVER_LLM_ENABLED: bool = True
    REPO_RESOLVER_CONFIDENCE_THRESHOLD: float = 0.7

    # LLM providers (P4 / multi-provider SDK)
    OPENAI_API_KEY: str = ""
    ANTHROPIC_API_KEY: str = ""
    GEMINI_API_KEY: str = Field(
        default="",
        validation_alias=AliasChoices("GEMINI_API_KEY", "GOOGLE_API_KEY"),
    )
    DEEPSEEK_API_KEY: str = ""
    DEEPSEEK_BASE_URL: str = "https://api.deepseek.com"
    LLM_MODEL: str = "gpt-4o"
    LLM_INTENT_MODEL: str = "gpt-4o-mini"
    LLM_REQUEST_TIMEOUT_SEC: int = 120
    LLM_MAX_RETRIES: int = 3
    LLM_RETRY_BACKOFF_FACTOR: float = 2.0
    LLM_CONTEXT_TOKEN_BUDGET: int = 12000
    TOKENIZER_MODEL: str = "cl100k_base"
    INTENT_CLASSIFIER: str = "tier1"
    QUERY_DEFAULT_TOP_K: int = 20
    QUERY_DEFAULT_SCORE_THRESHOLD: float = 0.25
    # Commit summaries and overview docs embed poorly vs natural-language questions.
    QUERY_HISTORICAL_SCORE_THRESHOLD: float = 0.18
    QUERY_MIXED_SCORE_THRESHOLD: float = 0.25
    QUERY_ARCHITECTURE_SCORE_THRESHOLD: float = 0.18
    QUERY_WEAK_RETRIEVAL_SCORE_THRESHOLD: float = 0.2
    QUERY_FALLBACK_DOCS_SCORE_THRESHOLD: float = 0.12
    QUERY_FALLBACK_BROAD_TOP_K: int = 50
    TIER1_CONFIDENCE_THRESHOLD: float = 0.6
    # When retrieval returns no hits, answer from general LLM knowledge (set false to disable).
    GENERAL_LLM_FALLBACK_ENABLED: bool = True

    # Cloud-side web_search for general/world-knowledge agent questions.
    WEB_SEARCH_TIMEOUT_SEC: float = 12.0
    # Optional Brave Search API key. When unset, DuckDuckGo is used.
    BRAVE_SEARCH_API_KEY: str = ""

    @field_validator(
        "EMBEDDING_RETRIEVAL_TIMEOUT_SEC",
        "QUERY_DEFAULT_TOP_K",
        "QUERY_FALLBACK_BROAD_TOP_K",
        "LLM_REQUEST_TIMEOUT_SEC",
        "LLM_MAX_RETRIES",
        "LLM_CONTEXT_TOKEN_BUDGET",
    )
    @classmethod
    def _positive_int(cls, value: int) -> int:
        if value < 1:
            raise ValueError("must be >= 1")
        return value

    @field_validator(
        "QUERY_DEFAULT_SCORE_THRESHOLD",
        "QUERY_HISTORICAL_SCORE_THRESHOLD",
        "QUERY_MIXED_SCORE_THRESHOLD",
        "QUERY_ARCHITECTURE_SCORE_THRESHOLD",
        "QUERY_WEAK_RETRIEVAL_SCORE_THRESHOLD",
        "QUERY_FALLBACK_DOCS_SCORE_THRESHOLD",
        "TIER1_CONFIDENCE_THRESHOLD",
    )
    @classmethod
    def _score_range(cls, value: float) -> float:
        if not 0.0 <= value <= 1.0:
            raise ValueError("must be between 0 and 1")
        return value

    @field_validator("REPO_RESOLVER_CONFIDENCE_THRESHOLD")
    @classmethod
    def _repo_resolver_threshold(cls, value: float) -> float:
        if not 0.0 <= value <= 1.0:
            raise ValueError("must be between 0 and 1")
        return value

    def resolved_environment(self) -> str:
        return (self.ENVIRONMENT or "development").strip().lower()

    def is_local(self) -> bool:
        return self.resolved_environment() in LOCAL_ENVIRONMENTS

    def validate_runtime(self) -> None:
        """Fail fast when required settings are missing outside local/dev."""
        if self.INTENT_CLASSIFIER not in SUPPORTED_INTENT_CLASSIFIERS:
            raise ValueError(
                f"Unsupported INTENT_CLASSIFIER: {self.INTENT_CLASSIFIER}. "
                f"Supported: {', '.join(sorted(SUPPORTED_INTENT_CLASSIFIERS))}"
            )

        if self.is_local():
            return

        missing: list[str] = []
        for name, value in (
            ("MONGO_URI", self.MONGO_URI),
            ("EMBEDDING_RETRIEVAL_SERVICE_URL", self.EMBEDDING_RETRIEVAL_SERVICE_URL),
        ):
            if not str(value).strip():
                missing.append(name)

        has_llm_key = any(
            str(value).strip()
            for value in (
                self.OPENAI_API_KEY,
                self.ANTHROPIC_API_KEY,
                self.GEMINI_API_KEY,
                self.DEEPSEEK_API_KEY,
            )
        )
        if not has_llm_key:
            missing.append(
                "OPENAI_API_KEY|ANTHROPIC_API_KEY|GEMINI_API_KEY|DEEPSEEK_API_KEY"
            )

        if missing:
            raise ValueError(
                f"Missing required configuration: {', '.join(missing)}"
            )


settings = Settings()
