"""Repair commands: detect drift and AI-propose fixes.

All repair commands follow the same two-phase pattern:
  Phase 1 (deterministic): drift_detector detects what changed.
  Phase 2 (AI-assisted):   repair_engine proposes and applies fixes.

The two phases are NEVER mixed: detection logic contains no AI calls.
"""

from __future__ import annotations

import asyncio
import json
import os
import sys
from pathlib import Path

import click

from ._core import repair_group
from ._utils import _spinner, _repo_config_dir, _next_steps


# ---------------------------------------------------------------------------
# Shared helpers
# ---------------------------------------------------------------------------


def _auth_check(config_dir: Path) -> None:
    from maeris_mcp.auth.token_store import TokenStore
    ts = TokenStore(config_dir=config_dir)
    if not ts.is_authenticated():
        click.secho("✗ Not authenticated. Run 'maeris auth login' first.", fg="red")
        sys.exit(1)
    if ts.is_token_expired():
        click.secho("✗ Token expired. Run 'maeris auth login' to re-authenticate.", fg="red")
        sys.exit(1)


def _print_report_summary(report, output_json: bool) -> None:
    """Print a drift report summary to the terminal or as JSON."""
    if output_json:
        click.echo(json.dumps(report.to_dict(), indent=2))
    else:
        if not report.has_drift:
            click.secho(f"  ✓ No drift detected in domain '{report.domain}'.", fg="green")
        else:
            click.secho(
                f"  ✗ {len(report.issues)} issue(s) found:", fg="red", bold=True
            )
            for issue in report.issues:
                icon = "●" if issue.severity.value == "high" else "○"
                click.echo(
                    f"    {icon} [{issue.severity.value}] {issue.description}"
                )


def _print_repair_result(result, output_json: bool) -> None:
    if output_json:
        click.echo(json.dumps(result.to_dict(), indent=2))
    else:
        click.echo(
            f"\n  Repair complete — "
            f"fixed: {result.fixed}  skipped: {result.skipped}  failed: {result.failed}"
        )


# ---------------------------------------------------------------------------
# Shared CLI options (applied via decorators on each command)
# ---------------------------------------------------------------------------

_source_option = click.option(
    "--source", "-s", default=None,
    help="Source directory to scan (default: cwd).",
)
_model_option = click.option(
    "--model", "-m", default="claude-sonnet-4-6", show_default=True,
    help="AI model for repair suggestions.",
)
_dry_run_option = click.option(
    "--dry-run", "dry_run", is_flag=True, default=False,
    help="Show proposed changes without applying them.",
)
_yes_option = click.option(
    "--yes", "-y", "auto_yes", is_flag=True, default=False,
    help="Auto-approve all suggestions without prompting.",
)
_json_option = click.option(
    "--json", "output_json", is_flag=True, default=False,
    help="Emit structured JSON output.",
)


# ---------------------------------------------------------------------------
# maeris repair ui
# ---------------------------------------------------------------------------


@repair_group.command("ui")
@_source_option
@_model_option
@_dry_run_option
@_yes_option
@_json_option
def repair_ui_cmd(
    source: str | None,
    model: str,
    dry_run: bool,
    auto_yes: bool,
    output_json: bool,
) -> None:
    """Detect and repair UI selector drift in testcase steps.

    Phase 1 (deterministic): scans testcase selectors against your frontend
    source to find stale CSS identifiers.

    Phase 2 (AI-assisted): AI proposes replacement selectors from the
    actual source code. You confirm each change before it is applied.

    \b
    Examples:
      maeris repair ui
      maeris repair ui --source ../my-frontend --dry-run
      maeris repair ui --yes --model claude-opus-4-6
    """
    from maeris_mcp.maeris.client import MaerisClient
    from maeris_mcp.repair_engine.ui_repair import repair_ui

    config_dir = _repo_config_dir()
    _auth_check(config_dir)
    scan_root = Path(source) if source else Path.cwd()
    os.environ["MAERIS_CONFIG_DIR"] = str(config_dir)

    async def _detect():
        async with MaerisClient() as client:
            from maeris_mcp.drift_detector import detect_ui_drift
            with _spinner("Detecting UI drift…"):
                report = await detect_ui_drift(client, scan_root)
            _print_report_summary(report, output_json=output_json)
            return report

    report = asyncio.run(_detect())

    if not report.has_drift:
        _next_steps(
            "maeris repair contract --dry-run   Check all domains (UI + API + flows)",
            "maeris report coverage              Review test coverage",
        )
        return

    if dry_run:
        click.secho(
            "\n  [dry-run] No changes applied. Remove --dry-run to fix.", fg="cyan"
        )
        _next_steps("maeris repair ui   Apply the fixes")
        sys.exit(1)

    if not auto_yes:
        try:
            if not click.confirm(
                f"\n  Proceed with AI-assisted repair of "
                f"{len(report.issues)} issue(s)?",
                default=True,
            ):
                click.echo("Cancelled.")
                sys.exit(0)
        except click.Abort:
            click.echo("\nAborted.")
            sys.exit(0)

    async def _fix():
        async with MaerisClient() as client:
            return await repair_ui(client, scan_root, model, dry_run=False, yes=auto_yes)

    # Don't show a spinner while waiting for interactive prompts; Rich's status
    # can hide click.confirm and make the command look "hung".
    result = asyncio.run(_fix())

    if result is not None:
        _print_repair_result(result, output_json)
        if result.failed > 0:
            _next_steps("maeris repair ui   Re-run to retry failed fixes")
            sys.exit(2)
        _next_steps(
            "maeris repair ui --dry-run   Verify no drift remains",
            "maeris test run              Re-run tests to confirm",
        )
    elif not dry_run:
        sys.exit(0)
    else:
        sys.exit(1)


