"""YAML config loading with Pydantic validation and 3-level merge."""

from __future__ import annotations

import os
import sys
from pathlib import Path
from typing import Any

import yaml
from platformdirs import user_config_dir, user_data_dir
from pydantic import BaseModel, Field

# --- Pydantic models ---


class GitHubOrgConfig(BaseModel):
    """Configuration for a single GitHub organization."""

    name: str
    include: list[str] = Field(default_factory=list)
    exclude: list[str] = Field(default_factory=list)
    depth: int | None = 1


class GitHubConfig(BaseModel):
    """GitHub source configuration."""

    token: str | None = None
    use_ssh: bool = True
    orgs: list[GitHubOrgConfig] = Field(default_factory=list)


class GitLabGroupConfig(BaseModel):
    """Configuration for a single GitLab group."""

    name: str
    include: list[str] = Field(default_factory=list)
    exclude: list[str] = Field(default_factory=list)
    depth: int | None = 1


class GitLabInstanceConfig(BaseModel):
    """Configuration for a single GitLab instance."""

    url: str
    token: str | None = None
    groups: list[GitLabGroupConfig] = Field(default_factory=list)


class GitLabConfig(BaseModel):
    """GitLab source configuration."""

    token: str | None = None
    use_ssh: bool = True
    instances: list[GitLabInstanceConfig] = Field(default_factory=list)


class SlackConfig(BaseModel):
    """Slack source configuration."""

    token: str | None = None
    xoxc_token: str | None = None
    d_cookie: str | None = None
    workspace_id: str = "E030G10V24F"
    enterprise_id: str = ""
    host: str = "redhat.enterprise.slack.com"
    channels: list[str] = Field(default_factory=list)
    history_days: int = 730


class JiraConfig(BaseModel):
    """Jira Server/DC source configuration."""

    url: str = "https://issues.redhat.com"
    token: str | None = None
    projects: list[str] = Field(default_factory=lambda: ["RHCLOUD", "AAP", "ANSTRAT", "INSIGHTS"])
    max_results: int = 100
    include_comments: bool = True
    history_days: int = 365


class WebConfig(BaseModel):
    """Web scrape source configuration."""

    urls: list[str] = Field(default_factory=list)
    timeout: int = 30
    max_depth: int = 3
    max_pages_per_site: int = 200


class PagesConfig(BaseModel):
    """GitLab Pages source configuration.

    Each site is a *.pages.redhat.com subdomain backed by a GitLab repo
    at gitlab.cee.redhat.com. The connector clones the repo and indexes
    its content (markdown, HTML, etc.) into the oracle_pages collection.
    """

    gitlab_url: str = "https://gitlab.cee.redhat.com"
    token: str | None = None  # GitLab PAT — falls back to sources.gitlab.token
    sites: list[str] = Field(default_factory=lambda: [
        "agent-files",
        "ai-assisted-coding",
        "ansible",
        "brq-map",
        "consoledot",
        "core-platform-apps",
        "data-hub",
        "dataverse",
        "devhub",
        "fuse",
        "fuse-prod",
        "infrastories",
        "insights-experiences",
        "it-akamai",
        "it-platform",
        "ocponrhv",
        "osbuild",
        "osci",
        "ospodata",
        "poseidon",
        "releng-league",
        "rhel-lightspeed",
        "shiftstack",
        "spmm",
        "sysdeseng",
        "vault",
    ])
    depth: int = 1


class SourcesConfig(BaseModel):
    """All data source configurations."""

    github: GitHubConfig = Field(default_factory=GitHubConfig)
    gitlab: GitLabConfig = Field(default_factory=GitLabConfig)
    slack: SlackConfig = Field(default_factory=SlackConfig)
    jira: JiraConfig = Field(default_factory=JiraConfig)
    web: WebConfig = Field(default_factory=WebConfig)
    pages: PagesConfig = Field(default_factory=PagesConfig)
    max_concurrent_clones: int = 5


class EmbeddingsConfig(BaseModel):
    """Embedding provider configuration."""

    provider: str = "sentence-transformers"
    model: str = "all-MiniLM-L6-v2"
    batch_size: int = 64


class ChunkingConfig(BaseModel):
    """Chunking configuration."""

    method: str = "ast"
    chunk_size: int = 500
    chunk_overlap: int = 50
    max_file_size: int = 524288  # 512KB
    index_extensions: list[str] = Field(
        default_factory=lambda: [
            ".py",
            ".js",
            ".ts",
            ".jsx",
            ".tsx",
            ".java",
            ".go",
            ".rs",
            ".rb",
            ".c",
            ".cpp",
            ".h",
            ".hpp",
            ".yaml",
            ".yml",
            ".json",
            ".toml",
            ".md",
            ".rst",
            ".txt",
            ".sh",
            ".bash",
            ".zsh",
            ".sql",
            ".html",
            ".css",
            ".xml",
        ]
    )


class VectorDBConfig(BaseModel):
    """Vector database configuration."""

    embeddings: EmbeddingsConfig = Field(default_factory=EmbeddingsConfig)
    chunking: ChunkingConfig = Field(default_factory=ChunkingConfig)


