"""Router for mapping MCP tool calls to Python SDK methods.

Maps (service, resource, action) tuples to the correct SDK client method
using getattr chain resolution and endpoints.jsonl catalog data.
"""

from __future__ import annotations

import json
from importlib.resources import files
from pathlib import Path
from typing import Any

from augur_api import AugurAPI
from augur_api.core.errors import AugurError

# Service name mapping: kebab-case (jsonl filename) -> snake_case (SDK property)
SERVICE_MAP: dict[str, str] = {
    "agr-info": "agr_info",
    "agr-site": "agr_site",
    "agr-work": "agr_work",
    "avalara": "avalara",
    "basecamp2": "basecamp2",
    "brand-folder": "brand_folder",
    "commerce": "commerce",
    "customers": "customers",
    "gregorovich": "gregorovich",
    "items": "items",
    "joomla": "joomla",
    "legacy": "legacy",
    "logistics": "logistics",
    "nexus": "nexus",
    "open-search": "open_search",
    "orders": "orders",
    "p21-apis": "p21_apis",
    "p21-core": "p21_core",
    "p21-pim": "p21_pim",
    "p21-sism": "p21_sism",
    "payments": "payments",
    "pricing": "pricing",
    "shipping": "shipping",
    "slack": "slack",
    "smarty-streets": "smarty_streets",
    "ups": "ups",
    "vmi": "vmi",
}


def to_snake(name: str) -> str:
    """Convert kebab-case to snake_case."""
    return name.replace("-", "_")


def singularize(name: str) -> str:
    """Simple singularization for SDK method names.

    SDK convention: list uses plural (list_conversations),
    get/create/update/delete use singular (get_conversation).
    """
    if name.endswith("ses"):
        # addresses -> address (drop trailing 's' only)
        return name[:-2]
    if name.endswith("ies"):
        return name[:-3] + "y"
    if name.endswith("s"):
        return name[:-1]
    return name


def load_catalog(endpoints_dir: Path | None = None) -> dict[str, list[dict[str, Any]]]:
    """Load endpoints.jsonl files into a service catalog.

    Args:
        endpoints_dir: Directory containing .jsonl files.
            Defaults to bundled package data.

    Returns:
        Dict mapping service names to lists of endpoint definitions.
    """
    if endpoints_dir is None:
        endpoints_dir = Path(str(files("augur_mcp") / "endpoints"))

    catalog: dict[str, list[dict[str, Any]]] = {}
    if not endpoints_dir.is_dir():
        return catalog

    for jsonl_file in sorted(endpoints_dir.glob("*.jsonl")):
        service_name = jsonl_file.stem  # e.g., "agr-site"
        endpoints: list[dict[str, Any]] = []
        with open(jsonl_file) as f:
            for line in f:
                line = line.strip()
                if line:
                    endpoints.append(json.loads(line))
        catalog[service_name] = endpoints

    return catalog


def parse_resource_path(resource: str) -> tuple[str, list[int], str | None]:
    """Parse a resource path into components.

    Handles simple and nested resource paths:
    - "settings" -> ("settings", [], None)
    - "training/1/conversations" -> ("training", [1], "conversations")
    - "training/1/conversations/5/messages" -> ("training", [1, 5], "messages")

    Args:
        resource: Resource path string.

    Returns:
        Tuple of (root_resource, parent_ids, child_resource).
    """
    parts = resource.split("/")

    # Simple resource: "settings"
    if len(parts) == 1:
        return (to_snake(parts[0]), [], None)

    # Nested: "training/1/conversations" or deeper
    root = to_snake(parts[0])
    parent_ids: list[int] = []
    child: str | None = None

    for i in range(1, len(parts)):
        try:
            parent_ids.append(int(parts[i]))
        except ValueError:
            child = to_snake(parts[i])

    return (root, parent_ids, child)


def build_method_name(action: str, child: str | None) -> str:
    """Build the SDK method name from action and child resource.

    Args:
        action: One of "list", "get", "create", "update", "delete".
        child: Child resource name (plural), or None for simple resources.

    Returns:
        SDK method name (e.g., "list", "list_conversations", "get_conversation").
    """
    if child is None:
        return action

    if action == "list":
        return f"list_{child}"
    return f"{action}_{singularize(child)}"


