"""Global configuration for the Vastian Network."""

from __future__ import annotations

import contextlib
from enum import StrEnum
from pathlib import Path

from pydantic import Field, model_validator
from pydantic_settings import BaseSettings


class LLMProvider(StrEnum):
    OPENAI = "openai"
    ANTHROPIC = "anthropic"
    OLLAMA = "ollama"
    AUTO = "auto"  # auto-detect based on available API keys
    LOCAL = "local"


# ── Model Catalog ────────────────────────────────────────
# Two models per provider: one powerful, one fast/cheap.

OPENAI_MODELS = {
    "default": "gpt-4o",
    "fast": "gpt-4o-mini",
}

ANTHROPIC_MODELS = {
    "default": "claude-opus-4-5-20251101",  # Opus 4.5 — frontier reasoning
    "fast": "claude-sonnet-4-20250514",  # Sonnet 4 — fast & capable
}

# Ollama uses a single local model for both tiers (resolved at runtime from OLLAMA_MODEL)
OLLAMA_MODELS = {
    "default": "llama3.1",
    "fast": "llama3.1",
}

# Unified lookup: tier -> (provider, model)
MODEL_CATALOG: dict[str, dict[str, tuple[str, str]]] = {
    "openai": {
        "default": ("openai", OPENAI_MODELS["default"]),
        "fast": ("openai", OPENAI_MODELS["fast"]),
    },
    "anthropic": {
        "default": ("anthropic", ANTHROPIC_MODELS["default"]),
        "fast": ("anthropic", ANTHROPIC_MODELS["fast"]),
    },
    "ollama": {
        "default": ("ollama", OLLAMA_MODELS["default"]),
        "fast": ("ollama", OLLAMA_MODELS["fast"]),
    },
}