# ---------------------------------------------------------------------------
# maeris repair api
# ---------------------------------------------------------------------------


@repair_group.command("api")
@_source_option
@_model_option
@_dry_run_option
@_yes_option
@_json_option
def repair_api_cmd(
    source: str | None,
    model: str,
    dry_run: bool,
    auto_yes: bool,
    output_json: bool,
) -> None:
    """Detect and repair API route drift between codebase and Maeris registry.

    Phase 1 (deterministic): scans backend source for HTTP route definitions
    and diffs them against the Maeris API registry.

    Phase 2 (AI-assisted): AI recommends deleting stale entries or
    updating paths. You confirm each change.

    \b
    Examples:
      maeris repair api
      maeris repair api --source ../my-backend --dry-run
      maeris repair api --yes --json
    """
    from maeris_mcp.maeris.client import MaerisClient
    from maeris_mcp.repair_engine.api_repair import repair_api

    config_dir = _repo_config_dir()
    _auth_check(config_dir)
    scan_root = Path(source) if source else Path.cwd()
    os.environ["MAERIS_CONFIG_DIR"] = str(config_dir)

    async def _detect():
        async with MaerisClient() as client:
            from maeris_mcp.drift_detector import detect_api_drift
            return await detect_api_drift(client, scan_root)

    with _spinner("Detecting drift…"):
        report = asyncio.run(_detect())

    _print_report_summary(report, output_json=False)

    if not report.has_drift:
        _next_steps(
            "maeris repair contract --dry-run   Check all domains (UI + API + flows)",
        )
        return

    if dry_run:
        click.secho(
            "\n  [dry-run] No changes applied. Remove --dry-run to fix.", fg="cyan"
        )
        _next_steps("maeris repair api   Apply the fixes")
        sys.exit(1)

    if not auto_yes:
        try:
            if not click.confirm(
                f"\n  Proceed with AI-assisted repair of "
                f"{len(report.issues)} issue(s)?",
                default=True,
            ):
                click.echo("Cancelled.")
                sys.exit(0)
        except click.Abort:
            click.echo("\nAborted.")
            sys.exit(0)

    async def _fix():
        async with MaerisClient() as client:
            return await repair_api(client, scan_root, model, dry_run=False, yes=auto_yes)

    result = asyncio.run(_fix())

    if result is not None:
        _print_repair_result(result, output_json)
        if result.failed > 0:
            _next_steps("maeris repair api   Re-run to retry failed fixes")
            sys.exit(2)
        _next_steps(
            "maeris repair api --dry-run   Verify no drift remains",
            "maeris api list               Review API registry",
        )


# ---------------------------------------------------------------------------
# maeris repair flow
# ---------------------------------------------------------------------------


@repair_group.command("flow")
@_model_option
@_dry_run_option
@_yes_option
@_json_option
def repair_flow_cmd(
    model: str,
    dry_run: bool,
    auto_yes: bool,
    output_json: bool,
) -> None:
    """Detect and repair stale flow steps in Maeris.

    Phase 1 (deterministic): checks each flow step against the current API
    registry — no filesystem scan needed.

    Phase 2 (AI-assisted): AI proposes a replacement API for each stale
    step or recommends removing the step.

    \b
    Examples:
      maeris repair flow
      maeris repair flow --dry-run
      maeris repair flow --yes
    """
    from maeris_mcp.maeris.client import MaerisClient
    from maeris_mcp.repair_engine.flow_repair import repair_flow

    config_dir = _repo_config_dir()
    _auth_check(config_dir)
    os.environ["MAERIS_CONFIG_DIR"] = str(config_dir)

    async def _detect():
        async with MaerisClient() as client:
            from maeris_mcp.drift_detector import detect_flow_drift
            return await detect_flow_drift(client)

    with _spinner("Detecting drift…"):
        report = asyncio.run(_detect())

    _print_report_summary(report, output_json=False)

    if not report.has_drift:
        _next_steps(
            "maeris repair contract --dry-run   Check all domains (UI + API + flows)",
        )
        return

    if dry_run:
        click.secho(
            "\n  [dry-run] No changes applied. Remove --dry-run to fix.", fg="cyan"
        )
        _next_steps("maeris repair flow   Apply the fixes")
        sys.exit(1)

    if not auto_yes:
        try:
            if not click.confirm(
                f"\n  Proceed with AI-assisted repair of "
                f"{len(report.issues)} issue(s)?",
                default=True,
            ):
                click.echo("Cancelled.")
                sys.exit(0)
        except click.Abort:
            click.echo("\nAborted.")
            sys.exit(0)

    async def _fix():
        async with MaerisClient() as client:
            return await repair_flow(client, model, dry_run=False, yes=auto_yes)

    result = asyncio.run(_fix())

    if result is not None:
        _print_repair_result(result, output_json)
        if result.failed > 0:
            _next_steps("maeris repair flow   Re-run to retry failed fixes")
            sys.exit(2)
        _next_steps(
            "maeris repair flow --dry-run   Verify no drift remains",
            "maeris flow list               Review flows",
        )


