"""MCP tools for running API security scans."""

import json

from mcp.types import TextContent, Tool

from maeris_mcp.maeris.client import MaerisClient
from maeris_mcp.maeris.types import MaerisAPI
from maeris_mcp.tools.collection_tools import _find_collection_by_name


def api_security_scan_tool() -> Tool:
    """Return the tool definition for api_security_scan."""
    return Tool(
        name="api_security_scan",
        description=(
            "Run security scans for one or more APIs by name. "
            "If a requested API does not exist yet, provide api_definitions (+ collection_name) "
            "to auto-create it before scanning."
        ),
        inputSchema={
            "type": "object",
            "properties": {
                "api_names": {
                    "type": "array",
                    "description": "API names to scan.",
                    "items": {"type": "string"},
                },
                "collection_name": {
                    "type": "string",
                    "description": "Collection to create missing APIs in when api_definitions are supplied.",
                },
                "api_definitions": {
                    "type": "array",
                    "description": (
                        "Optional API definitions used for auto-save fallback. "
                        "Each item requires name, method, and url."
                    ),
                    "items": {
                        "type": "object",
                        "properties": {
                            "name": {"type": "string"},
                            "method": {"type": "string"},
                            "url": {"type": "string"},
                            "payload": {},
                            "headers": {"type": "array", "items": {"type": "object"}},
                            "params": {"type": "array", "items": {"type": "object"}},
                        },
                        "required": ["name", "method", "url"],
                    },
                },
            },
            "required": ["api_names"],
        },
    )


def _api_id(item: dict) -> str:
    for key in ["api_id", "id", "_id"]:
        value = item.get(key)
        if value:
            return str(value)
    return ""


def _normalize_candidates(items: list[dict]) -> list[dict]:
    return [
        {
            "id": _api_id(item),
            "name": item.get("name"),
            "url": item.get("url"),
            "collectionId": item.get("collection_id") or item.get("collectionId"),
            "collectionName": item.get("collection_name") or item.get("collectionName"),
        }
        for item in items
    ]


def _normalize_name_list(api_names: object) -> list[str]:
    if not isinstance(api_names, list):
        return []

    seen: set[str] = set()
    normalized: list[str] = []
    for name in api_names:
        if not isinstance(name, str):
            continue
        trimmed = name.strip()
        if not trimmed:
            continue
        key = trimmed.lower()
        if key in seen:
            continue
        seen.add(key)
        normalized.append(trimmed)
    return normalized


def _index_definitions(api_definitions: object) -> tuple[dict[str, dict], str | None]:
    if api_definitions is None:
        return {}, None
    if not isinstance(api_definitions, list):
        return {}, "Error: api_definitions must be an array"

    by_name: dict[str, dict] = {}
    for idx, definition in enumerate(api_definitions):
        if not isinstance(definition, dict):
            return {}, f"Error: api_definitions[{idx}] must be an object"
        name = definition.get("name")
        method = definition.get("method")
        url = definition.get("url")
        if not all([name, method, url]):
            return {}, (
                f"Error: api_definitions[{idx}] must include non-empty name, method, and url"
            )
        key = str(name).strip().lower()
        if key:
            by_name[key] = definition
    return by_name, None


async def _resolve_collection_id_by_name(client: MaerisClient, collection_name: str) -> tuple[str | None, str | None]:
    collection = await _find_collection_by_name(client, collection_name)
    if not collection:
        return None, f"Error: no collection found with name '{collection_name}'"
    return str(collection["api_collection_id"]), None


async def _resolve_existing_apis(
    client: MaerisClient,
    api_names: list[str],
) -> tuple[list[dict], list[str], str | None]:
    all_apis = await client.get_all_apis()
    resolved: list[dict] = []
    unresolved: list[str] = []

    for api_name in api_names:
        matches = [api for api in all_apis if (api.get("name") or "").lower() == api_name.lower()]
        if not matches:
            unresolved.append(api_name)
            continue
        if len(matches) > 1:
            candidates = _normalize_candidates(matches)
            return [], [], (
                f"Error: multiple APIs found with name '{api_name}'. "
                f"Matches: {json.dumps(candidates)}"
            )
        api_id = _api_id(matches[0])
        if not api_id:
            return [], [], f"Error: resolved API '{api_name}' has no id"
        resolved.append({"apiName": api_name, "apiId": api_id, "source": "existing"})

    return resolved, unresolved, None


