"""LLM-guided API automation setup tool for MCP server."""

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

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.automation_store import SetupAutomationStore


# ---------------------------------------------------------------------------
# Tool definition
# ---------------------------------------------------------------------------


def setup_api_automation_tool() -> Tool:
    """Return the tool definition for setup_api_automation."""
    return Tool(
        name="setup_api_automation",
        description=(
            "Fully automates setting up API management in Maeris from a repository scan. Use when the user says 'setup API automation' or 'setup automation for API automation'. "
            "Scans the repo to extract all APIs, detects environments (variable names only — "
            "never reads real secret files), and proposes meaningful API flows grouped by feature "
            "domain — then enters an interactive review loop before saving everything to Maeris.\n\n"
            "Workflow:\n"
            "Phase 1 (Scanning): Call with target_path + include_content=true to start. "
            "Analyze each file batch and submit apis=[] alongside the next offset. "
            "On the final batch call with is_last=true, also submit flows=[] and environments=[].\n"
            "Phase 2 (Review): Inspect the proposed APIs, flows, and environments. "
            "To exclude items the user does not want saved, use the toggle parameters: "
            "exclude_apis, exclude_flows, exclude_flow_steps, exclude_environments, exclude_env_fields. "
            "To edit content use updated_apis / updated_flows / updated_environments. "
            "Repeat until the user confirms the selection is correct. "
            "IMPORTANT: ALL exclusions must be applied during this phase — NEVER call approve=true "
            "and then delete items from Maeris afterwards. Save-then-delete is always wrong here.\n"
            "Phase 3 (Save): Only call with collection_name + approve=true after all exclusions are applied. "
            "Only the selected items will be persisted to Maeris."
        ),
        inputSchema={
            "type": "object",
            "properties": {
                "target_path": {
                    "type": "string",
                    "description": "Absolute path to scan (first call only).",
                },
                "session_id": {
                    "type": "string",
                    "description": "Existing session ID for subsequent calls.",
                },
                "include_content": {
                    "type": "boolean",
                    "description": "Include file contents during scanning phase. 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 extracted from the previous file batch. Submit alongside the next offset.",
                },
                "flows": {
                    "type": "array",
                    "items": {"type": "object"},
                    "description": "Proposed API flows to submit on is_last=true call.",
                },
                "environments": {
                    "type": "array",
                    "items": {"type": "object"},
                    "description": "Detected environments to submit on is_last=true call.",
                },
                "is_last": {
                    "type": "boolean",
                    "description": "Set true on the final scanning call to finalize scan and transition to review.",
                },
                "updated_apis": {
                    "type": "array",
                    "items": {"type": "object"},
                    "description": "Review phase: full corrected APIs list (replaces current state).",
                },
                "updated_flows": {
                    "type": "array",
                    "items": {"type": "object"},
                    "description": "Review phase: full corrected flows list (replaces current state).",
                },
                "updated_environments": {
                    "type": "array",
                    "items": {"type": "object"},
                    "description": "Review phase: full corrected environments list (replaces current state).",
                },
                "exclude_apis": {
                    "type": "array",
                    "items": {"type": "string"},
                    "description": "Review phase: API names to deselect (will not be saved to Maeris).",
                },
                "include_apis": {
                    "type": "array",
                    "items": {"type": "string"},
                    "description": "Review phase: API names to re-select (undo a previous exclusion).",
                },
                "exclude_flows": {
                    "type": "array",
                    "items": {"type": "string"},
                    "description": "Review phase: flow names to deselect.",
                },
                "include_flows": {
                    "type": "array",
                    "items": {"type": "string"},
                    "description": "Review phase: flow names to re-select.",
                },
                "exclude_flow_steps": {
                    "type": "array",
                    "items": {
                        "type": "object",
                        "properties": {
                            "flow_name": {"type": "string"},
                            "api_name": {"type": "string"},
                        },
                        "required": ["flow_name", "api_name"],
                    },
                    "description": "Review phase: individual flow steps to deselect.",
                },
                "include_flow_steps": {
                    "type": "array",
                    "items": {
                        "type": "object",
                        "properties": {
                            "flow_name": {"type": "string"},
                            "api_name": {"type": "string"},
                        },
                        "required": ["flow_name", "api_name"],
                    },
                    "description": "Review phase: individual flow steps to re-select.",
                },
                "exclude_environments": {
                    "type": "array",
                    "items": {"type": "string"},
                    "description": "Review phase: environment names to deselect (will not be saved).",
                },
                "include_environments": {
                    "type": "array",
                    "items": {"type": "string"},
                    "description": "Review phase: environment names to re-select.",
                },
                "exclude_env_fields": {
                    "type": "array",
                    "items": {
                        "type": "object",
                        "properties": {
                            "env_name": {"type": "string"},
                            "field_name": {"type": "string"},
                        },
                        "required": ["env_name", "field_name"],
                    },
                    "description": "Review phase: individual env var fields to deselect.",
                },
                "include_env_fields": {
                    "type": "array",
                    "items": {
                        "type": "object",
                        "properties": {
                            "env_name": {"type": "string"},
                            "field_name": {"type": "string"},
                        },
                        "required": ["env_name", "field_name"],
                    },
                    "description": "Review phase: individual env var fields to re-select.",
                },
                "set_env_field_values": {
                    "type": "array",
                    "items": {
                        "type": "object",
                        "properties": {
                            "env_name": {"type": "string"},
                            "field_name": {"type": "string"},
                            "field_value": {"type": "string"},
                        },
                        "required": ["env_name", "field_name", "field_value"],
                    },
                    "description": "Review phase: set actual values for specific env var fields (replaces {{PLACEHOLDER}}).",
                },
                "collection_name": {
                    "type": "string",
                    "description": "Save phase: name for the new Maeris collection.",
                },
                "approve": {
                    "type": "boolean",
                    "description": "Save phase: set true (along with collection_name) to persist everything to Maeris.",
                },
                "verbose": {
                    "type": "boolean",
                    "description": "Review phase: set true to include full apis/flows/environments in the response. Forced true on content edits and initial review. Default: false.",
                    "default": False,
                },
            },
        },
    )


