"""MCP tool policy loading and authorization helpers."""
from __future__ import annotations

import json
import os
from dataclasses import dataclass
from functools import lru_cache
from importlib import resources
from pathlib import Path
from typing import Any


_AUTH_MODES = {"api_key", "internal_only", "either"}


@dataclass(frozen=True)
class ToolPolicy:
    """One MCP tool authorization policy."""

    enabled: bool
    auth_mode: str
    required_scopes: list[str]


def _default_policy_path() -> Path:
    try:
        return Path(resources.files("security.auth").joinpath("mcp_tool_policy.json"))
    except Exception as exc:  # pragma: no cover
        raise RuntimeError("Unable to resolve default MCP tool policy path.") from exc


def _policy_path() -> Path:
    override = (os.getenv("AIF_MCP_TOOL_POLICY_PATH") or "").strip()
    if override:
        return Path(override)
    return _default_policy_path()


def _normalize_tool_name(value: Any) -> str:
    return str(value or "").strip()


def _normalize_scope_list(value: Any) -> list[str]:
    result: list[str] = []
    for item in value or []:
        text = str(item or "").strip()
        if text and text not in result:
            result.append(text)
    return result


def _parse_one(tool_name: str, payload: dict[str, Any]) -> ToolPolicy:
    if not isinstance(payload, dict):
        raise RuntimeError(f"Invalid policy for tool '{tool_name}': object expected.")
    enabled = bool(payload.get("enabled", True))
    auth_mode = str(payload.get("auth_mode", "api_key")).strip()
    if auth_mode not in _AUTH_MODES:
        raise RuntimeError(
            f"Invalid auth_mode for tool '{tool_name}': '{auth_mode}'. "
            f"Allowed: {sorted(_AUTH_MODES)}."
        )
    required_scopes = _normalize_scope_list(payload.get("required_scopes"))
    return ToolPolicy(
        enabled=enabled,
        auth_mode=auth_mode,
        required_scopes=required_scopes,
    )


@lru_cache(maxsize=1)
def load_tool_policies() -> dict[str, ToolPolicy]:
    """Load and validate MCP tool policies from JSON config."""
    path = _policy_path()
    if not path.exists():
        raise RuntimeError(f"MCP tool policy file not found: {path}")
    try:
        raw = json.loads(path.read_text(encoding="utf-8"))
    except Exception as exc:
        raise RuntimeError(f"Failed to parse MCP tool policy file: {path}") from exc
    if not isinstance(raw, dict):
        raise RuntimeError("MCP tool policy file must be a JSON object.")

    policies: dict[str, ToolPolicy] = {}
    for key, value in raw.items():
        tool_name = _normalize_tool_name(key)
        if not tool_name:
            continue
        policies[tool_name] = _parse_one(tool_name, value)
    if not policies:
        raise RuntimeError("MCP tool policy file contains no valid tool entries.")
    return policies


def reload_tool_policies() -> dict[str, ToolPolicy]:
    """Clear cache and reload policy file."""
    load_tool_policies.cache_clear()
    return load_tool_policies()


def get_tool_policy(tool_name: str) -> ToolPolicy | None:
    """Return policy for one tool, or None if not configured."""
    return load_tool_policies().get((tool_name or "").strip())

