"""
CLI entitlement configuration loader.

This defines plan tiers, capabilities, and meter limits for g-gremlin.
Command-to-capability mapping remains in g_gremlin.command_metadata.
"""

from __future__ import annotations

import logging
from pathlib import Path
from typing import Any, Dict, Literal

import yaml
from pydantic import BaseModel, Field

logger = logging.getLogger(__name__)


class CapabilityDef(BaseModel):
    label: str
    description: str = ""
    tiers: Dict[str, bool]


class LimitDef(BaseModel):
    label: str
    period: Literal["daily", "monthly", "static"]
    meter_type: Literal["per_attempt", "per_volume"]
    tiers: Dict[str, int | None]


class CLIEntitlementsConfig(BaseModel):
    schema_version: int = Field(ge=1)
    tier_order: list[str]
    tier_labels: Dict[str, str]
    capabilities: Dict[str, CapabilityDef]
    limits: Dict[str, LimitDef]

    def get_tier_defaults(self, tier: str) -> tuple[Dict[str, bool], Dict[str, int | None]]:
        if tier not in self.tier_order:
            raise ValueError(f"Unknown tier '{tier}'. Available tiers: {', '.join(self.tier_order)}")
        entitlements = {
            capability_name: bool(capability_def.tiers.get(tier, False))
            for capability_name, capability_def in self.capabilities.items()
        }
        limits = {
            limit_name: limit_def.tiers.get(tier)
            for limit_name, limit_def in self.limits.items()
        }
        return entitlements, limits

    def plans_for_capability(self, capability_name: str) -> list[str]:
        capability = self.capabilities.get(capability_name)
        if not capability:
            return []
        return [tier for tier in self.tier_order if capability.tiers.get(tier, False)]

    def to_api_response(self, command_safety: Dict[str, Dict[str, Any]]) -> Dict[str, Any]:
        command_rows: list[Dict[str, Any]] = []
        for command_id in sorted(command_safety.keys()):
            safety = command_safety.get(command_id) or {}
            required_capability = safety.get("required_capability")
            meter_keys = [str(m) for m in (safety.get("meters") or [])]
            command_rows.append(
                {
                    "command_id": command_id,
                    "impact": safety.get("impact", "?"),
                    "required_capability": required_capability,
                    "meters": meter_keys,
                    "fallback_mode": safety.get("fallback_mode", "none"),
                    "tier_access": {
                        tier: (
                            True
                            if not required_capability
                            else bool(
                                (self.capabilities.get(str(required_capability)) or CapabilityDef(label="", tiers={})).tiers.get(
                                    tier,
                                    False,
                                )
                            )
                        )
                        for tier in self.tier_order
                    },
                }
            )

        return {
            "schema_version": self.schema_version,
            "tier_order": self.tier_order,
            "tier_labels": self.tier_labels,
            "capabilities": {
                capability_name: {
                    "label": capability_def.label,
                    "description": capability_def.description,
                    "tiers": capability_def.tiers,
                }
                for capability_name, capability_def in self.capabilities.items()
            },
            "limits": {
                limit_name: {
                    "label": limit_def.label,
                    "period": limit_def.period,
                    "meter_type": limit_def.meter_type,
                    "tiers": limit_def.tiers,
                }
                for limit_name, limit_def in self.limits.items()
            },
            "commands": command_rows,
        }


def _default_config_path() -> Path:
    from g_gremlin.config import get_config_dir
    return get_config_dir() / "cli_entitlements.yaml"


def load_cli_entitlements_config(config_path: Path | None = None) -> CLIEntitlementsConfig:
    path = config_path or _default_config_path()
    if not path.exists():
        raise FileNotFoundError(f"CLI entitlements config not found: {path}")

    logger.info("Loading CLI entitlements config from %s", path)
    data = yaml.safe_load(path.read_text(encoding="utf-8")) or {}
    config = CLIEntitlementsConfig(**data)

    # Ensure every capability and limit defines all tiers.
    missing_tiers_errors: list[str] = []
    expected_tiers = set(config.tier_order)
    for capability_name, capability_def in config.capabilities.items():
        missing = expected_tiers - set(capability_def.tiers.keys())
        if missing:
            missing_tiers_errors.append(
                f"capability '{capability_name}' missing tiers: {', '.join(sorted(missing))}"
            )
    for limit_name, limit_def in config.limits.items():
        missing = expected_tiers - set(limit_def.tiers.keys())
        if missing:
            missing_tiers_errors.append(
                f"limit '{limit_name}' missing tiers: {', '.join(sorted(missing))}"
            )

    if missing_tiers_errors:
        raise ValueError("Invalid CLI entitlement config:\n" + "\n".join(missing_tiers_errors))

    return config


CLI_ENTITLEMENTS: CLIEntitlementsConfig | None
CLI_ENTITLEMENTS_LOAD_ERROR: Exception | None = None
try:
    CLI_ENTITLEMENTS = load_cli_entitlements_config()
except Exception as exc:
    logger.warning("Failed to load CLI entitlement config at import time: %s", exc)
    CLI_ENTITLEMENTS = None
    CLI_ENTITLEMENTS_LOAD_ERROR = exc


def get_cli_entitlements_config(force_reload: bool = False) -> CLIEntitlementsConfig:
    global CLI_ENTITLEMENTS
    global CLI_ENTITLEMENTS_LOAD_ERROR

    if force_reload:
        CLI_ENTITLEMENTS = None
        CLI_ENTITLEMENTS_LOAD_ERROR = None

    if CLI_ENTITLEMENTS is not None:
        return CLI_ENTITLEMENTS

    if CLI_ENTITLEMENTS_LOAD_ERROR is not None:
        raise RuntimeError("CLI entitlements config failed to load earlier.") from CLI_ENTITLEMENTS_LOAD_ERROR

    try:
        CLI_ENTITLEMENTS = load_cli_entitlements_config()
        return CLI_ENTITLEMENTS
    except Exception as exc:
        CLI_ENTITLEMENTS_LOAD_ERROR = exc
        raise