# ---------------------------------------------------------------------------
# Analysis instructions embedded in responses
# ---------------------------------------------------------------------------


def _scanning_instructions() -> str:
    return (
        "setup_api_automation scanning instructions:\n"
        "1. Call with include_content=true and offset=0 to receive the first file batch.\n"
        "2. Analyze each file for HTTP API calls: endpoints, methods, headers, params, auth, payloads.\n"
        "3. Call again with apis=[...found] and the next offset to submit and fetch the next batch.\n"
        "4. Repeat until hasMore=false.\n"
        "5. On the final call, also submit:\n"
        "   - flows=[...]: 3-7 meaningful user-journey flows grouped by feature domain.\n"
        "     Each flow: {name, description, steps: [{api_name, order, method, endpoint}]}\n"
        "     Name flows as user-facing actions: 'User Login', 'Browse Products', 'Complete Purchase'.\n"
        "     Steps should reflect real sequential data dependencies (e.g. login → get profile).\n"
        "   - environments=[...]: 1-3 named environments ('Development', 'Staging', 'Production').\n"
        "     NEVER read .env, .env.local, .env.production, or any file with real credentials.\n"
        "     Instead infer variable NAMES ONLY from:\n"
        "       * .env.example / .env.template placeholder files\n"
        "       * process.env.FOO / os.environ['FOO'] / os.getenv('FOO') in source code\n"
        "       * Base URL constants hardcoded in config files\n"
        "     Use {{VARIABLE_NAME}} as the field value — users fill in real values during review or after import.\n"
        "     Env vars found in API route source code (process.env.FOO, os.environ['FOO']) should be\n"
        "     included as fields in ALL detected environments — the same variable name exists in every\n"
        "     environment, just with different values per environment.\n"
        "     Each environment: {name, fields: [{field_name, field_value}]}\n"
        "6. Set is_last=true to finalize the scan and transition to review.\n"
        "Note: The MCP tool stores your submissions; Claude must perform the analysis."
    )


def _api_output_schema() -> dict[str, Any]:
    return {
        "type": "object",
        "properties": {
            "name": {"type": "string", "description": "Human-readable API name"},
            "method": {
                "type": "string",
                "enum": ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"],
            },
            "url": {
                "type": "string",
                "description": "Full URL including base URL (use {{BASE_URL}} placeholder if dynamic)",
            },
            "endpoint": {
                "type": "string",
                "description": "Path portion only, e.g. /users/{id}",
            },
            "payload": {
                "type": ["object", "string", "null"],
                "description": "Example request body or schema description",
            },
            "headers": {
                "type": "array",
                "items": {
                    "type": "object",
                    "properties": {
                        "field_name": {"type": "string"},
                        "field_value": {"type": "string"},
                    },
                },
            },
            "params": {
                "type": "array",
                "items": {
                    "type": "object",
                    "properties": {
                        "field_name": {"type": "string"},
                        "field_value": {"type": "string"},
                    },
                },
            },
        },
        "required": ["name", "method", "url"],
    }


