"""LLM-guided codebase scan that extracts and auto-saves relevant APIs to Maeris."""

import json
import os
from pathlib import Path
from typing import Any
from urllib.parse import urlparse

from mcp.types import TextContent, Tool

from maeris_mcp.maeris.client import MaerisClient
from maeris_mcp.maeris.types import FieldPair, MaerisAPI
from maeris_mcp.scanners.code_reader import collect_scan_files, get_file_contents
from maeris_mcp.storage.api_store import APIStore
from maeris_mcp.tools.collection_tools import _find_collection_by_name
from maeris_mcp.tools.portal_api_tools import _parameterize_url, _setup_environment
from maeris_mcp.tools.process_data import _parse_extracted_api
from maeris_mcp.types.api import ExtractedAPI

_MAX_FILE_CHARS = 8_000


def _api_output_schema() -> dict[str, Any]:
    """Simplified flat API schema — only fields that get saved to Maeris."""
    return {
        "type": "object",
        "properties": {
            "name": {"type": "string", "description": "Human-readable API name"},
            "method": {
                "type": "string",
                "enum": ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"],
            },
            "endpoint": {
                "type": "string",
                "description": "Full URL or path (e.g. {{BASE_URL}}/users/{id} or /users/{id})",
            },
            "headers": {
                "type": "array",
                "items": {
                    "type": "object",
                    "properties": {
                        "name": {"type": "string"},
                        "value": {"type": "string"},
                    },
                },
            },
            "queryParams": {
                "type": "array",
                "items": {
                    "type": "object",
                    "properties": {
                        "name": {"type": "string"},
                        "type": {"type": "string"},
                    },
                },
            },
        },
        "required": ["name", "method", "endpoint"],
    }


def _scanning_instructions(functionality: str) -> str:
    return (
        f"create_apis_for scanning instructions (functionality: {functionality!r}):\n"
        "1. Analyze each file for HTTP API calls: endpoints, methods, headers, query params.\n"
        "2. Submit ONLY the apis that are relevant to the requested functionality.\n"
        "3. Skip all APIs unrelated to the functionality — do not submit them.\n"
        "4. Call again with apis=[...found] and the next offset to continue scanning.\n"
        "5. Repeat until hasMore=false, then call with apis=[...] and is_last=true to auto-save.\n"
        "Note: The MCP tool stores your submissions; you must perform the analysis and filtering."
    )


def create_apis_for_tool() -> Tool:
    """Return the tool definition for create_apis_for."""
    return Tool(
        name="create_apis_for",
        description=(
            "LLM-guided codebase scan that extracts and automatically creates in Maeris "
            "only the APIs relevant to a given functionality. Use when the user asks to "
            "'create APIs for <feature>' or 'scan codebase for <feature> APIs'.\n\n"
            "Workflow (same paginated pattern as api_scan):\n"
            "Phase 1 (Scan): Call with target_path + functionality + collection_name + "
            "include_content=true. Analyze each file batch. Submit only the apis relevant "
            "to the functionality alongside the next offset. Repeat until hasMore=false.\n"
            "Phase 2 (Save): On the final call pass apis=[...] + is_last=true. "
            "The tool auto-saves the APIs to Maeris under collection_name and returns a summary."
        ),
        inputSchema={
            "type": "object",
            "properties": {
                "target_path": {
                    "type": "string",
                    "description": "Absolute path to scan (first call only; defaults to server working directory)",
                },
                "session_id": {
                    "type": "string",
                    "description": "Existing session ID to continue/paginate",
                },
                "functionality": {
                    "type": "string",
                    "description": "Feature or domain to filter for (e.g. 'user authentication', 'order management')",
                },
                "collection_name": {
                    "type": "string",
                    "description": "Maeris collection name to save the discovered APIs into",
                },
                "include_content": {
                    "type": "boolean",
                    "description": "Include file contents for analysis. Default: false",
                    "default": False,
                },
                "offset": {
                    "type": "integer",
                    "description": "Skip first N files when including content. Default: 0",
                    "default": 0,
                },
                "limit": {
                    "type": "integer",
                    "description": "Max files to return content for (1-10). Default: 10",
                    "default": 10,
                },
                "apis": {
                    "type": "array",
                    "items": {"type": "object"},
                    "description": (
                        "APIs relevant to the functionality, extracted from the previous file batch. "
                        "Only include APIs that match the requested functionality."
                    ),
                },
                "http_clients": {
                    "type": "array",
                    "items": {"type": "string"},
                    "description": "HTTP client libraries detected (e.g. ['axios', 'fetch'])",
                },
                "is_last": {
                    "type": "boolean",
                    "description": (
                        "Set true on the final call to auto-save the collected APIs to Maeris "
                        "and receive a creation summary."
                    ),
                },
            },
        },
    )


def _normalize_api(raw: dict[str, Any]) -> ExtractedAPI:
    """Normalize a raw API dict using the shared parser."""
    return _parse_extracted_api(raw)


