"""FastMCP server exposing Augur API as 7 generic tools.

Tools:
    augur_sites    - List configured sites
    augur_discover - List services or endpoints for a service
    augur_list     - List records (GET collection)
    augur_get      - Get single record by ID
    augur_create   - Create record (POST)
    augur_update   - Update record (PUT)
    augur_delete   - Delete record (DELETE)
"""

from __future__ import annotations

import json
import os
from pathlib import Path
from typing import Any

from augur_api import AugurAPI
from augur_api.core.errors import (
    AugurError,
    AuthenticationError,
    NotFoundError,
    RateLimitError,
    ValidationError,
)
from mcp.server.fastmcp import FastMCP

from augur_mcp.router import (
    SERVICE_MAP,
    get_resource_map,
    load_catalog,
    resolve_and_call,
)

mcp = FastMCP("augur")

# Module-level state
_catalog: dict[str, list[dict[str, Any]]] = {}
_clients: dict[str, AugurAPI] = {}
_initialized: bool = False

DEFAULT_CREDS_FILE = Path(os.path.expanduser("~/.simpleapps/augur-api.json"))
CREDS_FILENAME = "augur-api.json"


def init_catalog(endpoints_dir: Path | None = None) -> None:
    """Initialize the endpoints catalog."""
    global _catalog  # noqa: PLW0603
    _catalog = load_catalog(endpoints_dir)


def _parse_site_cred(
    data: dict[str, Any], site_id: str = ""
) -> dict[str, Any] | None:
    """Parse a credential entry into a normalized dict.

    Args:
        data: Raw credential dict (must contain ``jwt``).
        site_id: Explicit site ID (used in multi-site format where the
            outer key is the site ID). Falls back to ``data["siteId"]``.

    Returns:
        Dict with site_id and jwt, or None if unusable.
    """
    sid = site_id or data.get("siteId", "")
    jwt = data.get("jwt", "")
    if sid and jwt:
        return {"site_id": sid, "jwt": jwt}
    return None


def _read_creds_json(path: Path) -> list[dict[str, Any]]:
    """Read credentials from a JSON file.

    Detects single-site vs multi-site format:
    - Single-site: ``{"siteId": "...", "jwt": "..."}`` (has top-level siteId)
    - Multi-site: ``{"site-id": {"jwt": "..."}, ...}`` (key = siteId)

    Returns:
        List of parsed credential dicts (may be empty).
    """
    if not path.is_file():
        return []
    with open(path) as f:
        data = json.load(f)
    if not isinstance(data, dict):
        return []
    # Single-site: has top-level siteId key
    if "siteId" in data:
        parsed = _parse_site_cred(data)
        return [parsed] if parsed else []
    # Multi-site: outer key is the siteId
    results: list[dict[str, Any]] = []
    for key, entry in data.items():
        if isinstance(entry, dict):
            parsed = _parse_site_cred(entry, site_id=key)
            if parsed:
                results.append(parsed)
    return results


def _load_all_creds(creds_path: Path | None = None) -> list[dict[str, Any]]:
    """Load and deduplicate credentials from all sources.

    Resolution order (first match for each site_id wins):
    1. Explicit creds_path argument
    2. AUGUR_CREDS_FILE env var
    3. <cwd>/.simpleapps/augur-api.json (project-level)
    4. ~/.simpleapps/augur-api.json (global)

    Steps 3+4 are merged; project takes precedence via first-seen dedup.
    """
    seen: set[str] = set()
    results: list[dict[str, Any]] = []

    def _add(creds: list[dict[str, Any]]) -> None:
        for c in creds:
            if c["site_id"] not in seen:
                seen.add(c["site_id"])
                results.append(c)

    if creds_path:
        _add(_read_creds_json(creds_path))
        return results

    env_path = os.environ.get("AUGUR_CREDS_FILE", "")
    if env_path:
        _add(_read_creds_json(Path(os.path.expanduser(env_path))))
        return results

    # Project-level, then global — project wins via first-seen dedup
    project_creds = Path.cwd() / ".simpleapps" / CREDS_FILENAME
    _add(_read_creds_json(project_creds))
    _add(_read_creds_json(DEFAULT_CREDS_FILE))
    return results