class Settings(BaseSettings):
    """Vastian Network global settings — loaded from environment / .env file."""

    # ── LLM ──────────────────────────────────────────
    llm_provider: LLMProvider = Field(default=LLMProvider.OLLAMA, alias="VASTIAN_LLM_PROVIDER")
    openai_api_key: str = Field(default="", alias="OPENAI_API_KEY")
    anthropic_api_key: str = Field(default="", alias="ANTHROPIC_API_KEY")
    default_model: str = Field(default="gpt-4o", alias="VASTIAN_DEFAULT_MODEL")
    fast_model: str = Field(default="gpt-4o-mini", alias="VASTIAN_FAST_MODEL")

    # ── Knowledge Store ──────────────────────────────
    chroma_persist_dir: Path = Field(
        default=Path("./data/chromadb"), alias="VASTIAN_CHROMA_PERSIST_DIR"
    )
    sqlite_path: Path = Field(default=Path("./data/vastian.db"), alias="VASTIAN_SQLITE_PATH")

    # ── Paths ────────────────────────────────────────
    data_root: Path | None = Field(default=None, alias="VASTIAN_DATA_ROOT")
    agents_dir: Path = Field(default=Path("./agents"))
    teams_dir: Path = Field(default=Path("./teams"))
    prompts_dir: Path = Field(default=Path("./prompts"))
    docs_dir: Path = Field(default=Path("./docs"))
    state_dir: Path = Field(default=Path("./state"), alias="VASTIAN_STATE_DIR")

    @model_validator(mode="after")
    def _resolve_data_root_paths(self) -> Settings:
        """Resolve docs_dir, state_dir, etc. from data root. Uses platform default when not set.

        Also loads ``config/local/settings.json`` from the data root and applies
        any values that aren't already set via environment variables.  This lets
        installed-app users configure the system without a ``.env`` file.
        """
        import sys

        from vastian.storage.data_root import get_data_root

        root = get_data_root(None)
        self.data_root = root
        self.docs_dir = root / "docs"
        self.state_dir = root / "state"
        self.chroma_persist_dir = root / "data" / "chromadb"
        self.sqlite_path = root / "data" / "vastian.db"
        self.log_dir = root / "logs"
        self.local_config_dir = root / "config" / "local"

        # Load settings.json and apply to any fields still at their defaults
        try:
            from vastian.config_store import load_settings_json

            stored = load_settings_json(root)
            if stored:
                import os

                # Only override fields where both the env var is unset AND
                # the current value is the Pydantic default
                _json_to_field = {
                    "user_name": ("VASTIAN_USER_NAME", "User"),
                    "llm_provider": ("VASTIAN_LLM_PROVIDER", None),
                    "openai_api_key": ("OPENAI_API_KEY", ""),
                    "anthropic_api_key": ("ANTHROPIC_API_KEY", ""),
                    "default_model": ("VASTIAN_DEFAULT_MODEL", "gpt-4o"),
                    "fast_model": ("VASTIAN_FAST_MODEL", "gpt-4o-mini"),
                    "user_tier": ("VASTIAN_USER_TIER", "admin"),
                    "log_level": ("VASTIAN_LOG_LEVEL", "INFO"),
                }
                for json_key, (env_key, default_val) in _json_to_field.items():
                    if (
                        json_key in stored
                        and stored[json_key] not in (None, "")
                        and not os.environ.get(env_key)
                    ):
                        current = getattr(self, json_key, None)
                        if current is None or (
                            default_val is not None and str(current) == str(default_val)
                        ):
                            value = stored[json_key]
                            # Coerce llm_provider string to enum so comparisons work
                            if json_key == "llm_provider" and isinstance(value, str):
                                with contextlib.suppress(ValueError):
                                    value = LLMProvider(value)
                            setattr(self, json_key, value)
        except Exception:
            pass  # settings.json is optional — don't break startup

        # When frozen (PyInstaller), agents/prompts/teams live in the bundle (_MEIPASS)
        if getattr(sys, "frozen", False):
            bundle = Path(getattr(sys, "_MEIPASS", Path(sys.executable).parent))
            self.agents_dir = bundle / "agents"
            self.prompts_dir = bundle / "prompts"
            self.teams_dir = bundle / "teams"
        elif not self.agents_dir.exists():
            # Non-frozen (pip install / dev): search multiple locations for agents/
            pkg_dir = Path(__file__).resolve().parent  # src/vastian/
            search_paths = [
                pkg_dir.parents[1],  # repo root (dev mode: vastian-network/)
                pkg_dir.parents[0],  # src/ directory
                Path.cwd(),  # current working directory
                root,  # data root (may have agents copied by seed)
            ]
            # Also check site-packages (pip install may place agents there)
            if hasattr(pkg_dir, "parent"):
                search_paths.append(pkg_dir.parent)
            for parent in search_paths:
                if parent and (parent / "agents").is_dir():
                    self.agents_dir = parent / "agents"
                    if (parent / "prompts").is_dir():
                        self.prompts_dir = parent / "prompts"
                    if (parent / "teams").is_dir():
                        self.teams_dir = parent / "teams"
                    break
        return self

    # ── Logging ──────────────────────────────────────
    log_level: str = Field(default="INFO", alias="VASTIAN_LOG_LEVEL")
    log_dir: Path = Field(default=Path("./logs"), alias="VASTIAN_LOG_DIR")

    # ── User Identity ─────────────────────────────────
    user_name: str = Field(default="User", alias="VASTIAN_USER_NAME")

    # ── Mercury Banking ────────────────────────────────
    mercury_api_token: str = Field(default="", alias="MERCURY_API_TOKEN")
    mercury_account_id: str = Field(default="", alias="MERCURY_ACCOUNT_ID")
    mercury_account_name: str = Field(default="", alias="MERCURY_ACCOUNT_NAME")
    # Read-only accounts (individual env vars, collected into a list)
    mercury_ro_1: str = Field(default="", alias="MERCURY_READONLY_ACCOUNT_1")
    mercury_ro_2: str = Field(default="", alias="MERCURY_READONLY_ACCOUNT_2")
    mercury_ro_3: str = Field(default="", alias="MERCURY_READONLY_ACCOUNT_3")
    mercury_ro_4: str = Field(default="", alias="MERCURY_READONLY_ACCOUNT_4")
    mercury_ro_5: str = Field(default="", alias="MERCURY_READONLY_ACCOUNT_5")
    mercury_ro_6: str = Field(default="", alias="MERCURY_READONLY_ACCOUNT_6")
    mercury_ro_7: str = Field(default="", alias="MERCURY_READONLY_ACCOUNT_7")
    mercury_ro_8: str = Field(default="", alias="MERCURY_READONLY_ACCOUNT_8")
    # Read-only account names (labels for Finley's context)
    mercury_rn_1: str = Field(default="", alias="MERCURY_READONLY_NAME_1")
    mercury_rn_2: str = Field(default="", alias="MERCURY_READONLY_NAME_2")
    mercury_rn_3: str = Field(default="", alias="MERCURY_READONLY_NAME_3")
    mercury_rn_4: str = Field(default="", alias="MERCURY_READONLY_NAME_4")
    mercury_rn_5: str = Field(default="", alias="MERCURY_READONLY_NAME_5")
    mercury_rn_6: str = Field(default="", alias="MERCURY_READONLY_NAME_6")
    mercury_rn_7: str = Field(default="", alias="MERCURY_READONLY_NAME_7")
    mercury_rn_8: str = Field(default="", alias="MERCURY_READONLY_NAME_8")

    @property
    def mercury_readonly_accounts(self) -> list[str]:
        """Return all non-empty read-only Mercury account IDs."""
        return [
            aid
            for aid in [
                self.mercury_ro_1,
                self.mercury_ro_2,
                self.mercury_ro_3,
                self.mercury_ro_4,
                self.mercury_ro_5,
                self.mercury_ro_6,
                self.mercury_ro_7,
                self.mercury_ro_8,
            ]
            if aid
        ]

    @property
    def mercury_account_names(self) -> dict[str, str]:
        """Return a mapping of account ID -> user-assigned name."""
        names: dict[str, str] = {}
        if self.mercury_account_id and self.mercury_account_name:
            names[self.mercury_account_id] = self.mercury_account_name
        ro_ids = [
            self.mercury_ro_1,
            self.mercury_ro_2,
            self.mercury_ro_3,
            self.mercury_ro_4,
            self.mercury_ro_5,
            self.mercury_ro_6,
            self.mercury_ro_7,
            self.mercury_ro_8,
        ]
        ro_names = [
            self.mercury_rn_1,
            self.mercury_rn_2,
            self.mercury_rn_3,
            self.mercury_rn_4,
            self.mercury_rn_5,
            self.mercury_rn_6,
            self.mercury_rn_7,
            self.mercury_rn_8,
        ]
        for aid, aname in zip(ro_ids, ro_names, strict=False):
            if aid and aname:
                names[aid] = aname
        return names

    # ── Google Drive backup (optional) ─────────────────
    google_drive_client_id: str = Field(default="", alias="GOOGLE_DRIVE_CLIENT_ID")
    google_drive_client_secret: str = Field(default="", alias="GOOGLE_DRIVE_CLIENT_SECRET")

    # ── Dropbox backup (optional) ──────────────────────
    dropbox_app_key: str = Field(default="", alias="DROPBOX_APP_KEY")
    dropbox_app_secret: str = Field(default="", alias="DROPBOX_APP_SECRET")
    dropbox_access_token: str = Field(default="", alias="DROPBOX_ACCESS_TOKEN")

    # ── Meeting Transcript Providers ────────────────────
    pocket_api_key: str = Field(default="", alias="POCKET_API_KEY")
    fireflies_api_key: str = Field(default="", alias="FIREFLIES_API_KEY")

    # ── Zo Computer (SMS/email messaging bridge) ─────
    zo_api_key: str = Field(default="", alias="ZO_API_KEY")

    # ── Local Config ──────────────────────────────────
    local_config_dir: Path = Field(default=Path("./config/local"), alias="VASTIAN_LOCAL_CONFIG_DIR")

    # ── Access Tier ───────────────────────────────────
    user_tier: str = Field(default="open", alias="VASTIAN_USER_TIER")

    # ── Mentor Council ───────────────────────────────
    council_max_rounds: int = Field(default=5, alias="VASTIAN_COUNCIL_MAX_ROUNDS")
    council_consensus_threshold: float = Field(
        default=0.75, alias="VASTIAN_COUNCIL_CONSENSUS_THRESHOLD"
    )

    model_config = {
        "env_file": ".env",
        "env_file_encoding": "utf-8",
        "extra": "ignore",
    }

    # ── Template Var Builders ─────────────────────────

    def _build_mercury_account_directory(self) -> str:
        """Build a markdown table of Mercury accounts from env-var settings."""
        rows: list[str] = []
        # Primary (read-write) account
        if self.mercury_account_id:
            name = self.mercury_account_name or "Primary"
            rows.append(f"  | {self.mercury_account_id:<17} | {name:<33} | read-write |")
        # Read-only accounts
        ro_ids = [
            self.mercury_ro_1,
            self.mercury_ro_2,
            self.mercury_ro_3,
            self.mercury_ro_4,
            self.mercury_ro_5,
            self.mercury_ro_6,
            self.mercury_ro_7,
            self.mercury_ro_8,
        ]
        ro_names = [
            self.mercury_rn_1,
            self.mercury_rn_2,
            self.mercury_rn_3,
            self.mercury_rn_4,
            self.mercury_rn_5,
            self.mercury_rn_6,
            self.mercury_rn_7,
            self.mercury_rn_8,
        ]
        for aid, aname in zip(ro_ids, ro_names, strict=False):
            if aid:
                label = aname or "Unnamed"
                rows.append(f"  | {aid:<17} | {label:<33} | read-only  |")

        if not rows:
            return "(No Mercury accounts configured — set MERCURY_* env vars)"

        header = (
            "  | Account ID        | Name                              | Access     |\n"
            "  |-------------------|-----------------------------------|------------|"
        )
        return header + "\n" + "\n".join(rows)

    def _load_financial_context(self) -> str:
        """Load financial context from config/local/financial-context.txt."""
        path = self.local_config_dir / "financial-context.txt"
        if path.exists():
            return path.read_text(encoding="utf-8").strip()
        return "(No financial context configured — create config/local/financial-context.txt)"

    def build_agent_template_vars(self) -> dict[str, str]:
        """Build the full template variable dict for agent YAML substitution."""
        return {
            "user_name": self.user_name,
            "mercury_account_directory": self._build_mercury_account_directory(),
            "financial_context": self._load_financial_context(),
        }


