"""
stlc-migrate CLI — Migrate a Playwright test suite to Helix-QA structure.

Usage:
    stlc-migrate --source <dir> --helix <dir> [options]

Options:
    --conflict  interactive | overwrite | skip   (default: interactive)
    --dry-run   Preview migration without writing any files
    --json      Output results as JSON
"""
from __future__ import annotations
import argparse
import json
import sys
from pathlib import Path

from .detector import detect_framework
from .mapper import map_source_files
from ._migrate import (
    run_migration,
    _load_migration_manifest,
)
from ._vrt import parse_features, autodetect_vrt


def main() -> None:
    parser = argparse.ArgumentParser(
        prog="stlc-migrate",
        description="Migrate a Playwright test suite to the Helix-QA agent-compatible structure.",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog=__doc__,
    )
    parser.add_argument(
        "--source",
        required=True,
        metavar="DIR",
        help="Source test project directory (plain Playwright JS/TS or Playwright+Cucumber JS/TS).",
    )
    parser.add_argument(
        "--helix",
        required=True,
        metavar="DIR",
        help="Helix-QA root directory (output). Must already exist unless --dry-run.",
    )
    parser.add_argument(
        "--conflict",
        choices=["interactive", "overwrite", "skip"],
        default="interactive",
        metavar="STRATEGY",
        help=(
            "Conflict resolution when a target file already exists: "
            "'interactive' shows a report (default), "
            "'overwrite' replaces existing files, "
            "'skip' keeps existing files."
        ),
    )
    parser.add_argument(
        "--integration",
        choices=["ado", "jira", "both"],
        default="both",
        metavar="WHICH",
        help=(
            "Which QA-STLC agent integration to install skills + .mcp.json for: "
            "'ado' (qa-test-case-manager workflow), 'jira' (qa-jira-manager workflow), "
            "or 'both' (default — every agent reachable from the migrated tree)."
        ),
    )
    parser.add_argument(
        "--features",
        default="",
        metavar="CSV",
        help=(
            "Comma-separated optional capabilities to scaffold. Recognised: "
            "'vrt' (visual regression testing — drops src/helper/vrt/* + step defs + "
            "npm scripts + deps). Auto-enabled when the source project already "
            "uses VRT (VRT_MODE env or vrt:* npm scripts)."
        ),
    )
    parser.add_argument(
        "--preserve-tree",
        action="store_true",
        dest="preserve_tree",
        help=(
            "Preserve the source's subfolder grouping inside the standard "
            "Helix-QA top-level dirs (src/test/features/<sub>/..., "
            "src/test/steps/<sub>/..., src/pages/<sub>/..., "
            "src/locators/<sub>/...). Subdirectory names are kebab-cased "
            "for consistency. Default flattens all files into the top-level dirs."
        ),
    )
    parser.add_argument(
        "--dry-run",
        action="store_true",
        dest="dry_run",
        help="Preview migration — show what would be written without touching any files.",
    )
    parser.add_argument(
        "--json",
        action="store_true",
        dest="json_output",
        help="Write results as JSON to stdout.",
    )

    args = parser.parse_args()

    source_path = Path(args.source).resolve()
    helix_path  = Path(args.helix).resolve()

    # ── Validation ──────────────────────────────────────────────────────────
    if not source_path.exists():
        _err(f"Source directory does not exist: {source_path}")

    if not helix_path.exists() and not args.dry_run:
        _err(
            f"Helix directory does not exist: {helix_path}\n"
            "  Create it first or pass --dry-run to preview without writing."
        )

    # ── In-place safety: auto-preserve-tree when source == helix ────────────
    # Running the migration in-place on an already-organised tree would
    # otherwise produce duplicates: the existing `src/pages/scweb/foo.page.ts`
    # stays put under `--conflict skip`, AND a new flat `src/pages/foo.page.ts`
    # gets written from the same source. Detect this and force preserve-tree
    # so the existing layout is honoured.
    if source_path == helix_path and not args.preserve_tree:
        args.preserve_tree = True
        _info(
            "  In-place migration detected (source == helix); "
            "auto-enabling --preserve-tree to avoid duplicating files into "
            "flat paths alongside the existing subdirectory layout."
        )

    # ── Detection ───────────────────────────────────────────────────────────
    _info(f"Scanning source: {source_path}")
    detection = detect_framework(str(source_path))

    if "error" in detection:
        _err(detection["error"])

    fw = detection["framework"]
    fc = detection["file_counts"]
    _info(
        f"  Framework : {fw}\n"
        f"  Language  : {detection['language']}  |  BDD: {detection['bdd']}\n"
        f"  Files     : {fc['ts']} .ts   {fc['js']} .js   {fc['feature']} .feature"
    )

    # ── Manifest-based exclusion (idempotent in-place re-runs) ──────────────
    # When source == helix, the existing tree contains files written by a
    # previous migration run. Re-feeding those through the mapper produces
    # mangled duplicates (e.g. `BasePage.ts` → `base.page.ts`,
    # `src/locators/scweb/foo.locators.ts` → `locators-scweb-foo.locators.ts`).
    # Load `.helix-migration.json` and skip those paths during source scan.
    # If no manifest exists, treat the tree as fresh source even when an old
    # MIGRATION-REPORT.md is still present after a manual reset.
    prior_outputs: set[str] = set()
    if source_path == helix_path:
        prior_outputs = _load_migration_manifest(helix_path)
        if prior_outputs:
            _info(
                f"  Manifest  : skipping {len(prior_outputs)} files listed as "
                f"prior migration outputs (.helix-migration.json)"
            )

    # ── File mapping ────────────────────────────────────────────────────────
    files    = map_source_files(
        str(source_path),
        preserve_tree=args.preserve_tree,
        exclude=prior_outputs,
    )
    if args.preserve_tree:
        _info("  Layout    : preserving source subfolder grouping (--preserve-tree)")
    mappable = [f for f in files if f["target"] is not None]
    # role="unknown" files are raw-copied at their original path. We still list
    # them so the user can see what got copied without classification.
    unknown  = [f for f in files if f["role"] == "unknown"]

    _info(f"  Mappable  : {len(mappable)} files   Raw-copied (unclassified): {len(unknown)}")

    # ── Dry-run ─────────────────────────────────────────────────────────────
    if args.dry_run:
        _print_dry_run(mappable, unknown, helix_path, args.json_output)
        return

    # ── Conflict pre-check ──────────────────────────────────────────────────
    pre_conflicts = [
        f for f in mappable if f["target"] and (helix_path / f["target"]).exists()
    ]
    if pre_conflicts and args.conflict == "interactive":
        _info(
            f"\n  ⚠  {len(pre_conflicts)} file(s) already exist in Helix. "
            "They will be overwritten and listed in MIGRATION-REPORT.md.\n"
            "  Use --conflict skip to keep existing files, or --conflict overwrite to suppress this notice."
        )

    # ── Features (explicit CSV ∪ source auto-detect) ────────────────────────
    features = parse_features(args.features)
    if autodetect_vrt(source_path):
        if "vrt" not in features:
            _info("  Auto-detected VRT usage in source — enabling --features=vrt")
        features.add("vrt")
    if features:
        _info(f"  Features  : {','.join(sorted(features))}")

    # ── Migration ───────────────────────────────────────────────────────────
    _info("\nRunning migration ...")
    result = run_migration(
        source_dir=str(source_path),
        helix_root=str(helix_path),
        file_mappings=mappable,
        conflict_strategy=args.conflict,
        framework=fw,
        integration=args.integration,
        features=features,
        prior_outputs=prior_outputs,
    )

    if args.json_output:
        print(json.dumps(result, indent=2))
        return

    # ── Human-readable summary ──────────────────────────────────────────────
    print()
    print("Migration complete")
    print(f"  Written   : {len(result['written'])}")
    print(f"  Skipped   : {len(result['skipped'])}")
    print(f"  Conflicts : {len(result['conflicts'])}")
    print(f"  TODOs     : {result['todo_count']}")
    if result.get("removed_orphans"):
        print(f"  Removed   : {len(result['removed_orphans'])} orphan file(s) from role dirs")

    if result.get("todos"):
        print("\nTODOs:")
        for t in result["todos"][:10]:
            print(f"  • {t}")
        if len(result["todos"]) > 10:
            print(f"  … and {len(result['todos']) - 10} more (see MIGRATION-REPORT.md)")

    if result.get("report_path"):
        print(f"\nReport    : {result['report_path']}")


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