def _init_clients(creds_path: Path | None = None) -> None:
    """Initialize all AugurAPI clients from available credentials.

    Loads creds from files, applies env var overrides, builds clients.
    """
    global _initialized  # noqa: PLW0603
    _initialized = True

    all_creds = _load_all_creds(creds_path)

    env_token = os.environ.get("AUGUR_TOKEN", "")
    env_site_id = os.environ.get("AUGUR_SITE_ID", "")

    if env_token and env_site_id:
        env_cred: dict[str, Any] = {"site_id": env_site_id, "jwt": env_token}
        all_creds = [env_cred] + [
            c for c in all_creds if c["site_id"] != env_site_id
        ]
    elif env_token and all_creds:
        all_creds[0] = {**all_creds[0], "jwt": env_token}
    elif env_site_id and all_creds:
        all_creds[0] = {**all_creds[0], "site_id": env_site_id}

    for cred in all_creds:
        _clients[cred["site_id"]] = AugurAPI(
            token=cred["jwt"], site_id=cred["site_id"]
        )


def get_api(site: str = "") -> AugurAPI:
    """Get an AugurAPI client for a site.

    If site is empty and only one client exists, returns it.
    If multiple clients exist, site is required.

    Lazy-calls ``_init_clients()`` on first access.
    """
    if not _initialized:
        _init_clients()

    if not _clients:
        msg = (
            "No Augur credentials found. Provide credentials via: "
            "(1) AUGUR_TOKEN + AUGUR_SITE_ID env vars, "
            "(2) AUGUR_CREDS_FILE env var, "
            "(3) .simpleapps/augur-api.json in cwd, or "
            f"(4) {DEFAULT_CREDS_FILE}. "
            'File format: {"siteId": "...", "jwt": "..."}.'
        )
        raise ValueError(msg)

    if not site:
        if len(_clients) == 1:
            return next(iter(_clients.values()))
        available = ", ".join(sorted(_clients.keys()))
        msg = (
            f"Multiple sites configured ({available}). "
            "Specify site parameter."
        )
        raise ValueError(msg)

    if site not in _clients:
        available = ", ".join(sorted(_clients.keys()))
        msg = f"Unknown site: {site}. Available: {available}"
        raise ValueError(msg)

    return _clients[site]


def set_api(api: AugurAPI, site: str = "") -> None:
    """Set an AugurAPI client (for testing).

    Uses site if provided, otherwise derives key from api.config.site_id.
    """
    global _initialized  # noqa: PLW0603
    _initialized = True
    key = (
        site
        or getattr(getattr(api, "config", None), "site_id", "")
        or "default"
    )
    _clients[key] = api


def reset_state() -> None:
    """Reset module state (for testing)."""
    global _clients, _catalog, _initialized  # noqa: PLW0603
    _clients = {}
    _catalog = {}
    _initialized = False


def _format_error(error: Exception) -> str:
    """Format an error as a JSON string for MCP response."""
    if isinstance(error, AuthenticationError):
        return json.dumps({"error": "Authentication failed", "detail": str(error)})
    if isinstance(error, NotFoundError):
        return json.dumps({"error": "Not found", "detail": str(error)})
    if isinstance(error, RateLimitError):
        return json.dumps({"error": "Rate limited", "detail": str(error)})
    if isinstance(error, ValidationError):
        return json.dumps({"error": "Validation error", "detail": str(error)})
    if isinstance(error, AugurError):
        return json.dumps({"error": "API error", "detail": str(error)})
    if isinstance(error, ValueError):
        return json.dumps({"error": "Invalid request", "detail": str(error)})
    return json.dumps({"error": "Unexpected error", "detail": str(error)})


@mcp.tool()
def augur_sites() -> str:
    """List configured Augur sites.

    Returns:
        JSON with list of sites, each having site_id and default (bool).
    """
    if not _initialized:
        try:
            _init_clients()
        except Exception:  # noqa: BLE001
            pass

    sites = []
    default_site = ""
    if len(_clients) == 1:
        default_site = next(iter(_clients.keys()))

    for sid in sorted(_clients.keys()):
        sites.append({
            "site_id": sid,
            "default": sid == default_site,
        })

    return json.dumps({"sites": sites}, indent=2)