def _build_api_url(api: ExtractedAPI) -> str:
    """Build a full URL from base_url + endpoint."""
    if api.base_url:
        base = api.base_url.rstrip("/")
        endpoint = api.endpoint if api.endpoint.startswith("/") else f"/{api.endpoint}"
        return base + endpoint
    return api.endpoint


def _extract_base_url_from_extracted_apis(apis: list[ExtractedAPI]) -> str | None:
    """Try to extract a base URL from the first API that has a full URL or base_url."""
    for api in apis:
        if api.base_url:
            return api.base_url.rstrip("/")
        url = _build_api_url(api)
        if url.startswith(("http://", "https://")):
            parsed = urlparse(url)
            return f"{parsed.scheme}://{parsed.netloc}"
    return None


def _extracted_to_maeris(api: ExtractedAPI, collection_id: str, parameterize: bool = False) -> MaerisAPI:
    """Convert an ExtractedAPI to a MaerisAPI for the portal."""
    url = _build_api_url(api)
    if parameterize:
        url = _parameterize_url(url)

    headers: list[FieldPair] = []
    for h in api.headers or []:
        headers.append(FieldPair(field_name=h.name, field_value=h.value or ""))

    params: list[FieldPair] = []
    for q in api.query_params or []:
        params.append(FieldPair(field_name=q.name, field_value=q.type or ""))

    return MaerisAPI(
        name=api.name,
        type=api.method.value,
        url=url,
        payload="",
        headers=headers,
        params=params,
        parent_collection_id=collection_id,
    )


async def _find_or_create_collection(client: MaerisClient, collection_name: str) -> str:
    """Return the collection ID, creating the collection if it does not exist."""
    collection = await _find_collection_by_name(client, collection_name)
    if collection:
        return str(collection.get("api_collection_id") or collection.get("_id") or "")
    return await client.create_collection(collection_name)


