from __future__ import annotations

import os
from pathlib import Path
from typing import Any

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

PACKAGE_ROOT = Path(__file__).resolve().parents[1]
PROJECT_ROOT = PACKAGE_ROOT.parent
DEFAULT_CA_BUNDLE_PATH = Path(r"C:\certs\valmont_root_cert.cer")
TLS_CA_ENV_VARS = ("SSL_CERT_FILE", "REQUESTS_CA_BUNDLE", "CURL_CA_BUNDLE")


def _default_data_dir() -> Path:
    return PROJECT_ROOT / "data"


def _default_artifacts_dir() -> Path:
    return PROJECT_ROOT / "artifacts"


def _default_database_path() -> Path:
    return _default_data_dir() / "tasks.db"


def _default_memory_database_path() -> Path:
    return _default_data_dir() / "memory.db"


def _default_workspaces_dir() -> Path:
    return _default_data_dir() / "workspaces"


def _default_prompt_dir() -> Path:
    return PACKAGE_ROOT / "prompts"


def _default_model_config_path() -> Path:
    return PACKAGE_ROOT / "config" / "models.yaml"


def _default_env_file() -> Path:
    return PROJECT_ROOT / ".env"


class Settings(BaseSettings):
    model_config = SettingsConfigDict(
        env_prefix="AIIQ_",
        extra="ignore",
        env_file=str(_default_env_file()),
        env_file_encoding="utf-8",
        populate_by_name=True,
    )

    app_name: str = "AIIQ Orchestrator"
    default_engine: str = "langgraph"
    queue_backend: str = "sqlite"
    postgres_dsn: str | None = None
    ca_bundle_path: Path | None = Field(default=None, alias="AIIQ_CA_BUNDLE")
    default_ca_bundle_path: Path | None = DEFAULT_CA_BUNDLE_PATH

    data_dir: Path = Field(default_factory=_default_data_dir)
    artifacts_dir: Path = Field(default_factory=_default_artifacts_dir)
    database_path: Path = Field(default_factory=_default_database_path)
    memory_database_path: Path = Field(default_factory=_default_memory_database_path)
    workspaces_dir: Path = Field(default_factory=_default_workspaces_dir)
    prompt_dir: Path = Field(default_factory=_default_prompt_dir)
    model_config_path: Path = Field(default_factory=_default_model_config_path)
    env_file_path: Path = Field(default_factory=_default_env_file)
    model_preset: str | None = None

    runtime_use_docker: bool = False
    docker_image: str = "python:3.13-slim"
    docker_memory: str = "2g"
    docker_cpus: float = 2.0
    docker_network_disabled: bool = True

    default_command_timeout_seconds: int = 300
    default_max_attempts: int = Field(default=5, ge=1, le=10)
    orchestrator_context_request_rounds: int = Field(default=3, ge=1, le=20)
    critic_reviewer_count: int = Field(default=3, ge=1, le=10)
    validation_feedback_max_items: int = Field(default=12, ge=1, le=100)
    repo_scan_file_limit: int = 200
    repo_preview_files: int = 5
    repo_preview_bytes: int = 2048
    repo_focus_file_bytes: int = 16384
    memory_vector_dimensions: int = 128
    memory_embedding_max_chars: int = 4000
    memory_max_hits: int = 5
    worker_poll_interval_seconds: float = 0.5
    job_lease_seconds: int = 300
    run_embedded_worker: bool = False
    rag_task_file_limit: int = Field(default=3, ge=1, le=20)
    rag_task_max_results_per_unit: int = Field(default=2, ge=1, le=20)
    rag_task_min_relevance_score: float = Field(default=0.58, ge=0.0, le=1.0)
    rag_task_total_examples_limit: int = Field(default=5, ge=1, le=50)
    rag_max_per_dataset: int = Field(default=2, ge=1, le=20)
    validation_ruff_repo_file_cap: int = Field(default=20, ge=1)
    validation_ruff_target_file_limit: int = Field(default=10, ge=1)
    validation_ruff_version_timeout_seconds: int = Field(default=5, ge=1)
    validation_ruff_check_timeout_seconds: int = Field(default=30, ge=1)
    validation_rag_max_results_per_unit: int = Field(default=3, ge=1, le=20)
    validation_rag_min_relevance_score: float = Field(default=0.67, ge=0.0, le=1.0)
    provider_retry_attempts: int = Field(default=3, ge=1, le=10)
    provider_retry_backoff_base_seconds: float = Field(default=0.5, ge=0.0)
    rag_keyword_exact_hit_boost_per_term: float = Field(default=0.05, ge=0.0)
    rag_keyword_exact_hit_boost_cap: float = Field(default=0.12, ge=0.0)
    rag_keyword_error_class_boost: float = Field(default=0.10, ge=0.0)
    rag_keyword_function_name_boost: float = Field(default=0.08, ge=0.0)
    rag_vector_weight: float = Field(default=0.45, ge=0.0)
    rag_keyword_weight: float = Field(default=0.25, ge=0.0)
    rag_keyword_weight_no_embedding: float = Field(default=0.70, ge=0.0)
    rag_category_match_boost: float = Field(default=0.15, ge=0.0)
    rag_category_mismatch_penalty: float = Field(default=0.05, ge=0.0)
    rag_query_category_match_boost: float = Field(default=0.12, ge=0.0)
    rag_error_class_match_boost: float = Field(default=0.12, ge=0.0)
    rag_error_class_mismatch_penalty: float = Field(default=0.04, ge=0.0)
    rag_rerank_function_name_boost: float = Field(default=0.05, ge=0.0)
    rag_required_term_boost_per_term: float = Field(default=0.04, ge=0.0)
    rag_required_term_boost_cap: float = Field(default=0.10, ge=0.0)
    rag_dataset_preference_base_boost: float = Field(default=0.08, ge=0.0)
    rag_dataset_preference_decay: float = Field(default=0.01, ge=0.0)
    rag_dataset_preference_min_boost: float = Field(default=0.02, ge=0.0)
    rag_example_kind_base_boost: float = Field(default=0.09, ge=0.0)
    rag_example_kind_decay: float = Field(default=0.02, ge=0.0)
    rag_example_kind_min_boost: float = Field(default=0.03, ge=0.0)
    rag_example_kind_function_bonus: float = Field(default=0.02, ge=0.0)
    rag_example_kind_short_query_bonus: float = Field(default=0.02, ge=0.0)
    rag_short_query_line_limit: int = Field(default=80, ge=1)
    rag_synthetic_penalty_preferred: float = Field(default=0.08, ge=0.0)
    rag_synthetic_penalty_default: float = Field(default=0.04, ge=0.0)
    rag_telemetry_success_boost: float = Field(default=0.05, ge=0.0)
    rag_telemetry_failure_penalty: float = Field(default=0.02, ge=0.0)
    embedding_backend: str = Field(
        default="auto",
        pattern="^(auto|hash|sentence-transformers|openai)$",
    )
    embedding_model_name: str = "all-MiniLM-L6-v2"
    allow_embedding_model_downloads: bool = False
    rag_seed_examples_path: Path | None = None
    rag_auto_import_seed_examples: bool = False
    openai_compat_api_key: str | None = None
    openai_compat_model_name: str = "aiiq-chat"
    openai_compat_model_step: str = Field(
        default="coding",
        pattern="^(planning|coding|analysis|failure_analysis|summary)$",
    )
    openai_compat_auto_model_name: str | None = "aiiq"
    openai_compat_upstream_model: str | None = None
    openai_compat_upstream_provider: str | None = None
    openai_compat_agent_model_name: str | None = "aiiq-agent"
    openai_compat_agent_repo_path: Path | None = None
    openai_compat_agent_engine: str | None = Field(default=None, pattern="^(langgraph|microsoft)$")
    openai_compat_agent_max_attempts: int | None = Field(default=None, ge=1, le=10)
    openai_compat_agent_timeout_seconds: int = Field(default=1800, ge=1, le=86400)
    openai_compat_agent_poll_interval_seconds: float = Field(default=1.0, ge=0.05, le=60.0)

    @field_validator(
        "model_preset",
        "openai_compat_auto_model_name",
        "openai_compat_upstream_model",
        "openai_compat_upstream_provider",
        "openai_compat_agent_model_name",
        "openai_compat_agent_engine",
        mode="before",
    )
    @classmethod
    def _blank_optional_strings_to_none(cls, value: Any) -> Any:
        if isinstance(value, str) and not value.strip():
            return None
        return value

    @field_validator("openai_compat_agent_repo_path", mode="before")
    @classmethod
    def _blank_optional_paths_to_none(cls, value: Any) -> Any:
        if isinstance(value, str) and not value.strip():
            return None
        return value

    def model_post_init(self, __context: Any) -> None:
        self._apply_tls_ca_bundle()

    def prepare_dirs(self) -> None:
        self.data_dir.mkdir(parents=True, exist_ok=True)
        self.artifacts_dir.mkdir(parents=True, exist_ok=True)
        self.database_path.parent.mkdir(parents=True, exist_ok=True)
        self.memory_database_path.parent.mkdir(parents=True, exist_ok=True)
        self.workspaces_dir.mkdir(parents=True, exist_ok=True)

    def _apply_tls_ca_bundle(self) -> None:
        existing_bundle = os.environ.get("AIIQ_CA_BUNDLE")
        if not existing_bundle:
            for env_name in TLS_CA_ENV_VARS:
                if os.environ.get(env_name):
                    existing_bundle = os.environ[env_name]
                    break
        if existing_bundle:
            os.environ.setdefault("AIIQ_CA_BUNDLE", existing_bundle)
            return

        ca_bundle_path = self.ca_bundle_path
        if ca_bundle_path is None and self.default_ca_bundle_path and self.default_ca_bundle_path.exists():
            ca_bundle_path = self.default_ca_bundle_path
        if ca_bundle_path is None:
            return

        os.environ.setdefault("AIIQ_CA_BUNDLE", str(ca_bundle_path))
