"""
Agent 6: QA Migration Agent — MCP Server

Migrates existing Playwright test suites (plain JS/TS or Playwright+Cucumber JS/TS)
to the Helix-QA agent-compatible structure with full self-healing support.

Pipeline per source file:
  JS → TS conversion  →  locator modernisation (CSS/XPath → getByRole)
  →  healer injection (TimingHealer + VisualIntentChecker on ALL interactions)
  →  import path fixing  →  Helix-QA disk write  →  MIGRATION-REPORT.md

Tools exposed:
  detect_framework   — auto-detect source framework type and language
  preview_migration  — dry-run: enumerate what would be written / conflicts
  migrate_framework  — full migration with per-file conflict handling
"""
from __future__ import annotations
import asyncio
import json
import sys
from pathlib import Path

from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp import types

from stlc_agents.agent_migration.detector import detect_framework as _detect
from stlc_agents.agent_migration.mapper import map_source_files
from stlc_agents.agent_migration._migrate import (
    run_migration,
    _load_migration_manifest,
)

app = Server("qa-migration")


# ---------------------------------------------------------------------------
# Tool definitions
# ---------------------------------------------------------------------------

@app.list_tools()
async def list_tools() -> list[types.Tool]:
    return [
        types.Tool(
            name="detect_framework",
            description=(
                "Auto-detect the Playwright framework type from a source test directory. "
                "Returns: framework ('playwright-bdd-ts' | 'playwright-bdd-js' | "
                "'playwright-ts' | 'playwright-js'), language, bdd flag, config files "
                "found, and file counts (.ts / .js / .feature)."
            ),
            inputSchema={
                "type": "object",
                "properties": {
                    "source_dir": {
                        "type": "string",
                        "description": "Absolute path to the source test project directory.",
                    },
                },
                "required": ["source_dir"],
            },
        ),
        types.Tool(
            name="preview_migration",
            description=(
                "Dry-run: enumerate all files that would be migrated, their Helix-QA "
                "target paths, roles, and any conflicts (target already exists). "
                "Nothing is written to disk. Use this before migrate_framework to "
                "review the migration plan and spot any surprises.\n\n"
                "In-place safety (source_dir == helix_root): auto-enables "
                "preserve_tree and consults `.helix-migration.json` (or "
                "MIGRATION-REPORT.md as fallback) so files already produced by "
                "a previous migration are skipped instead of being re-fed "
                "through the pipeline."
            ),
            inputSchema={
                "type": "object",
                "properties": {
                    "source_dir": {
                        "type": "string",
                        "description": "Absolute path to source test project directory.",
                    },
                    "helix_root": {
                        "type": "string",
                        "description": "Absolute path to the Helix-QA root directory.",
                    },
                    "preserve_tree": {
                        "type": "boolean",
                        "description": (
                            "Preserve source subfolder grouping under standard Helix-QA "
                            "dirs (default false — flatten). Auto-enabled when "
                            "source_dir == helix_root."
                        ),
                    },
                },
                "required": ["source_dir", "helix_root"],
            },
        ),
        types.Tool(
            name="migrate_framework",
            description=(
                "Migrate an existing Playwright test framework to the Helix-QA "
                "agent-compatible structure.\n\n"
                "Transformations applied:\n"
                "  • JS → TS (CommonJS require → ES import, module.exports → export)\n"
                "  • Locator modernisation: [data-testid] → getByTestId, text= → getByText, "
                "[aria-label] → getByLabel, [placeholder] → getByPlaceholder, "
                "role=X[name=Y] → getByRole. Complex XPath/CSS flagged with TODO.\n"
                "  • Healer injection on ALL interactions: page.click → "
                "_healer.clickWithHealing, page.fill → _healer.fillWithHealing, "
                "page.goto wrapped with TimingHealer, expect.toBeVisible wrapped "
                "with VisualIntentChecker. Applies to page objects AND step definitions.\n"
                "  • Import paths updated to Helix structure "
                "(@utils/locators/*, @hooks/pageFixture, etc.).\n"
                "  • playwright.config merged into Helix playwright.config.ts.\n"
                "  • cucumber.config profiles merged into src/config/cucumber.config.ts.\n"
                "  • Per-app .env.<APP> files folded into root .env.example then removed.\n"
                "  • MIGRATION-REPORT.md + .helix-migration.json written to helix_root.\n\n"
                "Idempotency: every run writes `.helix-migration.json` listing the "
                "files produced. An in-place re-migration (source_dir == helix_root) "
                "loads that manifest and skips already-migrated files during source "
                "scanning, so re-runs never produce mangled duplicate outputs like "
                "`base.page.ts` from `BasePage.ts` or `locators-scweb-locators.ts` "
                "from previously-emitted locator aggregates. When no manifest "
                "exists, the in-place run is treated as fresh source.\n\n"
                "Supports: plain Playwright JS/TS, Playwright+Cucumber JS/TS."
            ),
            inputSchema={
                "type": "object",
                "properties": {
                    "source_dir": {
                        "type": "string",
                        "description": "Absolute path to source test project directory.",
                    },
                    "helix_root": {
                        "type": "string",
                        "description": "Absolute path to the Helix-QA root directory.",
                    },
                    "conflict_strategy": {
                        "type": "string",
                        "enum": ["interactive", "overwrite", "skip"],
                        "description": (
                            "'overwrite' — replace existing Helix files. "
                            "'skip' — keep existing files untouched. "
                            "'interactive' (default) — write and report conflicts "
                            "for user review."
                        ),
                    },
                    "preserve_tree": {
                        "type": "boolean",
                        "description": (
                            "Preserve source subfolder grouping under standard "
                            "Helix-QA dirs (default false — flatten). Auto-enabled "
                            "when source_dir == helix_root so an in-place re-migration "
                            "respects the existing tree layout."
                        ),
                    },
                    "integration": {
                        "type": "string",
                        "enum": ["ado", "jira", "both"],
                        "description": (
                            "Which agent integration assets to install. "
                            "'both' (default) installs every agent reachable from "
                            "the migrated tree."
                        ),
                    },
                    "features": {
                        "type": "array",
                        "items": {"type": "string"},
                        "description": (
                            "Optional capability flags. Recognised: 'vrt' for "
                            "visual regression testing scaffolding."
                        ),
                    },
                    "dry_run": {
                        "type": "boolean",
                        "description": (
                            "When true, return the migration plan without writing any files."
                        ),
                    },
                },
                "required": ["source_dir", "helix_root"],
            },
        ),
    ]


