"""MCP tool for running all APIs in a collection."""

import json
import re

from mcp.types import TextContent, Tool

from maeris_mcp.auth.token_store import TokenStore
from maeris_mcp.maeris.client import MaerisClient
from maeris_mcp.tools.collection_tools import _find_collection_by_name, _get_children


def run_collection_tool() -> Tool:
    return Tool(
        name="run_collection",
        description=(
            "Execute all saved APIs in a collection and return aggregated results. "
            "Environment may be passed by name."
        ),
        inputSchema={
            "type": "object",
            "properties": {
                "collection_name": {"type": "string", "description": "Name of the collection to execute."},
                "environment_name": {
                    "type": "string",
                    "description": "Optional environment name to resolve to ID.",
                },
            },
            "required": ["collection_name"],
        },
    )


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


def _api_name(api: dict) -> str:
    value = api.get("name")
    return str(value) if value else ""


def _api_method(api: dict) -> str:
    value = api.get("method") or api.get("type")
    return str(value).upper() if value else ""


def _api_url(api: dict) -> str:
    value = api.get("url")
    return str(value) if value else ""


def _environment_id(env: dict) -> str:
    for key in ["environment_id", "api_environment_id", "id", "env_id"]:
        value = env.get(key)
        if value:
            return str(value)
    return ""


def _environment_name(env: dict) -> str:
    for key in ["name", "environment_name"]:
        value = env.get(key)
        if isinstance(value, str) and value.strip():
            return value
    return ""


def _flow_id(flow: dict) -> str:
    for key in ["flow_id", "api_flow_id", "id", "_id"]:
        value = flow.get(key)
        if value:
            return str(value)
    return ""


def _extract_flow_map_rows(payload: dict | list[dict]) -> list[dict]:
    if isinstance(payload, list):
        return payload
    if isinstance(payload, dict):
        for key in ["flow_map", "data", "items", "results"]:
            value = payload.get(key)
            if isinstance(value, list):
                return value
    return []


def _normalize_error(value: object) -> str:
    text = str(value or "").strip()
    return text or "Unknown error"