# ---------------------------------------------------------------------------
# maeris repair contract
# ---------------------------------------------------------------------------


@repair_group.command("contract")
@_source_option
@_model_option
@_dry_run_option
@_yes_option
@_json_option
def repair_contract_cmd(
    source: str | None,
    model: str,
    dry_run: bool,
    auto_yes: bool,
    output_json: bool,
) -> None:
    """Run a full cross-domain drift check and repair (UI + API + Flow).

    Combines 'maeris repair ui', 'maeris repair api', and 'maeris repair flow'
    into a single compound operation.

    \b
    Examples:
      maeris repair contract
      maeris repair contract --dry-run
      maeris repair contract --source ../my-project --yes --json
    """
    from maeris_mcp.maeris.client import MaerisClient
    from maeris_mcp.drift_detector import detect_all_drift
    from maeris_mcp.repair_engine.ui_repair import repair_ui
    from maeris_mcp.repair_engine.api_repair import repair_api
    from maeris_mcp.repair_engine.flow_repair import repair_flow

    config_dir = _repo_config_dir()
    _auth_check(config_dir)
    scan_root = Path(source) if source else Path.cwd()
    os.environ["MAERIS_CONFIG_DIR"] = str(config_dir)

    async def _detect():
        async with MaerisClient() as client:
            return await detect_all_drift(client, scan_root)

    with _spinner("Detecting drift…"):
        report = asyncio.run(_detect())

    if output_json:
        click.echo(json.dumps(report.to_dict(), indent=2))
    else:
        if not report.has_drift:
            click.secho("  ✓ No drift detected.", fg="green")
        else:
            by_domain: dict[str, list] = {}
            for issue in report.issues:
                by_domain.setdefault(issue.resource_type, []).append(issue)
            for domain, issues in by_domain.items():
                click.echo(
                    f"\n  {domain.upper()}: {len(issues)} issue(s)"
                )
                for issue in issues:
                    click.echo(f"    ✗ {issue.description}")

    if not report.has_drift:
        _next_steps(
            "maeris report coverage   Review overall test coverage",
            "maeris ci                Run full quality gate",
        )
        return

    if dry_run:
        click.secho(
            f"\n  [dry-run] {len(report.issues)} issue(s) detected. "
            "Remove --dry-run to repair.",
            fg="cyan",
        )
        _next_steps("maeris repair contract   Apply all fixes")
        sys.exit(1)

    if not auto_yes:
        try:
            if not click.confirm(
                f"\n  Repair all {len(report.issues)} issue(s)?",
                default=True,
            ):
                click.echo("Cancelled.")
                sys.exit(0)
        except click.Abort:
            click.echo("\nAborted.")
            sys.exit(0)

    async def _fix():
        async with MaerisClient() as client:
            from maeris_mcp.drift_detector.models import RepairResult
            total = RepairResult()

            click.echo("\n→ Repairing UI…")
            r = await repair_ui(client, scan_root, model, dry_run=False, yes=auto_yes)
            total.fixed += r.fixed
            total.skipped += r.skipped
            total.failed += r.failed

            click.echo("\n→ Repairing API…")
            r = await repair_api(client, scan_root, model, dry_run=False, yes=auto_yes)
            total.fixed += r.fixed
            total.skipped += r.skipped
            total.failed += r.failed

            click.echo("\n→ Repairing flows…")
            r = await repair_flow(client, model, dry_run=False, yes=auto_yes)
            total.fixed += r.fixed
            total.skipped += r.skipped
            total.failed += r.failed

            return total

    result = asyncio.run(_fix())

    if result is not None:
        _print_repair_result(result, output_json)
        if result.failed > 0:
            _next_steps("maeris repair contract   Re-run to retry failed fixes")
            sys.exit(2)
        _next_steps(
            "maeris repair contract --dry-run   Verify no drift remains",
            "maeris test run                    Re-run tests to confirm",
        )