# ---------------------------------------------------------------------------
# Tool dispatch
# ---------------------------------------------------------------------------

@app.call_tool()
async def call_tool(name: str, arguments: dict) -> list[types.TextContent]:
    try:
        result = await _dispatch(name, arguments)
        return [types.TextContent(type="text", text=json.dumps(result, indent=2, ensure_ascii=False))]
    except Exception as exc:
        return [types.TextContent(
            type="text",
            text=json.dumps({"error": str(exc), "tool": name}, indent=2),
        )]


async def _dispatch(name: str, args: dict) -> dict:
    if name == "detect_framework":
        return _detect(args["source_dir"])

    if name == "preview_migration":
        return _preview(
            args["source_dir"],
            args["helix_root"],
            bool(args.get("preserve_tree", False)),
        )

    if name == "migrate_framework":
        features = args.get("features") or []
        return _migrate(
            args["source_dir"],
            args["helix_root"],
            args.get("conflict_strategy", "interactive"),
            bool(args.get("dry_run", False)),
            bool(args.get("preserve_tree", False)),
            args.get("integration", "both"),
            set(features) if isinstance(features, list) else set(),
        )

    return {"error": f"Unknown tool: {name}"}


# ---------------------------------------------------------------------------
# Tool implementations
# ---------------------------------------------------------------------------

