"""Shared instance configuration for all MCP eRegistrations components.

Provides:
- Built-in instance definitions from instances.yaml
- Per-component profile management (~/.config/mcp-eregistrations-{component}/)
- Instance name resolution (profiles override built-in)
- Auth key resolution (profile name lookup for authentication)
"""

from __future__ import annotations

import json
from pathlib import Path
from typing import Any

import yaml


class ConfigurationError(Exception):
    """Raised when instance configuration is invalid or missing."""

    pass


# Patchable base dir for tests (monkeypatch this to tmp_path)
CONFIG_BASE_DIR = Path.home() / ".config"


def load_builtin_instances() -> list[dict[str, Any]]:
    """Load built-in instance definitions from common instances.yaml.

    Returns:
        List of instance dicts with name, display_name, URLs, auth config.
    """
    yaml_path = Path(__file__).parent / "instances.yaml"
    with open(yaml_path) as f:
        data = yaml.safe_load(f)
    result: list[dict[str, Any]] = data.get("instances", [])
    return result


def get_builtin_instance(name: str) -> dict[str, Any] | None:
    """Find a built-in instance by name.

    Args:
        name: Instance name (e.g., "jamaica").

    Returns:
        Instance config dict, or None if not found.
    """
    for inst in load_builtin_instances():
        if inst["name"] == name:
            return inst
    return None


def _profiles_path(component: str) -> Path:
    """Get profiles.json path for a component.

    Args:
        component: Component name ("bpa", "ds", "gdb").

    Returns:
        Path like ~/.config/mcp-eregistrations-ds/profiles.json
    """
    return CONFIG_BASE_DIR / f"mcp-eregistrations-{component}" / "profiles.json"


def load_profiles(component: str) -> dict[str, dict[str, Any]]:
    """Load saved instance profiles for a component.

    Args:
        component: Component name ("bpa", "ds", "gdb").

    Returns:
        Dict of profile_name -> config dict.
    """
    path = _profiles_path(component)
    if not path.exists():
        return {}
    try:
        result: dict[str, dict[str, Any]] = json.loads(path.read_text())
        return result
    except (json.JSONDecodeError, OSError):
        return {}


def save_profiles(component: str, profiles: dict[str, dict[str, Any]]) -> None:
    """Save instance profiles for a component.

    Args:
        component: Component name ("bpa", "ds", "gdb").
        profiles: Dict of profile_name -> config dict.
    """
    path = _profiles_path(component)
    path.parent.mkdir(parents=True, exist_ok=True)
    tmp = Path(str(path) + ".tmp")
    tmp.write_text(json.dumps(profiles, indent=2))
    tmp.replace(path)


def get_instance_config(
    instance: str | None,
    *,
    component: str = "ds",
) -> dict[str, Any]:
    """Resolve instance name to config dict.

    Lookup order:
    1. Custom profiles for the component
    2. Built-in instances.yaml

    Args:
        instance: Instance name (e.g., "jamaica"). None raises ToolError.
        component: Component name for profile lookup (default: "ds").

    Returns:
        Config dict with URLs, auth config, etc.

    Raises:
        ToolError: If instance is None or not found.
    """
    from fastmcp.exceptions import ToolError

    if instance is None:
        raise ToolError(
            "No instance specified. Pass instance='<name>'. "
            "Use ds_instance_list to see available instances."
        )

    # Check custom profiles first
    profiles = load_profiles(component)
    if instance in profiles:
        return profiles[instance]

    # Fall back to built-in instances
    builtin = get_builtin_instance(instance)
    if builtin is not None:
        return builtin

    available = [i["name"] for i in load_builtin_instances()]
    raise ToolError(
        f"Unknown instance '{instance}'. "
        f"Available: {', '.join(available)}. "
        f"Or run ds_instance_add to register a custom instance."
    )


def resolve_auth_key(instance: str | None) -> str:
    """Resolve the universal auth key (profile name) for an instance.

    1. Explicit name -> return as-is
    2. None + exactly one profile -> auto-select
    3. None + multiple -> raise with list

    Args:
        instance: Instance name, or None for auto-select.

    Returns:
        Profile name to use as auth key.

    Raises:
        ConfigurationError: If auto-select fails (multiple or no instances).
    """
    if instance:
        return instance

    # Auto-select from BPA profiles (the canonical profile store)
    profiles = load_profiles("bpa")
    names = list(profiles.keys())
    if len(names) == 1:
        return names[0]
    if names:
        raise ConfigurationError(
            f"Multiple instances configured: {', '.join(names)}. "
            "Specify instance= parameter."
        )

    # No profiles — check built-in instances
    instances = load_builtin_instances()
    if len(instances) == 1:
        name: str = instances[0]["name"]
        return name

    raise ConfigurationError("No instance configured. Use instance_add first.")
