"""LLM-guided UI automation setup — discovers top flows, generates scripts, routes to push."""

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

from mcp.types import TextContent, Tool

from maeris_mcp.auth.token_store import TokenStore
from maeris_mcp.generators.playwright_generator import generate_step_files
from maeris_mcp.scanners.code_reader import collect_scan_files, get_file_contents
from maeris_mcp.storage.execution_store import TestExecutionStore
from maeris_mcp.storage.test_store import TestGenerationStore


def setup_ui_automation_tool() -> Tool:
    """Return the tool definition for setup_ui_automation."""
    return Tool(
        name="setup_ui_automation",
        description=(
            "Sets up complete UI automation for a project in one guided flow. "
            "Scans the repository, identifies the 3-7 most critical user flows "
            "(authentication, core CRUD, checkout, etc.), generates Playwright test scripts "
            "for all of them, then offers to run & auto-fix locally or push directly to Maeris.\n\n"
            "This is NOT an exhaustive test generator — it focuses on the critical path flows "
            "that matter most to end users.\n\n"
            "Workflow (3 phases):\n"
            "  Phase 1 — Discover (calls 1..N): Call with target_path + include_content=true. "
            "Receive file batches. Identify the top 3-7 critical flows and generate test cases "
            "for each. Submit identified_flows (with test_cases inside) alongside each next offset.\n"
            "  Phase 2 — Generate (is_last=true): Tool flattens all flows to test_cases, calls "
            "playwright generator, writes all scripts to a temp directory (never the user's repo). "
            "Returns pendingDecision with two options.\n"
            "  Phase 3 — Route (action='skip_run'): Checks Maeris auth. If not logged in, "
            "instructs user to run 'maeris login'. If logged in, shows test summary and offers "
            "to push to Maeris via push_tests_to_maeris.\n\n"
            "CRITICAL: After Phase 2 completes, present the pendingDecision to the user and "
            "wait for their choice before calling any follow-up tool."
        ),
        inputSchema={
            "type": "object",
            "properties": {
                "target_path": {
                    "type": "string",
                    "description": "Absolute path to the repository root to scan. Defaults to server working directory.",
                },
                "session_id": {
                    "type": "string",
                    "description": "Existing session ID to continue paginating or to finalize/skip.",
                },
                "include_content": {
                    "type": "boolean",
                    "description": "Include file contents for LLM flow discovery. Default: false.",
                    "default": False,
                },
                "offset": {
                    "type": "integer",
                    "description": "Skip first N files when fetching content batches. Default: 0.",
                    "default": 0,
                },
                "limit": {
                    "type": "integer",
                    "description": "Max files per content batch (1-10). Default: 5.",
                    "default": 5,
                },
                "identified_flows": {
                    "type": "array",
                    "description": (
                        "Flows identified from analyzing the previous file batch. "
                        "Submit alongside the next offset. Each flow must contain "
                        "name, description, importance, and test_cases."
                    ),
                    "items": {
                        "type": "object",
                        "required": ["name", "test_cases"],
                        "properties": {
                            "name": {
                                "type": "string",
                                "description": "Short flow label, e.g. 'Login', 'Registration', 'Checkout'.",
                            },
                            "description": {
                                "type": "string",
                                "description": "One sentence — what the user does and the expected outcome.",
                            },
                            "importance": {
                                "type": "string",
                                "enum": ["critical", "high", "medium"],
                                "description": "Priority tier for this flow.",
                            },
                            "test_cases": {
                                "type": "array",
                                "description": "Full test cases in the standard Maeris TestCase format.",
                                "items": {"type": "object"},
                            },
                        },
                    },
                },
                "base_url": {
                    "type": "string",
                    "description": "Application base URL (e.g., 'https://light-dev.up.railway.app'). Required with is_last=true.",
                },
                "is_last": {
                    "type": "boolean",
                    "description": "Set true on the final scanning call to trigger script generation.",
                },
                "action": {
                    "type": "string",
                    "enum": ["skip_run"],
                    "description": "Set to 'skip_run' to skip test execution and go to auth check + push decision.",
                },
                "test_credentials": {
                    "type": "object",
                    "description": (
                        "Optional. User-provided credentials for any login/auth flows in the app. "
                        "These are used verbatim in generated test cases instead of invented placeholder values. "
                        "Example: {\"username\": \"user@example.com\", \"password\": \"mypassword\"}. "
                        "Can also include additional fields like role, token, or any app-specific auth data."
                    ),
                },
            },
        },
    )