@mcp.tool()
def augur_discover(service: str = "") -> str:
    """List available Augur API services, or list resources/endpoints for a specific service.

    Args:
        service: Service name (e.g., "items", "agr-site"). Omit to list all services.

    Returns:
        JSON with service catalog or resource map.
    """
    if not service:
        services = []
        for svc_name in sorted(_catalog.keys()):
            resource_map = get_resource_map(_catalog[svc_name])
            services.append({
                "name": svc_name,
                "endpoints": len(_catalog[svc_name]),
                "resources": len(resource_map),
            })
        return json.dumps({"services": services}, indent=2)

    if service not in _catalog:
        available = ", ".join(sorted(_catalog.keys()))
        return json.dumps(
            {"error": f"Unknown service: {service}", "available": available}
        )

    resource_map = get_resource_map(_catalog[service])
    endpoints = []
    for resource_path, methods in sorted(resource_map.items()):
        endpoints.append({"resource": resource_path, "methods": sorted(methods)})

    return json.dumps({"service": service, "endpoints": endpoints}, indent=2)


@mcp.tool()
def augur_list(
    service: str, resource: str, params: str = "", site: str = ""
) -> str:
    """List records from a service resource.

    Args:
        service: Service name (e.g., "agr-site", "items").
        resource: Resource path (e.g., "settings", "training/1/conversations").
        params: JSON string of query parameters (e.g., '{"limit": 10}').
        site: Site ID to query. Omit if only one site is configured.

    Returns:
        JSON response with count, data, total.
    """
    try:
        api = get_api(site)
        parsed_params = json.loads(params) if params else None
        result = resolve_and_call(
            api, service, resource, "list", params=parsed_params
        )
        return json.dumps(result, indent=2, default=str)
    except Exception as e:
        return _format_error(e)


@mcp.tool()
def augur_get(
    service: str, resource: str, record_id: str, site: str = ""
) -> str:
    """Get a single record by ID.

    Args:
        service: Service name (e.g., "agr-site", "items").
        resource: Resource path (e.g., "settings", "training/1/conversations").
        record_id: The record ID to retrieve.
        site: Site ID to query. Omit if only one site is configured.

    Returns:
        JSON response with the record data.
    """
    try:
        api = get_api(site)
        result = resolve_and_call(
            api, service, resource, "get", record_id=record_id
        )
        return json.dumps(result, indent=2, default=str)
    except Exception as e:
        return _format_error(e)


@mcp.tool()
def augur_create(
    service: str, resource: str, data: str, site: str = ""
) -> str:
    """Create a new record.

    Args:
        service: Service name (e.g., "agr-site", "items").
        resource: Resource path (e.g., "settings", "training/1/conversations").
        data: JSON string of the record data to create.
        site: Site ID to query. Omit if only one site is configured.

    Returns:
        JSON response with the created record.
    """
    try:
        api = get_api(site)
        parsed_data = json.loads(data) if data else {}
        result = resolve_and_call(
            api, service, resource, "create", data=parsed_data
        )
        return json.dumps(result, indent=2, default=str)
    except Exception as e:
        return _format_error(e)


@mcp.tool()
def augur_update(
    service: str,
    resource: str,
    record_id: str,
    data: str,
    site: str = "",
) -> str:
    """Update an existing record.

    Args:
        service: Service name (e.g., "agr-site", "items").
        resource: Resource path (e.g., "settings", "training/1/conversations").
        record_id: The record ID to update.
        data: JSON string of the fields to update.
        site: Site ID to query. Omit if only one site is configured.

    Returns:
        JSON response with the updated record.
    """
    try:
        api = get_api(site)
        parsed_data = json.loads(data) if data else {}
        result = resolve_and_call(
            api, service, resource, "update", record_id=record_id, data=parsed_data
        )
        return json.dumps(result, indent=2, default=str)
    except Exception as e:
        return _format_error(e)


@mcp.tool()
def augur_delete(
    service: str, resource: str, record_id: str, site: str = ""
) -> str:
    """Delete a record.

    Args:
        service: Service name (e.g., "agr-site", "items").
        resource: Resource path (e.g., "settings", "training/1/conversations").
        record_id: The record ID to delete.
        site: Site ID to query. Omit if only one site is configured.

    Returns:
        JSON response confirming deletion.
    """
    try:
        api = get_api(site)
        result = resolve_and_call(
            api, service, resource, "delete", record_id=record_id
        )
        return json.dumps(result, indent=2, default=str)
    except Exception as e:
        return _format_error(e)


def main() -> None:
    """Entry point for the MCP server."""
    init_catalog()
    mcp.run()


if __name__ == "__main__":
    main()