def _error_signature(error_text: str) -> str:
    status_match = re.search(r"\bstatus\s+(\d{3})\b", error_text, re.IGNORECASE)
    if status_match:
        return f"HTTP_{status_match.group(1)}"

    http_match = re.search(r"\bHTTP[_\s]?(\d{3})\b", error_text, re.IGNORECASE)
    if http_match:
        return f"HTTP_{http_match.group(1)}"

    sentence = error_text.split("\n", 1)[0].split(".", 1)[0].strip()
    sentence = re.sub(
        r"\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b",
        "<id>",
        sentence,
        flags=re.IGNORECASE,
    )
    sentence = re.sub(r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z?", "<ts>", sentence)
    sentence = re.sub(r"\s+", " ", sentence).strip()
    return sentence[:120] if sentence else "UNKNOWN_ERROR"


async def _resolve_env(
    client: MaerisClient, environment_name: str | None
) -> tuple[str | None, str | None, str | None, list[str]]:
    warnings: list[str] = []

    if environment_name:
        environments = await client.get_environments()
        exact = [e for e in environments if _environment_name(e).lower() == environment_name.lower()]
        if len(exact) == 1:
            matched = exact[0]
            return _environment_id(matched), _environment_name(matched), None, warnings
        if len(exact) > 1:
            choices = [{"id": _environment_id(e), "name": _environment_name(e)} for e in exact]
            return None, None, f"Error: multiple environments matched '{environment_name}'. Matches: {json.dumps(choices)}", warnings

        partial = [e for e in environments if environment_name.lower() in _environment_name(e).lower()]
        if len(partial) == 1:
            matched = partial[0]
            return _environment_id(matched), _environment_name(matched), None, warnings
        if len(partial) > 1:
            choices = [{"id": _environment_id(e), "name": _environment_name(e)} for e in partial]
            return None, None, f"Error: multiple environments matched '{environment_name}'. Matches: {json.dumps(choices)}", warnings
        return None, None, f"Error: no environment matched '{environment_name}'", warnings

    store = TokenStore()
    selected_env_id = store.get_environment_id()
    selected_env_name = store.get_environment_name()
    if selected_env_id:
        return str(selected_env_id), selected_env_name, None, warnings

    warnings.append("No environment was provided or selected; running APIs without env_id.")
    return None, None, None, warnings


async def _collect_collection_apis(
    client: MaerisClient, root_collection_id: str, root_collection_name: str
) -> list[dict]:
    queue: list[tuple[str, str]] = [(root_collection_id, root_collection_name)]
    apis: list[dict] = []
    order_hint = 0

    while queue:
        col_id, path = queue.pop(0)
        children = await _get_children(client, col_id)

        for api in children.get("apis", []):
            apis.append(
                {
                    "id": _api_id(api),
                    "name": _api_name(api),
                    "method": _api_method(api),
                    "url": _api_url(api),
                    "collectionPath": path,
                    "orderHint": order_hint,
                }
            )
            order_hint += 1

        for sub in children.get("api_collections", []):
            sub_id = str(sub.get("api_collection_id") or "")
            sub_name = str(sub.get("name") or "")
            if sub_id and sub_name:
                queue.append((sub_id, f"{path} / {sub_name}"))

    apis.sort(key=lambda item: (item["orderHint"], item["name"].lower(), item["id"]))
    return apis


async def _build_flow_matches(client: MaerisClient) -> dict[str, list[str]]:
    matches: dict[str, list[str]] = {}
    flows = await client.get_api_flows()

    for flow in flows:
        candidate_flow_id = _flow_id(flow)
        if not candidate_flow_id:
            continue
        try:
            mapping = await client.get_api_flow_map(candidate_flow_id)
        except Exception:
            continue
        rows = _extract_flow_map_rows(mapping)
        for row in rows:
            child_id = str(row.get("child_id") or "")
            if not child_id:
                continue
            matches.setdefault(child_id, [])
            if candidate_flow_id not in matches[child_id]:
                matches[child_id].append(candidate_flow_id)

    return matches


def _build_failure_patterns(results: list[dict]) -> list[dict]:
    grouped: dict[str, dict] = {}
    for result in results:
        if result.get("ok"):
            continue
        error = _normalize_error(result.get("error"))
        signature = _error_signature(error)
        bucket = grouped.setdefault(
            signature,
            {
                "signature": signature,
                "count": 0,
                "sampleError": error,
                "apis": [],
            },
        )
        bucket["count"] += 1
        bucket["apis"].append(
            {
                "apiId": result.get("apiId"),
                "apiName": result.get("apiName"),
                "order": result.get("order"),
            }
        )

    return sorted(grouped.values(), key=lambda item: item["count"], reverse=True)


async def handle_run_collection(arguments: dict) -> list[TextContent]:
    collection_name = arguments.get("collection_name")
    environment_name = arguments.get("environment_name")

    if not collection_name:
        return [TextContent(type="text", text="Error: collection_name is required")]

    async with MaerisClient() as client:
        collection = await _find_collection_by_name(client, str(collection_name))
        if not collection:
            return [TextContent(type="text", text=f"Error: no collection found with name '{collection_name}'")]

        env_id, resolved_env_name, env_err, warnings = await _resolve_env(
            client, str(environment_name) if environment_name else None
        )
        if env_err:
            return [TextContent(type="text", text=env_err)]

        collection_id = str(collection.get("api_collection_id") or "")
        apis = await _collect_collection_apis(client, collection_id, str(collection_name))

        if not apis:
            result = {
                "collectionName": collection_name,
                "collectionId": collection_id,
                "environmentName": resolved_env_name,
                "envId": env_id,
                "totalApis": 0,
                "executed": 0,
                "succeeded": 0,
                "failed": 0,
                "summary": {
                    "failurePatterns": [],
                    "warnings": warnings + ["Collection has no APIs to run."],
                },
                "results": [],
            }
            return [TextContent(type="text", text=json.dumps(result, indent=2))]

        flow_matches = await _build_flow_matches(client)
        results: list[dict] = []

        for index, api in enumerate(apis, start=1):
            api_id = api["id"]
            matched_flow_ids = flow_matches.get(api_id, [])

            if len(matched_flow_ids) > 1:
                results.append(
                    {
                        "apiId": api_id,
                        "apiName": api["name"],
                        "method": api["method"],
                        "url": api["url"],
                        "collectionPath": api["collectionPath"],
                        "order": index,
                        "flowId": None,
                        "ok": False,
                        "error": f"Multiple flows contain api '{api_id}'. Matches: {json.dumps(matched_flow_ids)}",
                    }
                )
                continue

            flow_id = matched_flow_ids[0] if len(matched_flow_ids) == 1 else None

            try:
                response = await client.run_api(api_id=api_id, env_id=env_id, flow_id=flow_id)
                results.append(
                    {
                        "apiId": api_id,
                        "apiName": api["name"],
                        "method": api["method"],
                        "url": api["url"],
                        "collectionPath": api["collectionPath"],
                        "order": index,
                        "flowId": flow_id,
                        "ok": True,
                        "response": response,
                        "error": None,
                    }
                )
            except Exception as exc:
                results.append(
                    {
                        "apiId": api_id,
                        "apiName": api["name"],
                        "method": api["method"],
                        "url": api["url"],
                        "collectionPath": api["collectionPath"],
                        "order": index,
                        "flowId": flow_id,
                        "ok": False,
                        "error": _normalize_error(exc),
                    }
                )

    succeeded = len([r for r in results if r["ok"]])
    failed = len(results) - succeeded

    result = {
        "collectionName": collection_name,
        "collectionId": collection_id,
        "environmentName": resolved_env_name,
        "envId": env_id,
        "totalApis": len(apis),
        "executed": len(results),
        "succeeded": succeeded,
        "failed": failed,
        "summary": {
            "failurePatterns": _build_failure_patterns(results),
            "warnings": warnings,
        },
        "results": results,
    }
    return [TextContent(type="text", text=json.dumps(result, indent=2))]