def get_settings() -> Settings:
    """Return a cached Settings instance."""
    return Settings()


def make_user_settings(
    data_root: Path,
    *,
    user_name: str = "User",
    user_tier: str = "open",
    extra: dict[str, object] | None = None,
) -> Settings:
    """Construct a per-user Settings instance without reading env vars.

    Used by the cloud worker pool to create isolated Settings for each user.
    ``model_construct()`` bypasses Pydantic's env-var resolution and validators
    so we can point ``data_root``, ``docs_dir``, ``state_dir``, etc. at an
    ephemeral per-user directory (e.g. ``/tmp/vastian-user-<id>/``).

    Parameters
    ----------
    data_root:
        The user's ephemeral data root (hydrated from BYOS).
    user_name:
        Display name for template variable substitution.
    user_tier:
        Access tier (open / standard / elevated / admin).
    extra:
        Additional field overrides to apply.
    """
    fields: dict[str, object] = {
        "data_root": data_root,
        "docs_dir": data_root / "docs",
        "state_dir": data_root / "state",
        "chroma_persist_dir": data_root / "data" / "chromadb",
        "sqlite_path": data_root / "data" / "vastian.db",
        "log_dir": data_root / "logs",
        "local_config_dir": data_root / "config" / "local",
        "user_name": user_name,
        "user_tier": user_tier,
        # Inherit API keys from the host environment
        "openai_api_key": Settings.model_fields["openai_api_key"].default or "",
        "anthropic_api_key": Settings.model_fields["anthropic_api_key"].default or "",
    }
    # Pull actual API keys from env (they're shared, not per-user)
    import os

    fields["openai_api_key"] = os.environ.get("OPENAI_API_KEY", "")
    fields["anthropic_api_key"] = os.environ.get("ANTHROPIC_API_KEY", "")

    if extra:
        fields.update(extra)

    return Settings.model_construct(**fields)