def build_args(
    action: str,
    parent_ids: list[int],
    child: str | None,
    record_id: str | None = None,
    data: dict[str, Any] | None = None,
    params: dict[str, Any] | None = None,
) -> list[Any]:
    """Build positional arguments for the SDK method call.

    Args:
        action: One of "list", "get", "create", "update", "delete".
        parent_ids: Parent resource IDs from the path.
        child: Child resource name, or None.
        record_id: The target record ID (for get/update/delete).
        data: Request body data (for create/update).
        params: Query parameters (for list).

    Returns:
        List of positional arguments for the SDK method.
    """
    args: list[Any] = []

    # Parent IDs always come first (for nested resources)
    args.extend(parent_ids)

    if action == "list":
        if params:
            args.append(params)
    elif action == "get":
        if record_id is not None:
            args.append(_parse_id(record_id))
    elif action == "create":
        if data is not None:
            args.append(data)
    elif action == "update":
        if record_id is not None:
            args.append(_parse_id(record_id))
        if data is not None:
            args.append(data)
    elif action == "delete":
        if record_id is not None:
            args.append(_parse_id(record_id))

    return args


def _parse_id(record_id: str) -> int | str:
    """Parse a record ID, converting to int if numeric."""
    try:
        return int(record_id)
    except ValueError:
        return record_id


def resolve_and_call(
    api: AugurAPI,
    service: str,
    resource: str,
    action: str,
    record_id: str | None = None,
    data: dict[str, Any] | None = None,
    params: dict[str, Any] | None = None,
) -> dict[str, Any]:
    """Resolve an SDK method and call it.

    Args:
        api: Initialized AugurAPI client.
        service: Service name (kebab-case, e.g., "agr-site").
        resource: Resource path (e.g., "settings", "training/1/conversations").
        action: One of "list", "get", "create", "update", "delete".
        record_id: Target record ID (for get/update/delete).
        data: Request body (for create/update).
        params: Query parameters (for list).

    Returns:
        Response as a dictionary.

    Raises:
        ValueError: If service or resource is not found.
        AugurError: If the API call fails.
    """
    # Resolve service
    sdk_service_name = SERVICE_MAP.get(service)
    if sdk_service_name is None:
        msg = f"Unknown service: {service}. Available: {', '.join(sorted(SERVICE_MAP.keys()))}"
        raise ValueError(msg)

    svc = getattr(api, sdk_service_name)

    # Parse resource path
    root, parent_ids, child = parse_resource_path(resource)

    # Resolve resource object
    try:
        resource_obj = getattr(svc, root)
    except AttributeError:
        msg = f"Unknown resource '{root}' on service '{service}'"
        raise ValueError(msg) from None

    # Build method name and resolve
    method_name = build_method_name(action, child)
    try:
        method = getattr(resource_obj, method_name)
    except AttributeError:
        msg = f"No method '{method_name}' on resource '{root}' (service: {service})"
        raise ValueError(msg) from None

    # Build args and call
    args = build_args(action, parent_ids, child, record_id, data, params)
    response = method(*args)

    # Convert BaseResponse to dict
    if hasattr(response, "model_dump"):
        return response.model_dump()  # type: ignore[no-any-return]
    return dict(response)  # type: ignore[call-overload]


def get_resource_map(endpoints: list[dict[str, Any]]) -> dict[str, list[str]]:
    """Build a resource-to-methods map from endpoint definitions.

    Args:
        endpoints: List of endpoint dicts from endpoints.jsonl.

    Returns:
        Dict mapping resource paths to available HTTP methods.
    """
    resource_map: dict[str, list[str]] = {}

    for ep in endpoints:
        path: str = ep["path"]
        method: str = ep["method"]

        # Strip /api/ prefix and param placeholders for grouping
        clean = path.removeprefix("/api/")
        # Remove path parameter segments for resource grouping
        parts = clean.split("/")
        resource_parts = [p for p in parts if not p.startswith("{")]
        resource_key = "/".join(resource_parts)

        if resource_key not in resource_map:
            resource_map[resource_key] = []
        if method not in resource_map[resource_key]:
            resource_map[resource_key].append(method)

    return resource_map