def _resolve_in_place_safety(
    source_dir: str,
    helix_root: str,
    preserve_tree: bool,
) -> tuple[bool, set[str], dict]:
    """
    Shared logic for the MCP tools: when source == helix, auto-enable
    preserve_tree and load the migration manifest so previously-migrated
    files are skipped during source scan. Returns the effective preserve_tree
    flag, the set of paths to exclude, and a debug `in_place` block the
    caller can include in its response so users can see what was applied.
    """
    source_path = Path(source_dir).resolve()
    helix_path  = Path(helix_root).resolve()

    in_place_info: dict = {
        "in_place":           False,
        "preserve_tree_auto": False,
        "manifest_source":    None,
        "skip_count":         0,
    }
    prior_outputs: set[str] = set()

    if source_path != helix_path:
        return preserve_tree, prior_outputs, in_place_info

    in_place_info["in_place"] = True
    if not preserve_tree:
        preserve_tree = True
        in_place_info["preserve_tree_auto"] = True

    prior_outputs = _load_migration_manifest(helix_path)
    if prior_outputs:
        in_place_info["manifest_source"] = ".helix-migration.json"

    in_place_info["skip_count"] = len(prior_outputs)
    return preserve_tree, prior_outputs, in_place_info


def _preview(source_dir: str, helix_root: str, preserve_tree: bool = False) -> dict:
    detection = _detect(source_dir)
    if "error" in detection:
        return detection

    preserve_tree, prior_outputs, in_place_info = _resolve_in_place_safety(
        source_dir, helix_root, preserve_tree,
    )

    files    = map_source_files(
        source_dir,
        preserve_tree=preserve_tree,
        exclude=prior_outputs,
    )
    mappable = [f for f in files if f["target"] is not None]
    unknown  = [f for f in files if f["role"] == "unknown"]
    root     = Path(helix_root)

    conflicts = [
        {"source": f["relative"], "target": f["target"]}
        for f in mappable
        if f["target"] and (root / f["target"]).exists()
    ]

    return {
        "framework":     detection.get("framework", "unknown"),
        "language":      detection.get("language", "unknown"),
        "bdd":           detection.get("bdd", False),
        "file_counts":   detection.get("file_counts", {}),
        "total_files":   len(files),
        "mappable":      len(mappable),
        "unknown":       len(unknown),
        "conflicts":     len(conflicts),
        "conflict_list": conflicts,
        "in_place":      in_place_info,
        "preserve_tree": preserve_tree,
        "file_plan": [
            {
                "source":         f["relative"],
                "target":         f["target"],
                "role":           f["role"],
                "needs_js_to_ts": f.get("needs_js_to_ts", False),
            }
            for f in mappable
        ],
        "unknown_files": [f["relative"] for f in unknown],
    }


def _migrate(
    source_dir: str,
    helix_root: str,
    conflict_strategy: str,
    dry_run: bool,
    preserve_tree: bool = False,
    integration: str = "both",
    features: set[str] | None = None,
) -> dict:
    detection = _detect(source_dir)
    if "error" in detection:
        return detection

    preserve_tree, prior_outputs, in_place_info = _resolve_in_place_safety(
        source_dir, helix_root, preserve_tree,
    )

    framework = detection.get("framework", "unknown")
    files     = map_source_files(
        source_dir,
        preserve_tree=preserve_tree,
        exclude=prior_outputs,
    )
    mappable  = [f for f in files if f["target"] is not None]

    if dry_run:
        return {
            "dry_run":       True,
            "framework":     framework,
            "in_place":      in_place_info,
            "preserve_tree": preserve_tree,
            "total":         len(mappable),
            "would_migrate": [
                {"source": f["relative"], "target": f["target"], "role": f["role"]}
                for f in mappable
            ],
        }

    result = run_migration(
        source_dir=source_dir,
        helix_root=helix_root,
        file_mappings=mappable,
        conflict_strategy=conflict_strategy,
        framework=framework,
        integration=integration,
        features=features or set(),
        prior_outputs=prior_outputs,
    )

    # Summarise for the MCP response
    result["summary"] = {
        "framework":   framework,
        "written":     len(result["written"]),
        "skipped":     len(result["skipped"]),
        "conflicts":   len(result["conflicts"]),
        "todos":       result["todo_count"],
        "report_path": result.get("report_path"),
    }
    result["in_place"]      = in_place_info
    result["preserve_tree"] = preserve_tree
    return result


# ---------------------------------------------------------------------------
# Entry point
# ---------------------------------------------------------------------------

def main() -> None:
    asyncio.run(stdio_server(app))


if __name__ == "__main__":
    main()