def _pending_decision(
    selected_apis: int,
    total_apis: int,
    selected_flows: int,
    total_flows: int,
    env_names: list[str],
    selected_envs: int | None = None,
    total_envs: int | None = None,
    unfilled_env_vars: list[str] | None = None,
) -> dict:
    env_summary = f"{selected_envs} of {total_envs}" if selected_envs is not None else str(len(env_names))
    result = {
        "question": (
            f"{selected_apis} of {total_apis} APIs and {selected_flows} of {total_flows} flows are selected. "
            f"Environments: {env_summary} selected ({', '.join(env_names) if env_names else 'none'}). "
            "Confirm the selection is correct before saving, or adjust it first."
        ),
        "warning": (
            "NEVER call approve=true and then delete items from Maeris to clean up. "
            "Apply ALL exclusions using the toggle parameters in this phase BEFORE approving. "
            "approve=true saves exactly the currently selected items — nothing more, nothing less."
        ),
        "hint": (
            "Exclude items: exclude_apis / exclude_flows / exclude_flow_steps / "
            "exclude_environments / exclude_env_fields. "
            "Re-include: include_apis / include_flows / include_flow_steps / "
            "include_environments / include_env_fields. "
            "Set env values: set_env_field_values=[{env_name, field_name, field_value}]. "
            "Edit content: updated_apis / updated_flows / updated_environments. "
            "Save only when ready: collection_name + approve=true."
        ),
    }
    if unfilled_env_vars:
        result["unfilledEnvVars"] = unfilled_env_vars
        result["envVarAction"] = (
            "Ask the user for actual values for these env vars, "
            "then call set_env_field_values=[{env_name, field_name, field_value}, ...]"
        )
    return result


# ---------------------------------------------------------------------------
# Review response builder (shared by _continue_scan and _apply_feedback)
# ---------------------------------------------------------------------------


def _build_review_response(
    store: SetupAutomationStore,
    session_id: str,
    changes: list[str],
    verbose: bool = False,
) -> list[TextContent]:
    """Build a decorated review response with selected flags on every API/flow/step."""
    session = store.get_session(session_id)
    if not session:
        return [TextContent(type="text", text=f"Error: session lost: {session_id}")]

    env_names = [e.get("name", "") for e in session.environments]
    total_apis = len(session.apis)
    total_flows = len(session.flows)
    total_envs = len(session.environments)
    selected_apis = total_apis - len(session.excluded_api_names)
    selected_flows = total_flows - len(session.excluded_flow_names)
    selected_envs = total_envs - len(session.excluded_env_names)

    # Collect unfilled env vars (selected env + selected field + still a {{...}} placeholder)
    unfilled: list[str] = []
    for env in session.environments:
        env_name = env.get("name", "")
        if env_name in session.excluded_env_names:
            continue
        for f in env.get("fields") or []:
            field_name = f.get("field_name", "")
            field_value = f.get("field_value", "")
            if (env_name, field_name) not in session.excluded_env_fields:
                if field_value.startswith("{{") and field_value.endswith("}}"):
                    unfilled.append(f"{env_name}.{field_name}")

    response: dict[str, Any] = {
        "sessionId": session_id,
        "status": "review",
        "summary": {
            "totalApis": total_apis,
            "selectedApis": selected_apis,
            "totalFlows": total_flows,
            "selectedFlows": selected_flows,
            "totalEnvironments": total_envs,
            "selectedEnvironments": selected_envs,
            "environments": env_names,
        },
        "changesApplied": changes,
        "pendingDecision": _pending_decision(
            selected_apis, total_apis, selected_flows, total_flows, env_names,
            selected_envs=selected_envs, total_envs=total_envs,
            unfilled_env_vars=unfilled if unfilled else None,
        ),
    }

    if verbose:
        apis_with_selection = [
            {**api, "selected": api.get("name") not in session.excluded_api_names}
            for api in session.apis
        ]

        flows_with_selection = []
        for flow in session.flows:
            flow_name = flow.get("name", "")
            steps = flow.get("steps") or []
            steps_with_selection = [
                {
                    **step,
                    "selected": (flow_name, step.get("api_name", "")) not in session.excluded_flow_steps,
                }
                for step in steps
            ]
            flows_with_selection.append(
                {**flow, "selected": flow_name not in session.excluded_flow_names, "steps": steps_with_selection}
            )

        envs_with_selection = []
        for env in session.environments:
            env_name = env.get("name", "")
            fields_with_selection = [
                {**f, "selected": (env_name, f.get("field_name", "")) not in session.excluded_env_fields}
                for f in env.get("fields") or []
            ]
            envs_with_selection.append({
                **env,
                "selected": env_name not in session.excluded_env_names,
                "fields": fields_with_selection,
            })

        response["apis"] = apis_with_selection
        response["flows"] = flows_with_selection
        response["environments"] = envs_with_selection
    else:
        response["hint"] = "Pass verbose=true to see the full API/flow/environment list."

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


