"""Takumi configuration loader (centralized, fail-closed).

Principles:
  - One obvious place for configuration: ~/.ember/config.yaml (or EMBER_CONFIG_PATH)
  - Fail-closed with clear, actionable errors; no silent fallbacks or auto-migrations
  - Treat secrets as references (never raw credential bytes here)
"""

from __future__ import annotations

from dataclasses import dataclass
import os
from functools import lru_cache
from pathlib import Path
from typing import Any, Dict
from urllib.parse import urlparse

import yaml
from pydantic import BaseModel, ConfigDict, Field, field_validator

_PRIMARY_CONFIG_ENV_VAR = "EMBER_CONFIG_PATH"
_DEFAULT_CONFIG_PATH = Path.home() / ".ember" / "config.yaml"


class TakumiConfigModel(BaseModel):
    base_url: str | None = None
    gateway: Dict[str, Any] = Field(default_factory=dict)

    model_config = ConfigDict(extra="allow")

    @field_validator("base_url")
    def _validate_base_url(cls, value: str | None) -> str | None:
        if value is None:
            return None
        cleaned = value.strip()
        if not cleaned:
            return None
        parsed = urlparse(cleaned)
        if not parsed.scheme or not parsed.netloc:
            raise ValueError("base_url must include scheme and host")
        return cleaned.rstrip("/")


@dataclass(frozen=True)
class TakumiSettings:
    """Takumi configuration envelope."""

    config: TakumiConfigModel

    @property
    def base_url(self) -> str | None:
        # One canonical field; do not silently fall back to nested locations.
        return self.config.base_url

    @property
    def gateway(self) -> Dict[str, Any]:
        return dict(self.config.gateway)


def _resolve_config_path(path: Path | None) -> Path:
    if path is not None:
        return path
    env_primary = os.getenv(_PRIMARY_CONFIG_ENV_VAR)
    if env_primary:
        return Path(os.path.expanduser(env_primary))
    return _DEFAULT_CONFIG_PATH


def load_takumi_settings(path: Path | None = None) -> TakumiSettings:
    """Load Takumi settings from disk without caching."""

    resolved = _resolve_config_path(path)
    if not resolved.exists():
        hint_lines = [
            f"Configuration not found at '{resolved}'.",
            "Create ~/.ember/config.yaml or point EMBER_CONFIG_PATH at your config file.",
        ]
        raise FileNotFoundError("\n".join(hint_lines))

    with resolved.open("r", encoding="utf-8") as handle:
        data = yaml.safe_load(handle) or {}

    if not isinstance(data, dict):
        raise ValueError(f"Takumi configuration '{resolved}' must be a mapping")

    model = TakumiConfigModel.model_validate(data)
    return TakumiSettings(config=model)


@lru_cache(maxsize=1)
def get_takumi_settings(path: Path | None = None) -> TakumiSettings:
    """Return cached Takumi settings; clears when path argument changes."""

    return load_takumi_settings(path)


def reload_takumi_settings(path: Path | None = None) -> TakumiSettings:
    """Force reload of Takumi settings, bypassing the cache."""

    get_takumi_settings.cache_clear()
    return get_takumi_settings(path)


__all__ = [
    "TakumiSettings",
    "get_takumi_settings",
    "load_takumi_settings",
    "reload_takumi_settings",
]