def _print_dry_run(
    mappable: list[dict],
    unknown: list[dict],
    helix_path: Path,
    as_json: bool,
) -> None:
    plan = [
        {
            "source":  f["relative"],
            "target":  f["target"],
            "role":    f["role"],
            "js→ts":   f.get("needs_js_to_ts", False),
            "conflict": (helix_path / f["target"]).exists() if f["target"] else False,
        }
        for f in mappable
    ]
    if as_json:
        print(json.dumps({"dry_run": True, "plan": plan, "unknown": [f["relative"] for f in unknown]}, indent=2))
        return

    print("\n[DRY RUN] Files that would be migrated:")
    print(f"  {'Source':<45}  {'Target':<45}  Role")
    print(f"  {'-'*45}  {'-'*45}  ----")
    for f in plan:
        conflict_flag = "  ⚠ CONFLICT" if f["conflict"] else ""
        js_ts_flag    = " (JS→TS)" if f["js→ts"] else ""
        print(f"  {f['source']:<45}  {f['target']:<45}  {f['role']}{js_ts_flag}{conflict_flag}")

    if unknown:
        print(f"\n[DRY RUN] {len(unknown)} file(s) raw-copied with unclassified role:")
        for f in unknown[:5]:
            print(f"  {f['relative']}")
        if len(unknown) > 5:
            print(f"  … and {len(unknown) - 5} more")


def _info(msg: str) -> None:
    print(msg, file=sys.stdout)


def _err(msg: str) -> None:
    print(f"Error: {msg}", file=sys.stderr)
    sys.exit(1)


if __name__ == "__main__":
    main()