# ---------------------------------------------------------------------------
# Phase routing
# ---------------------------------------------------------------------------


async def handle_setup_api_automation(
    store: SetupAutomationStore, arguments: dict
) -> list[TextContent]:
    """Handle the setup_api_automation tool call."""
    session_id = arguments.get("session_id") or arguments.get("sessionId")

    # No session → start new scan
    if not session_id:
        return await _start_session(store, arguments)

    session = store.get_session(session_id)
    if not session:
        return [TextContent(type="text", text=f"Error: session not found: {session_id}")]

    if session.status == "scanning":
        return await _continue_scan(store, session_id, arguments)

    if session.status == "review":
        approve = bool(arguments.get("approve"))
        collection_name = arguments.get("collection_name")
        if approve and collection_name:
            return await _save_to_maeris(store, session_id, collection_name)
        return await _apply_feedback(store, session_id, arguments)

    # completed
    return [TextContent(
        type="text",
        text=json.dumps({"sessionId": session_id, "status": "completed"}, indent=2),
    )]


# ---------------------------------------------------------------------------
# Phase 1: Start session
# ---------------------------------------------------------------------------


async def _start_session(
    store: SetupAutomationStore, arguments: dict
) -> list[TextContent]:
    target_path = arguments.get("target_path") or os.getcwd()
    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, _info = 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 = store.create_session(
        target_path=scan_root,
        base_for_rel=base_for_rel,
        file_list=file_list,
    )

    response: dict[str, Any] = {
        "sessionId": session_id,
        "status": "scanning",
        "targetPath": scan_root,
        "filesTotal": len(file_list),
        "analysisInstructions": _scanning_instructions(),
        "expectedApiSchema": _api_output_schema(),
        "nextStep": (
            "Call again with include_content=true, offset=0 to fetch file contents. "
            "Pass apis=[...] alongside subsequent offsets to submit extracted APIs as you go. "
            "On the final call also submit flows=[] and environments=[], then set is_last=true."
        ),
    }
    return [TextContent(type="text", text=json.dumps(response, indent=2))]


# ---------------------------------------------------------------------------
# Phase 1: Continue scan (accept submissions, return next file batch)
# ---------------------------------------------------------------------------