async def _create_missing_apis_from_definitions(
    client: MaerisClient,
    unresolved_names: list[str],
    definitions_by_name: dict[str, dict],
    collection_id: str,
) -> tuple[list[dict], list[str], str | None]:
    created: list[dict] = []
    still_missing: list[str] = []

    for api_name in unresolved_names:
        definition = definitions_by_name.get(api_name.lower())
        if not definition:
            still_missing.append(api_name)
            continue
        name = str(definition["name"]).strip()
        method = str(definition["method"]).strip()
        url = str(definition["url"]).strip()
        if not all([name, method, url]):
            return [], [], (
                f"Error: api definition for '{api_name}' must include non-empty name, method, and url"
            )

        api = MaerisAPI(
            name=name,
            type=method.lower(),
            url=url,
            payload=definition.get("payload", ""),
            headers=definition.get("headers") or [],
            params=definition.get("params") or [],
            parent_collection_id=collection_id,
        )
        api_id = await client.create_api(api)
        if not api_id:
            return [], [], f"Error: failed to create missing API '{api_name}'"
        created.append({"apiName": api_name, "apiId": api_id, "source": "created"})

    return created, still_missing, None


async def handle_api_security_scan(arguments: dict) -> list[TextContent]:
    api_names = _normalize_name_list(arguments.get("api_names"))
    if not api_names:
        return [
            TextContent(
                type="text",
                text="Error: api_names is required and must not be empty",
            )
        ]

    definitions_by_name, definitions_error = _index_definitions(arguments.get("api_definitions"))
    if definitions_error:
        return [TextContent(type="text", text=definitions_error)]

    collection_name = arguments.get("collection_name")

    async with MaerisClient() as client:
        resolved_existing, unresolved_names, resolve_error = await _resolve_existing_apis(
            client,
            api_names,
        )
        if resolve_error:
            return [TextContent(type="text", text=resolve_error)]

        resolved_created: list[dict] = []
        if unresolved_names:
            if not definitions_by_name:
                return [
                    TextContent(
                        type="text",
                        text=(
                            "Error: unresolved APIs found with no matching definitions provided. "
                            f"Missing: {json.dumps(unresolved_names)}"
                        ),
                    )
                ]
            if not collection_name:
                return [
                    TextContent(
                        type="text",
                        text="Error: collection_name is required when auto-saving missing APIs",
                    )
                ]

            collection_id, collection_error = await _resolve_collection_id_by_name(
                client,
                str(collection_name),
            )
            if collection_error:
                return [TextContent(type="text", text=collection_error)]

            resolved_created, still_missing, create_error = await _create_missing_apis_from_definitions(
                client,
                unresolved_names,
                definitions_by_name,
                str(collection_id),
            )
            if create_error:
                return [TextContent(type="text", text=create_error)]
            if still_missing:
                return [
                    TextContent(
                        type="text",
                        text=(
                            "Error: unresolved APIs found with no matching definitions provided. "
                            f"Missing: {json.dumps(still_missing)}"
                        ),
                    )
                ]

        resolved_all = [*resolved_existing, *resolved_created]
        unique_ids: list[str] = []
        seen_ids: set[str] = set()
        for item in resolved_all:
            api_id = item["apiId"]
            if api_id in seen_ids:
                continue
            seen_ids.add(api_id)
            unique_ids.append(api_id)

        scan_response = await client.run_api_security_scan(unique_ids)

    result = {
        "apiIds": unique_ids,
        "resolved": resolved_all,
        "scanResponse": scan_response,
        "errors": [],
    }
    return [TextContent(type="text", text=json.dumps(result, indent=2))]