def _get_project_paths(target_path: str) -> tuple[Path, str]:
    """Return base for relative paths and display name."""
    path = Path(target_path)
    project_root = path if path.is_dir() else path.parent
    base_for_rel = project_root.parent if project_root.parent != project_root else project_root
    display_name = project_root.name or str(project_root)
    return base_for_rel, display_name


def _ui_automation_analysis_prompt(credentials: dict | None = None) -> str:
    """Return the LLM analysis prompt for flow discovery."""
    if credentials:
        creds_lines = "\n".join(f"  {k}: {v}" for k, v in credentials.items())
        creds_section = (
            "\n=== TEST CREDENTIALS — USE THESE EXACT VALUES ===\n"
            "\n"
            "The user has provided the following credentials. "
            "USE THEM VERBATIM in any login/auth test steps. "
            "Never substitute, invent, or use placeholder values.\n"
            "\n"
            f"{creds_lines}\n"
            "\n"
            "In generated test step values, reference these credentials directly "
            "(e.g., page.fill('#email', 'user@example.com')).\n"
        )
    else:
        creds_section = (
            "\n=== TEST CREDENTIALS — NOT PROVIDED ===\n"
            "\n"
            "The user did NOT provide credentials. If you encounter a login/auth flow:\n"
            "- Do NOT invent fake credentials (test@example.com, password123, admin/admin, etc.)\n"
            "- Do NOT use variable names or placeholders (TEST_USERNAME, TEST_PASSWORD, etc.)\n"
            "- ASK the user for their actual credentials before generating auth test cases.\n"
            "- Every value in every step must be a concrete absolute value — never a variable.\n"
        )

    return creds_section + (
        "=== UI AUTOMATION SETUP — CRITICAL PATH DISCOVERY ===\n"
        "\n"
        "OVERARCHING PRINCIPLE: Your ONLY job is to create test cases exactly how a REAL USER would test. "
        "Map out EACH AND EVERY step required to reach the end goal — no shortcuts, no skipped steps. "
        "A real user lands on the starting page, clicks a link/button, waits for the page, fills fields "
        "one by one, clicks submit, and checks the result. Your test must capture EVERY action.\n"
        "\n"
        "You are performing an INITIAL UI automation setup for this project. "
        "Your goal is NOT to cover every edge case. Your goal is to identify "
        "the 3 to 7 most important user flows and generate solid baseline tests for each.\n"
        "\n"
        "=== FLOW SELECTION CRITERIA (PRIORITY ORDER) ===\n"
        "\n"
        "TIER 1 — ALWAYS INCLUDE (if present in this codebase):\n"
        "  1. Authentication flow (login, logout, session management)\n"
        "  2. Core registration or onboarding flow\n"
        "  3. The primary value-delivery action of the app\n"
        "     (e.g., place order, create project, send message, upload file)\n"
        "\n"
        "TIER 2 — INCLUDE IF PRESENT AND SIGNIFICANT:\n"
        "  4. User profile / account settings update\n"
        "  5. A key CRUD operation (create/read/update/delete of the main entity)\n"
        "  6. Payment or checkout flow (if present)\n"
        "  7. Search or filter on the main data view (if present)\n"
        "\n"
        "WHAT TO EXPLICITLY IGNORE:\n"
        "  - Help pages, FAQ pages, static content pages\n"
        "  - Admin/internal tools not visible to end users\n"
        "  - Obscure edge-case flows that affect < 5% of users\n"
        "  - Duplicate flows (do not list 'edit profile' and 'update avatar' separately\n"
        "    if they live on the same settings page)\n"
        "\n"
        "HARD CAP: Identify at most 7 flows total. Quality over quantity.\n"
        "\n"
        "=== TEST CASE GENERATION ===\n"
        "\n"
        "For each selected flow, generate EXACTLY:\n"
        "  - 1 POSITIVE test (happy path): Complete the flow successfully with valid data\n"
        "  - 1 NEGATIVE test (validation): Attempt the flow with invalid/missing data, "
        "verify error handling\n"
        "  - For complex flows with 3+ distinct steps (checkout, multi-step forms): add 1 more edge-case test\n"
        "\n"
        "Naming convention — prefix each test name with the flow name:\n"
        "  'Login - Happy Path: Valid credentials'\n"
        "  'Login - Negative: Empty email and password'\n"
        "  'Checkout - Happy Path: Complete purchase'\n"
        "  'Checkout - Edge Case: Apply discount code'\n"
        "\n"
        "=== MANDATORY SELECTOR RULES (same as generate_tests) ===\n"
        "\n"
        "1. Extract ALL selectors, URLs, and expected text VERBATIM from the source code. Never invent.\n"
        "2. Selector priority (read from source code in this order):\n"
        "   a. [data-testid=...] or [data-cy=...]\n"
        "   b. #id-attribute\n"
        "   c. [name=...]\n"
        "   d. button[type=submit], input[type=email]\n"
        "   e. [aria-label=...]\n"
        "   f. text=Exact Button Text\n"
        "3. Do NOT use placeholder credentials. If auth requires specific test data, "
        "use realistic example values.\n"
        "4. Every assertion 'expected' field = exact string copied from source code.\n"
        "5. EVERY test case MUST start with a go_to step to the base_url (the user's starting URL). "
        "Then use CLICK actions on links/buttons found in the source code to navigate to the target page. "
        "Do NOT go directly to deep URLs — click through the UI like a real user would.\n"
        "6. ONLY generate steps for elements you have ACTUALLY SEEN in the source code files. "
        "If you haven't read the component file for a page, do NOT guess its selectors. "
        "Common mistake: assuming a login form has '#email' and '#password' without reading the actual code.\n"
        "7. MAP THE REAL USER JOURNEY FROM BASE URL: Start at the base_url, then click through "
        "links/buttons to reach each target page. After each action (form submit, button click), "
        "check the source code to see where the app navigates (router.push, redirect, Link href, onSubmit handler). "
        "Assert the actual destination URL from code, not a guessed URL. Never jump directly to deep URLs.\n"
        "7b. REDIRECT AWARENESS: Check for SERVER-SIDE REDIRECTS (next.config.js redirects(), middleware.ts) "
        "and CLIENT-SIDE AUTH GUARDS (components in _app.js that call router.push('/login') when no auth session). "
        "If a route like /signup is redirected server-side (e.g. /signup → /) or client-side (auth guard → /login), "
        "do NOT navigate directly to it. Instead, go to an accessible page first (e.g. /login) and click a link "
        "to reach the target page. Always verify the URL after navigation to confirm you are on the expected page.\n"
        "8. EVERY MICRO-STEP COUNTS: Include EVERY action a real user performs — no shortcuts. "
        "If a user clicks a link, waits for a page, fills 3 fields, clicks submit — that's 5+ separate steps. "
        "Missing even ONE step (like a required field) will cause the test to fail.\n"
        "9. SELECTOR STABILITY: Always pick the MOST RESILIENT selector from the source code. "
        "Priority: data-testid > id > name > type attribute > aria-label > placeholder > visible text. "
        "NEVER use CSS classes (especially CSS modules), nested paths, or positional selectors.\n"
        "\n"
        "⚠️  CSS MODULES — CRITICAL RULE (most common mistake):\n"
        "   When source code shows className={styles.submitButton} or any className={styles.xxx},\n"
        "   this is a CSS Module. The actual DOM class will be HASHED (e.g. 'Form_submitButton__a1b2').\n"
        "   NEVER use 'button.submitButton', '.submitButton', or any className={styles.xxx} value.\n"
        "   CORRECT alternatives: button[type='submit'], text=Submit, [aria-label='...'].\n"
        "   Apply the same rule to ALL className={styles.xxx} — use type/id/name/placeholder/text instead.\n"
        "\n"
        "=== OUTPUT FORMAT ===\n"
        "\n"
        "Submit your findings as the 'identified_flows' parameter alongside the next offset call.\n"
        "Structure:\n"
        "[\n"
        "  {\n"
        "    \"name\": \"Login\",\n"
        "    \"description\": \"User authenticates with email and password\",\n"
        "    \"importance\": \"critical\",\n"
        "    \"test_cases\": [\n"
        "      { \"name\": \"Login - Happy Path: Valid credentials\", \"steps\": [...], \"assertions\": [...] },\n"
        "      { \"name\": \"Login - Negative: Empty email and password\", \"steps\": [...], \"assertions\": [...] }\n"
        "    ]\n"
        "  }\n"
        "]\n"
        "\n"
        "=== PROCESS ===\n"
        "1. Read each file batch and build a mental inventory of flows present.\n"
        "2. Apply the tier criteria above to select only the top 3-7.\n"
        "3. For each selected flow, generate test cases by reading selectors and strings from code.\n"
        "4. Submit identified_flows (with test_cases inside) alongside the next offset call.\n"
        "5. When all files are read, call with is_last=true and base_url to finalize.\n"
    )