async def _continue_scan(
    store: SetupAutomationStore, session_id: str, arguments: dict
) -> list[TextContent]:
    session = store.get_session(session_id)
    if not session:
        return [TextContent(type="text", text=f"Error: session not found: {session_id}")]

    include_content = bool(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_flows = arguments.get("flows") or []
    raw_environments = arguments.get("environments") or []
    is_last = bool(arguments.get("is_last") or arguments.get("isLast"))

    # 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 = len(raw_apis) - len(dict_apis)
        if dropped:
            warnings.append(f"{dropped} API item(s) skipped: not valid objects.")
        valid_apis = [a for a in dict_apis if (a.get("name") or "").strip() and (a.get("url") or a.get("endpoint") or "").strip()]
        dropped_empty = len(dict_apis) - len(valid_apis)
        if dropped_empty:
            warnings.append(f"{dropped_empty} API item(s) skipped: missing name or url/endpoint.")
        apis_stats = store.add_apis(session_id, valid_apis)

    base_response: dict[str, Any] = {
        "sessionId": session_id,
        "status": "scanning",
        "targetPath": session.target_path,
        "filesTotal": len(session.file_list),
        "apisAccumulatedTotal": len(session.apis),
    }
    if apis_stats:
        base_response["apisSubmitted"] = apis_stats
    if warnings:
        base_response["warnings"] = warnings

    # Finalize scan → transition to review
    if is_last:
        flows = [f for f in raw_flows if isinstance(f, dict)]
        environments = [e for e in raw_environments if isinstance(e, dict)]

        if not store.finalize_scan(session_id, flows, environments):
            return [TextContent(type="text", text=f"Error: could not finalize session: {session_id}")]

        review_result = _build_review_response(store, session_id, [], verbose=True)
        if warnings and review_result:
            # Inject warnings into the first content item if possible
            try:
                payload = json.loads(review_result[0].text)
                payload["warnings"] = warnings
                review_result = [TextContent(type="text", text=json.dumps(payload, indent=2))]
            except Exception:
                pass
        return review_result

    # Return next file batch
    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 the empty scan."
            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,
        }
        # Cap each file at 8 KB to keep the total response within MCP token limits.
        # API call patterns appear early and densely; truncation rarely loses signal.
        _MAX_FILE_CHARS = 8_000
        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."
            )
        if offset == 0:
            base_response["analysisInstructions"] = _scanning_instructions()
            base_response["expectedApiSchema"] = _api_output_schema()
    else:
        base_response["nextStep"] = (
            "Call again with include_content=true, offset=0 to fetch file contents. "
            "Pass apis=[...] alongside subsequent offsets to submit extracted APIs as you go. "
            "On the final call also submit flows=[] and environments=[], then set is_last=true."
        )

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


# ---------------------------------------------------------------------------
# Phase 2: Apply feedback (review loop)
# ---------------------------------------------------------------------------


async def _apply_feedback(
    store: SetupAutomationStore, session_id: str, arguments: dict
) -> list[TextContent]:
    changes: list[str] = []

    # Mode 1: toggle operations (targeted, name-based — no data replacement)
    toggle_keys = [
        "exclude_apis", "include_apis",
        "exclude_flows", "include_flows",
        "exclude_flow_steps", "include_flow_steps",
        "exclude_environments", "include_environments",
        "exclude_env_fields", "include_env_fields",
    ]
    has_toggles = any(arguments.get(k) for k in toggle_keys)
    if has_toggles:
        toggle_changes, ok = store.apply_toggles(
            session_id,
            exclude_apis=arguments.get("exclude_apis"),
            include_apis=arguments.get("include_apis"),
            exclude_flows=arguments.get("exclude_flows"),
            include_flows=arguments.get("include_flows"),
            exclude_flow_steps=arguments.get("exclude_flow_steps"),
            include_flow_steps=arguments.get("include_flow_steps"),
            exclude_environments=arguments.get("exclude_environments"),
            include_environments=arguments.get("include_environments"),
            exclude_env_fields=arguments.get("exclude_env_fields"),
            include_env_fields=arguments.get("include_env_fields"),
        )
        if not ok:
            return [TextContent(type="text", text=f"Error: session not found: {session_id}")]
        changes.extend(toggle_changes)

    # Mode 1b: env field value setting (targeted mutation — forces verbose)
    edit_keys = ["updated_apis", "updated_flows", "updated_environments", "set_env_field_values"]
    set_env_field_values = arguments.get("set_env_field_values")
    has_edits = any(arguments.get(k) is not None for k in edit_keys)
    if set_env_field_values:
        value_changes, ok = store.set_env_field_values(session_id, set_env_field_values)
        if not ok:
            return [TextContent(type="text", text=f"Error: session not found: {session_id}")]
        changes.extend(value_changes)

    # Mode 2: content edits (full replacement for URLs, headers, field values)
    content_edit_keys = ["updated_apis", "updated_flows", "updated_environments"]
    has_content_edits = any(arguments.get(k) is not None for k in content_edit_keys)
    if has_content_edits:
        edit_changes, ok = store.update_review(
            session_id,
            updated_apis=arguments.get("updated_apis"),
            updated_flows=arguments.get("updated_flows"),
            updated_environments=arguments.get("updated_environments"),
            feedback=str(arguments.get("feedback") or ""),
        )
        if not ok:
            return [TextContent(type="text", text=f"Error: session not found: {session_id}")]
        changes.extend(edit_changes)
    elif arguments.get("feedback"):
        store.update_review(session_id, None, None, None, feedback=str(arguments["feedback"]))

    if not changes:
        changes = ["No changes detected"]

    verbose = bool(arguments.get("verbose", False)) or has_edits
    return _build_review_response(store, session_id, changes, verbose=verbose)