async def handle_create_apis_for(api_store: APIStore, arguments: dict) -> list[TextContent]:
    """Handle the create_apis_for tool call."""
    target_path = arguments.get("target_path") or os.getcwd()
    session_id = arguments.get("session_id")
    functionality = arguments.get("functionality") or ""
    collection_name = arguments.get("collection_name") or ""
    include_content = arguments.get("include_content", False)
    try:
        offset = int(arguments.get("offset", 0))
        limit = max(1, min(int(arguments.get("limit", 10)), 10))
    except (ValueError, TypeError):
        return [TextContent(type="text", text="Error: offset and limit must be integers")]
    raw_apis = arguments.get("apis") or []
    if isinstance(raw_apis, dict):
        raw_apis = [raw_apis]
    raw_http_clients = arguments.get("http_clients") or []
    is_last = bool(arguments.get("is_last") or arguments.get("isLast"))

    # --- Resolve or create session ---
    if session_id:
        session = api_store.get_for_session(session_id)
        if not session:
            return [TextContent(type="text", text=f"Error: session not found: {session_id}")]
    else:
        if not functionality:
            return [TextContent(type="text", text="Error: functionality is required on the first call")]
        if not collection_name:
            return [TextContent(type="text", text="Error: collection_name is required on the first call")]

        scan_root = os.path.abspath(target_path)
        if not os.path.exists(scan_root):
            return [TextContent(type="text", text=f"Error: path does not exist: {target_path}")]

        path = Path(scan_root)
        project_root = path if path.is_dir() else path.parent
        base_for_rel = str(project_root.parent if project_root.parent != project_root else project_root)

        files_abs, _ = collect_scan_files(scan_root, api_signals_only=True)
        file_list = [os.path.relpath(p, base_for_rel) for p in files_abs]

        session_id = api_store.create_for_session(
            functionality=functionality,
            collection_name=collection_name,
            target_path=scan_root,
            scan_root=scan_root,
            base_for_rel=base_for_rel,
            file_list=file_list,
        )
        session = api_store.get_for_session(session_id)
        if not session:
            return [TextContent(type="text", text="Error: failed to initialize session")]

    # --- Store submitted APIs ---
    warnings: list[str] = []
    apis_stats: dict[str, int] = {}
    if raw_apis and isinstance(raw_apis, list):
        dict_apis = [r for r in raw_apis if isinstance(r, dict)]
        dropped_invalid = len(raw_apis) - len(dict_apis)
        if dropped_invalid:
            warnings.append(f"{dropped_invalid} API item(s) skipped: not valid objects.")
        normalized = [_normalize_api(r) for r in dict_apis]
        valid_apis = [a for a in normalized if a.name.strip() and a.endpoint.strip()]
        dropped_empty = len(normalized) - len(valid_apis)
        if dropped_empty:
            warnings.append(f"{dropped_empty} API item(s) skipped: missing name or endpoint.")
        apis_stats = api_store.add_for_apis(
            session_id,
            valid_apis,
            [c for c in raw_http_clients if isinstance(c, str) and c.strip()],
        )
    elif raw_http_clients and isinstance(raw_http_clients, list):
        valid_clients = [c for c in raw_http_clients if isinstance(c, str) and c.strip()]
        if valid_clients:
            api_store.add_for_apis(session_id, [], valid_clients)

    base_response: dict[str, Any] = {
        "sessionId": session_id,
        "functionality": session.functionality,
        "collectionName": session.collection_name,
        "targetPath": session.target_path,
        "filesTotal": len(session.file_list),
        "status": session.status,
    }

    if apis_stats:
        base_response["apisSubmitted"] = apis_stats
    if warnings:
        base_response["warnings"] = warnings

    # --- Finalize and save to Maeris ---
    if is_last:
        finalized = api_store.finalize_for_session(session_id)
        if not finalized:
            return [TextContent(type="text", text=f"Error: could not finalize session: {session_id}")]

        apis = finalized.apis
        if not apis:
            base_response["status"] = "completed"
            base_response["warnings"] = base_response.get("warnings", []) + [
                "No APIs were collected. Nothing saved to Maeris."
            ]
            base_response["created"] = []
            base_response["summary"] = {"total": 0, "byMethod": {}}
            return [TextContent(type="text", text=json.dumps(base_response, indent=2))]

        created: list[dict] = []
        save_errors: list[str] = []

        async with MaerisClient() as client:
            collection_id = await _find_or_create_collection(client, finalized.collection_name)

            # --- Environment setup: check/create env + parameterize URLs ---
            extracted_base_url = _extract_base_url_from_extracted_apis(apis)
            extracted_env_fields: list[dict] = []
            if extracted_base_url:
                extracted_env_fields.append({
                    "field_name": "base_url",
                    "field_value": extracted_base_url,
                })

            # Build API dicts for _setup_environment's fallback extraction
            api_dicts = [{"url": _build_api_url(a)} for a in apis]

            env_summary: dict = {}
            try:
                env_summary = await _setup_environment(
                    client,
                    finalized.collection_name,
                    extracted_env_fields,
                    api_dicts,
                )
            except Exception as exc:
                save_errors.append(f"Environment setup: {exc}")

            parameterize = bool(env_summary.get("base_url"))

            for api in apis:
                try:
                    maeris_api = _extracted_to_maeris(api, collection_id, parameterize=parameterize)
                    api_id = await client.create_api(maeris_api)
                    created.append({
                        "id": api_id,
                        "name": api.name,
                        "method": api.method.value,
                        "url": maeris_api.url,
                    })
                except Exception as exc:
                    save_errors.append(f"{api.name}: {exc}")

        by_method: dict[str, int] = {}
        for entry in created:
            m = entry["method"]
            by_method[m] = by_method.get(m, 0) + 1

        base_response["status"] = "completed"
        base_response["collectionId"] = collection_id
        base_response["created"] = created
        base_response["summary"] = {
            "total": len(created),
            "byMethod": by_method,
            "collectionName": finalized.collection_name,
        }
        if env_summary:
            base_response["environment"] = env_summary
        if save_errors:
            base_response["saveErrors"] = save_errors

        return [TextContent(type="text", text=json.dumps(base_response, indent=2))]

    # --- Paginate file contents ---
    if include_content:
        file_list = session.file_list
        if not file_list:
            base_response["warnings"] = base_response.get("warnings", []) + [
                "No scannable files found at the target path."
            ]
            base_response["nextStep"] = "Call with is_last=true to finalize."
            return [TextContent(type="text", text=json.dumps(base_response, indent=2))]
        if offset >= len(file_list):
            return [TextContent(
                type="text",
                text=f"Error: offset {offset} is out of range — file list has {len(file_list)} file(s)",
            )]
        base_for_rel = session.base_for_rel
        paginated_rels = file_list[offset: offset + limit]
        abs_paths = [str(Path(base_for_rel) / p) for p in paginated_rels]
        contents = get_file_contents(abs_paths)

        has_more = offset + limit < len(file_list)
        base_response["pagination"] = {
            "offset": offset,
            "limit": limit,
            "returned": len(paginated_rels),
            "total": len(file_list),
            "hasMore": has_more,
            "nextOffset": offset + limit if has_more else None,
        }

        # Fix 1: cap each file at _MAX_FILE_CHARS to keep response size bounded
        truncated = 0
        file_contents_map: dict[str, str] = {}
        for rel, abs_path in zip(paginated_rels, abs_paths):
            content = contents.get(abs_path, "")
            if len(content) > _MAX_FILE_CHARS:
                content = content[:_MAX_FILE_CHARS]
                truncated += 1
            file_contents_map[rel] = content
        base_response["fileContents"] = file_contents_map
        if truncated:
            base_response.setdefault("warnings", []).append(
                f"{truncated} file(s) truncated to {_MAX_FILE_CHARS} chars to stay within response limits."
            )

        # Fix 3: inject instructions + schema once on the first batch only
        if offset == 0:
            base_response["analysisInstructions"] = _scanning_instructions(session.functionality)
            base_response["expectedApiSchema"] = _api_output_schema()
    else:
        base_response["nextStep"] = (
            "Call again with include_content=true, offset=0 to fetch file contents. "
            f"Submit only apis relevant to {session.functionality!r} alongside subsequent offsets. "
            "Set is_last=true on the final call to auto-save to Maeris."
        )

    return [TextContent(type="text", text=json.dumps(base_response, indent=2))]
