"""
User config (~/.loom/config.yaml) — load, save, and merge with env vars.

The config file stores defaults for LLM backends, RAG settings, and Workshop
preferences. Values can be overridden by environment variables or CLI flags.

Priority (highest wins): CLI flags > env vars > config.yaml > built-in defaults.
"""

from __future__ import annotations

import os
import stat
from dataclasses import asdict, dataclass, fields
from pathlib import Path
from typing import Any

DEFAULT_CONFIG_PATH = "~/.loom/config.yaml"


@dataclass
class LoomConfig:
    """Parsed representation of ~/.loom/config.yaml."""

    # LLM backends
    ollama_url: str | None = None
    ollama_model: str = "llama3.2:3b"
    anthropic_api_key: str | None = None
    frontier_model: str = "claude-opus-4-20250514"
    embedding_model: str = "nomic-embed-text"

    # RAG pipeline
    rag_data_dir: str | None = None
    rag_vector_store: str = "duckdb"
    rag_db_path: str = "~/.loom/rag.duckdb"

    # Workshop
    workshop_port: int = 8080


# ---------------------------------------------------------------------------
# Env-var mapping: config field → env var name
# ---------------------------------------------------------------------------

_ENV_MAP: dict[str, str] = {
    "ollama_url": "OLLAMA_URL",
    "ollama_model": "OLLAMA_MODEL",
    "anthropic_api_key": "ANTHROPIC_API_KEY",
    "frontier_model": "FRONTIER_MODEL",
}


def load_config(path: str = DEFAULT_CONFIG_PATH) -> LoomConfig:
    """Load config from YAML file. Returns defaults if file is missing."""
    expanded = Path(path).expanduser()
    if not expanded.exists():
        return LoomConfig()

    import yaml

    with expanded.open() as f:
        data = yaml.safe_load(f) or {}

    # Flatten nested sections into flat dict
    flat: dict[str, Any] = {}
    for section_key in ("backends", "rag", "workshop"):
        section = data.get(section_key, {})
        if isinstance(section, dict):
            flat.update(section)

    # Also accept top-level keys
    valid_fields = {f.name for f in fields(LoomConfig)}
    flat.update({k: v for k, v in data.items() if k in valid_fields})

    # Build config with only recognized fields
    kwargs = {k: v for k, v in flat.items() if k in valid_fields and v is not None}
    return LoomConfig(**kwargs)


def save_config(config: LoomConfig, path: str = DEFAULT_CONFIG_PATH) -> None:  # noqa: PLR0912
    """Write config to YAML file, creating parent dirs as needed.

    Sets file permissions to 0o600 (owner read/write only) since the file
    may contain API keys.
    """
    import yaml

    expanded = Path(path).expanduser()
    expanded.parent.mkdir(parents=True, exist_ok=True)

    # Organize into sections for readability
    data: dict[str, Any] = {}

    backends: dict[str, Any] = {}
    if config.ollama_url:
        backends["ollama_url"] = config.ollama_url
    if config.ollama_model != "llama3.2:3b":
        backends["ollama_model"] = config.ollama_model
    if config.anthropic_api_key:
        backends["anthropic_api_key"] = config.anthropic_api_key
    if config.frontier_model != "claude-opus-4-20250514":
        backends["frontier_model"] = config.frontier_model
    if config.embedding_model != "nomic-embed-text":
        backends["embedding_model"] = config.embedding_model
    if backends:
        data["backends"] = backends

    rag: dict[str, Any] = {}
    if config.rag_data_dir:
        rag["rag_data_dir"] = config.rag_data_dir
    if config.rag_vector_store != "duckdb":
        rag["rag_vector_store"] = config.rag_vector_store
    if config.rag_db_path != "~/.loom/rag.duckdb":
        rag["rag_db_path"] = config.rag_db_path
    if rag:
        data["rag"] = rag

    workshop: dict[str, Any] = {}
    if config.workshop_port != 8080:
        workshop["workshop_port"] = config.workshop_port
    if workshop:
        data["workshop"] = workshop

    content = "# Loom configuration — generated by `loom setup`\n"
    content += "# This file may contain API keys. Keep it private.\n\n"
    if data:
        content += yaml.dump(data, default_flow_style=False, sort_keys=False)
    else:
        content += "# No non-default values configured.\n"

    expanded.write_text(content)
    expanded.chmod(stat.S_IRUSR | stat.S_IWUSR)  # 0o600


def resolve_config(
    cli_overrides: dict[str, Any] | None = None,
    config_path: str = DEFAULT_CONFIG_PATH,
) -> LoomConfig:
    """Merge: built-in defaults ← config.yaml ← env vars ← cli_overrides.

    Args:
        cli_overrides: Dict of field_name → value from CLI flags.
        config_path: Path to the YAML config file.

    Returns:
        Fully resolved LoomConfig.
    """
    # Start from config file (includes built-in defaults for missing fields)
    config = load_config(config_path)
    config_dict = asdict(config)

    # Layer 2: env vars override config file
    for field_name, env_var in _ENV_MAP.items():
        env_val = os.environ.get(env_var)
        if env_val is not None:
            config_dict[field_name] = env_val

    # Layer 3: CLI overrides win
    if cli_overrides:
        for k, v in cli_overrides.items():
            if v is not None and k in config_dict:
                config_dict[k] = v

    return LoomConfig(**config_dict)


def apply_config_to_env(config: LoomConfig) -> None:
    """Set env vars from config for backwards compat with ``build_backends_from_env``.

    Only sets variables that are NOT already present in the environment,
    so explicit ``export OLLAMA_URL=...`` always wins.
    """
    for field_name, env_var in _ENV_MAP.items():
        value = getattr(config, field_name, None)
        if value is not None:
            os.environ.setdefault(env_var, str(value))