# ---------------------------------------------------------------------------
# Phase 3: Save to Maeris
# ---------------------------------------------------------------------------


def _extract_env_id(result: dict) -> str:
    for key in ["environment_id", "api_environment_id", "env_id", "id", "_id"]:
        value = result.get(key)
        if value:
            return str(value)
    # Some create responses wrap the object under a "data" key
    nested = result.get("data") or result.get("environment") or result.get("api_environment")
    if isinstance(nested, dict):
        for key in ["environment_id", "api_environment_id", "env_id", "id", "_id"]:
            value = nested.get(key)
            if value:
                return str(value)
    return ""


def _extract_flow_id(result: dict) -> str:
    for key in ["flow_id", "api_flow_id", "id", "_id"]:
        value = result.get(key)
        if value:
            return str(value)
    # Some create responses wrap the object under a "data" key
    nested = result.get("data") or result.get("flow") or result.get("api_flow")
    if isinstance(nested, dict):
        for key in ["flow_id", "api_flow_id", "id", "_id"]:
            value = nested.get(key)
            if value:
                return str(value)
    return ""


def _build_flow_params(api_id: str, api_def: dict) -> list[dict]:
    """Build the params list for one flow map step from the API definition."""
    params: list[dict] = []

    # Headers
    for h in api_def.get("headers") or []:
        if isinstance(h, dict) and h.get("field_name"):
            params.append({
                "api_id": api_id,
                "param_key": h["field_name"],
                "response_api_id": None,
                "response_key": h.get("field_value") or "",
                "type": "HEADERS",
            })

    # URL placeholder variables — extract {{VAR}} tokens from the URL string
    url = api_def.get("url") or api_def.get("endpoint") or ""
    for var in re.findall(r"\{\{(\w+)\}\}", url):
        params.append({
            "api_id": api_id,
            "param_key": var,
            "response_api_id": None,
            "response_key": f"{{{{{var}}}}}",
            "type": "URL",
        })

    # Query params
    for p in api_def.get("params") or []:
        if isinstance(p, dict) and p.get("field_name"):
            params.append({
                "api_id": api_id,
                "param_key": p["field_name"],
                "response_api_id": None,
                "response_key": p.get("field_value") or "",
                "type": "URL",
            })

    # Payload / body fields
    payload = api_def.get("payload")
    if isinstance(payload, dict):
        for key, value in payload.items():
            params.append({
                "api_id": api_id,
                "param_key": key,
                "response_api_id": None,
                "response_key": str(value),
                "type": "BODY",
            })

    return params