class LLMConfig(BaseModel):
    """LLM provider configuration."""

    default_provider: str = "ollama"
    ollama_base_url: str = "http://localhost:11434"
    ollama_model: str = "llama3.2"
    openai_api_key: str | None = None
    anthropic_api_key: str | None = None


class RAGConfig(BaseModel):
    """RAG pipeline configuration."""

    top_k: int = 10
    min_score: float = 0.3
    system_prompt: str = (
        "You are the Red Hat Oracle, an AI assistant with deep knowledge of "
        "Red Hat engineering codebases and internal communications. Answer questions "
        "using the provided context. Always cite your sources."
    )


class OracleConfig(BaseModel):
    """Top-level configuration for Red Hat Oracle."""

    sources: SourcesConfig = Field(default_factory=SourcesConfig)
    vectordb: VectorDBConfig = Field(default_factory=VectorDBConfig)
    llm: LLMConfig = Field(default_factory=LLMConfig)
    rag: RAGConfig = Field(default_factory=RAGConfig)
    data_dir: str | None = None


# --- Config loading ---

_ENV_VAR_MAP: dict[tuple[str, ...], str] = {
    ("sources", "github", "token"): "ORACLE_GITHUB_TOKEN",
    ("sources", "gitlab", "token"): "ORACLE_GITLAB_TOKEN",
    ("sources", "slack", "token"): "ORACLE_SLACK_TOKEN",
    ("sources", "slack", "xoxc_token"): "ORACLE_SLACK_XOXC_TOKEN",
    ("sources", "slack", "d_cookie"): "ORACLE_SLACK_D_COOKIE",
    ("sources", "jira", "token"): "ORACLE_JIRA_TOKEN",
    ("llm", "openai_api_key"): "OPENAI_API_KEY",
    ("llm", "anthropic_api_key"): "ANTHROPIC_API_KEY",
}


def _deep_merge(base: dict[str, Any], override: dict[str, Any]) -> dict[str, Any]:
    """Deep merge two dicts. Values in `override` take precedence."""
    merged = base.copy()
    for key, value in override.items():
        if key in merged and isinstance(merged[key], dict) and isinstance(value, dict):
            merged[key] = _deep_merge(merged[key], value)
        else:
            merged[key] = value
    return merged


def _apply_env_vars(data: dict[str, Any]) -> dict[str, Any]:
    """Fill in null config values from environment variables."""
    for key_path, env_var in _ENV_VAR_MAP.items():
        env_value = os.environ.get(env_var)
        if env_value is None:
            continue
        # Walk the key path to check if the current value is None
        current = data
        for key in key_path[:-1]:
            if key not in current or not isinstance(current[key], dict):
                current[key] = {}
            current = current[key]
        last_key = key_path[-1]
        if current.get(last_key) is None:
            current[last_key] = env_value
    return data


def _config_paths() -> list[Path]:
    """Return config file paths in precedence order (lowest first)."""
    paths: list[Path] = []

    # System config (Linux/macOS only)
    if sys.platform != "win32":
        paths.append(Path("/etc/redhat-oracle/oracle.yaml"))

    # User config
    user_dir = Path(user_config_dir("redhat-oracle"))
    paths.append(user_dir / "oracle.yaml")

    # Project config (current directory)
    paths.append(Path("oracle.yaml"))

    return paths


def get_data_dir(config: OracleConfig | None = None) -> Path:
    """Return the data directory, respecting config override."""
    if config and config.data_dir:
        return Path(config.data_dir)
    return Path(user_data_dir("redhat-oracle"))


def _apply_chrome_slack_creds(data: dict[str, Any]) -> dict[str, Any]:
    """Auto-extract Slack xoxc_token/d_cookie from Chrome if not already set."""
    slack = data.get("sources", {}).get("slack", {})
    if slack.get("xoxc_token") and slack.get("d_cookie"):
        return data  # already configured
    if slack.get("token"):
        return data  # using bot token

    try:
        from redhat_oracle.core.sources.slack_creds import auto_extract_slack_creds

        creds = auto_extract_slack_creds()
        if creds["xoxc_token"] and creds["d_cookie"]:
            data.setdefault("sources", {}).setdefault("slack", {})
            if not data["sources"]["slack"].get("xoxc_token"):
                data["sources"]["slack"]["xoxc_token"] = creds["xoxc_token"]
            if not data["sources"]["slack"].get("d_cookie"):
                data["sources"]["slack"]["d_cookie"] = creds["d_cookie"]
    except Exception:
        pass  # extraction is best-effort

    return data


def load_config(extra_paths: list[Path] | None = None) -> OracleConfig:
    """Load and merge config from standard paths, then validate.

    Args:
        extra_paths: Additional config file paths to merge (highest precedence last).

    Returns:
        Validated OracleConfig instance.
    """
    merged: dict[str, Any] = {}

    paths = _config_paths()
    if extra_paths:
        paths.extend(extra_paths)

    for path in paths:
        if path.is_file():
            with open(path) as f:
                data = yaml.safe_load(f)
            if isinstance(data, dict):
                merged = _deep_merge(merged, data)

    merged = _apply_env_vars(merged)
    merged = _apply_chrome_slack_creds(merged)

    return OracleConfig(**merged)