def _identified_flows_schema() -> dict[str, Any]:
    """Return the expected JSON schema for identified_flows."""
    return {
        "type": "array",
        "description": "Top 3-7 critical user flows with test cases for each",
        "maxItems": 7,
        "items": {
            "type": "object",
            "required": ["name", "test_cases"],
            "properties": {
                "name": {"type": "string", "example": "Login"},
                "description": {"type": "string"},
                "importance": {"type": "string", "enum": ["critical", "high", "medium"]},
                "test_cases": {
                    "type": "array",
                    "items": {
                        "type": "object",
                        "required": ["name", "steps", "assertions"],
                        "properties": {
                            "name": {"type": "string"},
                            "description": {"type": "string"},
                            "priority": {"type": "string"},
                            "tags": {"type": "array", "items": {"type": "string"}},
                            "steps": {"type": "array", "items": {"type": "object"}},
                            "assertions": {"type": "array", "items": {"type": "object"}},
                        },
                    },
                },
            },
        },
    }


async def handle_setup_ui_automation(
    test_store: TestGenerationStore,
    exec_store: TestExecutionStore,
    arguments: dict,
) -> list[TextContent]:
    """Handle all phases of the setup_ui_automation tool call."""

    def _resp(data: dict) -> list[TextContent]:
        return [TextContent(type="text", text=json.dumps(data, indent=2))]

    def _err(msg: str) -> list[TextContent]:
        return [TextContent(type="text", text=json.dumps({"error": msg}, indent=2))]

    # -- Parse arguments (support both snake_case and camelCase keys) --
    target_path = arguments.get("target_path") or arguments.get("targetPath") or os.getcwd()
    session_id = arguments.get("session_id") or arguments.get("sessionId")
    include_content = bool(
        arguments.get("include_content") or arguments.get("includeContent", False)
    )
    offset = int(arguments.get("offset", 0))
    limit = min(int(arguments.get("limit", 5)), 10)
    identified_flows_raw = (
        arguments.get("identified_flows") or arguments.get("identifiedFlows") or []
    )
    base_url = arguments.get("base_url") or arguments.get("baseUrl")
    is_last = bool(arguments.get("is_last") or arguments.get("isLast"))
    action = arguments.get("action")
    test_credentials = arguments.get("test_credentials") or arguments.get("testCredentials")

    # ── Phase 3: skip_run — auth check + push decision ─────────────────────────
    if action == "skip_run":
        if not session_id:
            return _err("session_id is required for action='skip_run'")
        session = test_store.get_session(session_id)
        if not session:
            return _err(f"session not found: {session_id}")

        flow_summary = [
            {
                "name": f.get("name"),
                "importance": f.get("importance", "medium"),
                "testCount": len(f.get("test_cases", [])),
            }
            for f in session.identified_flows
        ]
        total_tests = len(session.test_cases)

        ts = TokenStore()
        if not ts.is_authenticated():
            return _resp(
                {
                    "status": "auth_required",
                    "sessionId": session_id,
                    "flowSummary": flow_summary,
                    "totalTests": total_tests,
                    "outputDir": session.output_dir,
                    "message": (
                        "You are not logged in to Maeris. "
                        "Run 'maeris login' in your terminal, then call this tool again "
                        "with the same session_id and action='skip_run' to continue."
                    ),
                }
            )

        creds = ts.get_token_data() or {}
        return _resp(
            {
                "status": "authenticated",
                "sessionId": session_id,
                "user": creds.get("email"),
                "appName": creds.get("app_name"),
                "flowSummary": flow_summary,
                "totalTests": total_tests,
                "outputDir": session.output_dir,
                "pendingDecision": {
                    "question": (
                        f"Your automation suite is ready! "
                        f"Would you like to push {total_tests} test case(s) to Maeris?"
                    ),
                    "options": {
                        "1_push": {
                            "label": "Yes — set up my automation in Maeris",
                            "description": "Push all generated test cases to Maeris via push_tests_to_maeris.",
                            "tool": "push_tests_to_maeris",
                            "arguments": {"session_id": session_id},
                            "note": "Ask the user which folder to save them in before calling push_tests_to_maeris.",
                        },
                        "2_skip": {
                            "label": "No — keep scripts locally for now",
                            "description": "Exit without pushing to Maeris. Scripts are already saved on disk.",
                            "tool": None,
                            "arguments": {},
                        },
                    },
                },
            }
        )

    # ── Phase 2: is_last=true — generate scripts ────────────────────────────────
    if is_last:
        if not session_id:
            return _err("session_id is required when is_last=true")
        session = test_store.get_session(session_id)
        if not session:
            return _err(f"session not found: {session_id}")

        if base_url:
            test_store.update_base_url(session_id, base_url)
            session = test_store.get_session(session_id)

        if not session.base_url:
            return _err(
                "base_url is required when is_last=true. "
                "Provide the application base URL (e.g., 'https://light-dev.up.railway.app')."
            )

        if not session.test_cases:
            return _err(
                "No test cases were collected. "
                "Submit identified_flows with test_cases in each batch before calling with is_last=true."
            )

        # Use temp dir so we never write into the user's repo
        output_path = os.path.join(
            tempfile.gettempdir(), "maeris_tests", session_id, "ui_automation_setup"
        )

        try:
            result = generate_step_files(
                test_cases=session.test_cases,
                flow_name="UI Automation Setup",
                base_url=session.base_url,
                output_dir=output_path,
            )
        except Exception as exc:
            return _err(f"Script generation failed: {exc}")

        test_store.finalize_session(session_id, output_path)

        flow_summary = [
            {
                "name": f.get("name"),
                "importance": f.get("importance", "medium"),
                "description": f.get("description", ""),
                "testCount": len(f.get("test_cases", [])),
            }
            for f in session.identified_flows
        ]

        return _resp(
            {
                "sessionId": session_id,
                "status": "generated",
                "outputDirectory": output_path,
                "filesGenerated": result.get("files_generated", []),
                "totalTestCases": len(session.test_cases),
                "flows": flow_summary,
                "summary": (
                    f"Generated {result.get('test_files_count', len(session.test_cases))} test file(s) "
                    f"across {len(session.identified_flows)} flow(s) in {output_path}"
                ),
                "pendingDecision": {
                    "question": "Test scripts have been generated successfully! What would you like to do next?",
                    "options": {
                        "1_run_and_fix": {
                            "label": "Run & auto-fix tests",
                            "description": (
                                "Execute each test in a browser, automatically fix failures, "
                                "and store only the passing scripts."
                            ),
                            "tool": "run_and_fix_tests",
                            "arguments": {"session_id": session_id},
                        },
                        "2_skip_run": {
                            "label": "Skip execution — proceed to next step",
                            "description": (
                                "Skip running the tests locally. "
                                "Check Maeris auth status and prepare to push test cases."
                            ),
                            "tool": "setup_ui_automation",
                            "arguments": {
                                "session_id": session_id,
                                "action": "skip_run",
                            },
                        },
                    },
                },
            }
        )

    # ── Pre-scan: ask for URL and credentials if not yet provided ───────────────
    if not session_id and not base_url:
        return _resp(
            {
                "status": "needs_info",
                "message": "Before scanning, I need a couple of details to generate accurate tests.",
                "pendingDecision": {
                    "question": (
                        "Please ask the user:\n"
                        "1. What URL is your application running at? "
                        "(e.g., https://light-dev.up.railway.app or https://myapp.example.com)\n"
                        "2. Does your app have a login or authentication flow? "
                        "If yes, what credentials should be used for the test cases? "
                        "(username/email and password)"
                    ),
                    "nextCall": {
                        "tool": "setup_ui_automation",
                        "requiredArguments": {
                            "target_path": target_path,
                            "base_url": "<fill in from user — e.g. https://light-dev.up.railway.app>",
                        },
                        "optionalArguments": {
                            "test_credentials": {
                                "description": "Provide if the app has login. Example: {\"username\": \"user@example.com\", \"password\": \"mypassword\"}",
                            },
                        },
                    },
                },
            }
        )

    # ── Phase 1: file scanning batch ────────────────────────────────────────────
    if not session_id:
        scan_root = os.path.abspath(target_path)
        if not os.path.exists(scan_root):
            return _err(f"path does not exist: {target_path}")

        base_for_rel, _ = _get_project_paths(scan_root)
        files_abs, _info = collect_scan_files(scan_root)
        file_list = [os.path.relpath(p, str(base_for_rel)) for p in files_abs]

        session_id = test_store.create_session(
            flow_description="ui_automation_setup",
            target_path=scan_root,
            scan_root=scan_root,
            base_for_rel=str(base_for_rel),
            file_list=file_list,
            base_url=base_url,
        )
    else:
        session = test_store.get_session(session_id)
        if not session:
            return _err(f"session not found: {session_id}")
        file_list = session.file_list
        base_for_rel = Path(session.base_for_rel)
        scan_root = session.scan_root

    # Accumulate identified flows submitted from the previous batch
    flow_stats: dict[str, int] = {}
    if identified_flows_raw and isinstance(identified_flows_raw, list):
        flow_stats = test_store.add_identified_flows(session_id, identified_flows_raw)

    if base_url:
        test_store.update_base_url(session_id, base_url)

    if test_credentials and isinstance(test_credentials, dict):
        test_store.set_credentials(session_id, test_credentials)

    session = test_store.get_session(session_id)
    total_files = len(file_list)

    response: dict[str, Any] = {
        "sessionId": session_id,
        "status": "analyzing",
        "filesTotal": total_files,
        "analysisInstructions": _ui_automation_analysis_prompt(credentials=session.test_credentials),
        "identifiedFlowsSchema": _identified_flows_schema(),
    }

    if flow_stats:
        response["flowsSubmitted"] = flow_stats

    if include_content:
        paginated_rel = file_list[offset: offset + limit]
        paginated_abs = [str(Path(session.base_for_rel) / p) for p in paginated_rel]
        contents = get_file_contents(paginated_abs)

        response["pagination"] = {
            "offset": offset,
            "limit": limit,
            "returned": len(paginated_rel),
            "total": total_files,
            "hasMore": offset + limit < total_files,
            "nextOffset": offset + limit if offset + limit < total_files else None,
        }
        response["fileContents"] = {
            rel: contents.get(abs_path, "")
            for rel, abs_path in zip(paginated_rel, paginated_abs)
        }
    else:
        response["nextStep"] = (
            "Call again with include_content=true and offset=0 to begin scanning. "
            "Identify the top 3-7 critical user flows and generate test cases for each. "
            "Submit identified_flows (with test_cases inside each flow) alongside each next offset. "
            "When done scanning all files, call with is_last=true and base_url to generate scripts."
        )

    return _resp(response)