async def _save_to_maeris(
    store: SetupAutomationStore, session_id: str, collection_name: str
) -> list[TextContent]:
    session = store.get_session(session_id)
    if not session:
        return [TextContent(type="text", text=f"Error: session not found: {session_id}")]

    apis_created: list[dict] = []
    apis_failed: list[str] = []
    envs_created: list[str] = []
    envs_failed: list[str] = []
    flows_created: list[str] = []
    flows_failed: list[str] = []

    async with MaerisClient() as client:
        # 1. Create collection
        try:
            collection_id = await client.create_collection(collection_name)
        except Exception as exc:
            return [TextContent(type="text", text=f"Error: failed to create collection '{collection_name}': {exc}")]

        if not collection_id:
            return [TextContent(type="text", text=f"Error: collection created but no ID returned for '{collection_name}'")]

        # 2. Create APIs and build name→id map and name→def map (only selected APIs)
        api_id_map: dict[str, str] = {}
        api_def_map: dict[str, dict] = {}
        selected_api_list = [a for a in session.apis if a.get("name") not in session.excluded_api_names]
        for api_def in selected_api_list:
            name = api_def.get("name") or ""
            method = api_def.get("method") or ""
            url = api_def.get("url") or api_def.get("endpoint") or ""
            if not name or not method or not url:
                apis_failed.append(f"Skipped (missing name/method/url): {api_def}")
                continue

            # Convert headers/params to FieldPair list
            raw_headers = api_def.get("headers") or []
            raw_params = api_def.get("params") or []

            def _to_field_pairs(items: list) -> list[FieldPair]:
                result_pairs: list[FieldPair] = []
                for item in items:
                    if isinstance(item, dict):
                        fn = item.get("field_name") or item.get("name") or ""
                        fv = item.get("field_value") or item.get("value") or ""
                        if fn:
                            result_pairs.append(FieldPair(field_name=fn, field_value=str(fv)))
                return result_pairs

            headers_fp = _to_field_pairs(raw_headers)
            params_fp = _to_field_pairs(raw_params)

            try:
                maeris_api = MaerisAPI(
                    name=name,
                    type=method.lower(),
                    url=url,
                    payload=api_def.get("payload") or "",
                    headers=headers_fp,
                    params=params_fp,
                    parent_collection_id=collection_id,
                )
                api_id = await client.create_api(maeris_api)
                api_id_map[name] = api_id
                api_def_map[name] = api_def
                apis_created.append({"id": api_id, "name": name, "method": method.upper(), "url": url})
            except Exception as exc:
                apis_failed.append(f"Failed to create '{name}': {exc}")

        # 3. Create selected environments with selected fields only
        selected_env_list = [e for e in session.environments if e.get("name") not in session.excluded_env_names]
        for env_def in selected_env_list:
            env_name = env_def.get("name") or ""
            if not env_name:
                envs_failed.append(f"Skipped environment (no name): {env_def}")
                continue
            try:
                env_result = await client.create_environment(env_name)
                env_id = _extract_env_id(env_result)
                if env_id and env_def.get("fields"):
                    fields_payload = [
                        {"field_name": f.get("field_name", ""), "field_value": f.get("field_value", "")}
                        for f in env_def.get("fields", [])
                        if isinstance(f, dict) and f.get("field_name")
                        and (env_name, f.get("field_name", "")) not in session.excluded_env_fields
                    ]
                    if fields_payload:
                        await client.add_environment_fields(env_id, fields_payload)
                envs_created.append(env_name)
            except Exception as exc:
                envs_failed.append(f"Failed to create environment '{env_name}': {exc}")

        # 4. Create flows (only selected flows, with only selected steps)
        selected_flow_list = [f for f in session.flows if f.get("name") not in session.excluded_flow_names]
        for flow_def in selected_flow_list:
            flow_name = flow_def.get("name") or ""
            if not flow_name:
                flows_failed.append(f"Skipped flow (no name): {flow_def}")
                continue
            steps = flow_def.get("steps") or []
            try:
                flow_result = await client.create_api_flow(flow_name)
                flow_id = _extract_flow_id(flow_result)
                if flow_id and steps:
                    flow_map = []
                    prev_api_id: str | None = None
                    for flow_order, step in enumerate(steps):
                        step_api_name = step.get("api_name") or ""
                        step_excluded = (flow_name, step_api_name) in session.excluded_flow_steps
                        if not step_api_name or step_api_name not in api_id_map or step_excluded:
                            continue
                        step_api_id = api_id_map[step_api_name]
                        step_api_def = api_def_map.get(step_api_name, {})
                        flow_map.append({
                            "child_id": step_api_id,
                            "flow_order": flow_order,
                            "parent_id": prev_api_id,
                            "params": _build_flow_params(step_api_id, step_api_def),
                        })
                        prev_api_id = step_api_id
                    if flow_map:
                        await client.create_api_flow_map(flow_id, flow_map)
                flows_created.append(flow_name)
            except Exception as exc:
                flows_failed.append(f"Failed to create flow '{flow_name}': {exc}")

    store.mark_completed(session_id)

    summary = {
        "sessionId": session_id,
        "status": "completed",
        "collectionName": collection_name,
        "collectionId": collection_id,
        "apisCreated": len(apis_created),
        "apisTotalScanned": len(session.apis),
        "apisFailed": len(apis_failed),
        "environmentsCreated": envs_created,
        "environmentsFailed": envs_failed,
        "flowsCreated": flows_created,
        "flowsFailed": flows_failed,
        "apis": apis_created,
    }
    if apis_failed:
        summary["apiErrors"] = apis_failed
    if envs_failed:
        summary["environmentErrors"] = envs_failed
    if flows_failed:
        summary["flowErrors"] = flows_failed

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

