"""Access-tier configuration loader — single source of truth for feature gating,
board visibility, model selection per agent, and agent enablement.

Reads config/access-tiers.yaml once and exposes pure-function helpers that
the orchestrator, feed, backup panel, and CLI can call.
"""

from __future__ import annotations

import logging
from functools import lru_cache
from pathlib import Path
from typing import Any

logger = logging.getLogger(__name__)


def _find_project_root() -> Path:
    """Locate project root (repo root). Used for data_root.txt resolution."""
    import os as _os

    env_root = _os.environ.get("VASTIAN_PROJECT_ROOT")
    if env_root:
        return Path(env_root).resolve()
    cwd = Path.cwd()
    # Check for project indicators (config or pyproject.toml)
    if (cwd / "config" / "access-tiers.yaml").exists():
        return cwd
    if (cwd / "pyproject.toml").exists():
        return cwd
    start = Path(__file__).resolve().parent
    for _ in range(8):
        if (start / "config" / "access-tiers.yaml").exists() or (start / "pyproject.toml").exists():
            return start
        if not start.parent or start.parent == start:
            break
        start = start.parent
    return Path(__file__).resolve().parents[2]  # fallback


@lru_cache(maxsize=1)
def _get_config_path() -> Path:
    """Resolve config path lazily. Uses data root config, falls back to seed in CI/fresh install."""
    from vastian.storage.data_root import get_config_dir

    path = get_config_dir(_find_project_root()) / "access-tiers.yaml"
    if path.exists():
        return path
    try:
        from vastian.seed import get_seed_path

        seed_path = get_seed_path("config", "access-tiers.yaml")
        if seed_path.exists():
            return seed_path
    except ImportError:
        pass
    return path  # return original for error messages


# ── Board constants ──────────────────────────────────────────────
BOARD_PREFIXES: dict[str, str] = {
    "user": "USR-",
    "comms": "MSG-",
    "company": "REL-",
    "agent": "CYC-",
    "ops": "OPS-",
}

VALID_TIERS = ("open", "standard", "elevated", "admin")
VALID_MODEL_TIERS = ("cheap", "mid", "premium")


# ── YAML loader (cached) ────────────────────────────────────────


@lru_cache(maxsize=1)
def _load_config() -> dict[str, Any]:
    """Load and cache the access-tiers YAML config."""
    try:
        import yaml  # type: ignore[import-untyped]
    except ImportError:
        logger.warning("PyYAML not installed; access-tiers config unavailable")
        return {}

    config_path = _get_config_path()
    if not config_path.exists():
        logger.warning("Access-tiers config not found at %s", config_path)
        return {}

    try:
        raw = config_path.read_text(encoding="utf-8")
        return yaml.safe_load(raw) or {}
    except Exception as e:
        logger.error("Failed to load access-tiers config: %s", e)
        return {}


def reload_config() -> dict[str, Any]:
    """Force-reload the config (call after hot-editing the YAML)."""
    _load_config.cache_clear()
    _get_config_path.cache_clear()
    return _load_config()


# ── Tier helpers ─────────────────────────────────────────────────


def _tier_config(user_tier: str) -> dict[str, Any]:
    """Return the sub-dict for the given tier, falling back to open."""
    cfg = _load_config()
    tiers = cfg.get("tiers", {})
    if user_tier not in tiers:
        logger.debug("Unknown tier '%s', falling back to 'open'", user_tier)
        user_tier = "open"
    return tiers.get(user_tier, {})


def is_feature_allowed(feature: str, user_tier: str) -> bool:
    """Check whether *feature* is accessible at *user_tier*."""
    tc = _tier_config(user_tier)
    features = tc.get("features", [])
    return "all" in features or feature in features


def visible_boards(user_tier: str) -> list[str]:
    """Return the list of board names visible at *user_tier*."""
    tc = _tier_config(user_tier)
    return list(tc.get("boards", ["user"]))


def is_agent_enabled(agent_id: str, user_tier: str) -> bool:
    """Return True if the agent is NOT disabled for background loops at the given tier.

    "Disabled" means the agent will not run in background loops or auto-suggest
    Kanban tasks.  Disabled agents CAN still participate in user-initiated
    features like councils, presentations, and direct invocation (/agent <id>).
    """
    tc = _tier_config(user_tier)
    disabled = tc.get("agent_disabled", [])
    return agent_id not in disabled


def get_model_tier_for_agent(agent_id: str, user_tier: str) -> str:
    """Return the model tier name (cheap/mid/premium) for an agent at a user tier.

    Falls back to the tier's default_model, then to 'mid'.
    """
    tc = _tier_config(user_tier)
    mapping = tc.get("agent_models", {})
    if agent_id in mapping:
        return mapping[agent_id]
    return tc.get("default_model", "mid")


def get_model_id(agent_id: str, user_tier: str, provider: str = "openai") -> str:
    """Resolve a concrete model ID for an agent at a user tier and provider.

    Example: get_model_id("henry", "admin", "anthropic") -> "claude-opus-4-5-20251101"
    """
    cfg = _load_config()
    model_tier = get_model_tier_for_agent(agent_id, user_tier)
    models = cfg.get("models", {})
    tier_models = models.get(model_tier, {})
    model_id = tier_models.get(provider)
    if model_id:
        return model_id
    # Fallback: try mid tier, then cheap
    for fallback in ("mid", "cheap"):
        fb = models.get(fallback, {}).get(provider)
        if fb:
            return fb
    # Last resort: hard-coded defaults per provider
    if provider == "openai":
        return "gpt-4o-mini"
    if provider == "ollama":
        return "llama3.1"  # Ollama local model
    return "claude-sonnet-4-20250514"


def get_board_prefix(board: str) -> str:
    """Return the ticket prefix for a board name, e.g. 'user' -> 'USR-'."""
    return BOARD_PREFIXES.get(board, "USR-")


# ── Scenario domains ─────────────────────────────────────


@lru_cache(maxsize=1)
def _load_scenario_config() -> dict[str, Any]:
    """Load and cache the scenario domains config."""
    try:
        import yaml  # type: ignore[import-untyped]
    except ImportError:
        return {}
    scenarios_path = _get_config_path().parent / "scenarios" / "domains.yaml"
    if not scenarios_path.exists():
        return {}
    try:
        raw = scenarios_path.read_text(encoding="utf-8")
        return yaml.safe_load(raw) or {}
    except Exception:
        return {}


def get_scenario_domains() -> list[dict[str, str]]:
    """Return the list of scenario domain dicts (id, label, description, color)."""
    cfg = _load_scenario_config()
    return cfg.get("domains", [])


def get_scenario_template(domain_id: str) -> dict[str, Any]:
    """Return the Kiran simulation template for a domain, if any."""
    cfg = _load_scenario_config()
    templates = cfg.get("templates", {})
    return templates.get(domain_id, {})


# ── Summary / introspection ──────────────────────────────────────


def tier_summary(user_tier: str) -> dict[str, Any]:
    """Return a human-readable summary dict for a given tier."""
    tc = _tier_config(user_tier)
    return {
        "tier": user_tier,
        "label": tc.get("label", user_tier),
        "features": tc.get("features", []),
        "boards": tc.get("boards", []),
        "disabled_agents": tc.get("agent_disabled", []),
        "agent_models": tc.get("agent_models", {}),
        "default_model": tc.get("default_model", "mid"),
    }
