"""CI command: compound quality gate for pipelines."""

import json
import os
import sys

import click

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


@cli.command("ci")
@click.option("--scan/--no-scan", "run_scan", default=True, show_default=True,
              help="Run security scan.")
@click.option("--coverage/--no-coverage", "run_coverage", default=True, show_default=True,
              help="Check UI test coverage.")
@click.option("--run/--no-run", "run_tests", default=False, show_default=True,
              help="Execute test suite.")
@click.option("--folder", "-f", default="", help="Test folder filter (used with --run).")
@click.option("--fail-below", "fail_below", default=0, type=int, show_default=True,
              help="Exit 1 if coverage percentage is below this threshold.")
@click.option("--source", "-s", default=None, help="Source directory for scan/coverage (default: cwd).")
@click.option("--model", "-m", default="claude-sonnet-4-6", show_default=True,
              help="AI model for scan.")
@click.option("--json", "output_json", is_flag=True, default=False,
              help="Emit a JSON summary to stdout.")
@click.option("--contract-check/--no-contract-check", "run_contract", default=False,
              show_default=True,
              help="Run deterministic drift detection (UI + API + flows). Exits 1 if drift found. Never auto-repairs.")
def ci_cmd(
    run_scan: bool,
    run_coverage: bool,
    run_tests: bool,
    folder: str,
    fail_below: int,
    source: str | None,
    model: str,
    output_json: bool,
    run_contract: bool,
) -> None:
    """Run a compound quality gate — security scan, coverage check, and/or test run.

    Designed for CI pipelines: exits non-zero if any enabled check fails
    or if coverage falls below --fail-below.

    \b
    Examples:
      maeris ci                              # scan + coverage (default)
      maeris ci --run-tests --folder smoke   # also run smoke tests
      maeris ci --no-scan --fail-below 80   # coverage gate only
      maeris ci --json > ci_summary.json    # machine-readable output
    """
    import asyncio
    import shutil
    import subprocess
    from maeris_mcp.auth.token_store import TokenStore

    config_dir = _repo_config_dir()
    token_store = TokenStore(config_dir=config_dir)

    if not token_store.is_authenticated():
        click.secho("✗ Not authenticated. Run 'maeris auth login' first.", fg="red")
        sys.exit(1)
    if token_store.is_token_expired():
        click.secho("✗ Token expired. Run 'maeris auth login' to re-authenticate.", fg="red")
        sys.exit(1)

    from pathlib import Path as _Path
    scan_root = str(_Path(source or os.getcwd()).resolve())
    results: dict[str, object] = {}
    failed = False

    # ── Security scan ──────────────────────────────────────────────────────
    if run_scan:
        click.echo("→ Running security scan...")
        claude_bin = shutil.which("claude")
        if not claude_bin:
            click.secho("  ✗ AI CLI not found on PATH — skipping scan.", fg="yellow")
            results["scan"] = {"status": "skipped", "reason": "claude not found"}
        else:
            if not os.access(claude_bin, os.X_OK):
                raise click.ClickException(f"'{claude_bin}' is not executable. Re-install Claude CLI.")
            try:
                from ._utils import _spinner
                with _spinner("Running security scan…"):
                    proc = subprocess.run(
                        [claude_bin, "--model", model,
                         "--print",
                         f"Use the security_scan MCP tool on {scan_root} with profile owasp_top10_static and batch_size 5. Report findings as JSON."],
                        capture_output=True, text=True, timeout=300,
                    )
                if proc.returncode == 0:
                    click.secho("  ✓ Scan complete.", fg="green")
                    results["scan"] = {"status": "passed", "output": proc.stdout[-2000:]}
                else:
                    click.secho("  ✗ Scan returned non-zero exit.", fg="red")
                    results["scan"] = {"status": "failed", "output": proc.stderr[-1000:]}
                    failed = True
            except subprocess.TimeoutExpired:
                click.secho("  ✗ Scan timed out.", fg="red")
                results["scan"] = {"status": "timeout"}
                failed = True
            except Exception as e:
                click.secho(f"  ✗ Scan error: {e}", fg="red")
                results["scan"] = {"status": "error", "error": str(e)}
                failed = True

    # ── Coverage check ─────────────────────────────────────────────────────
    if run_coverage:
        click.echo("→ Checking UI coverage...")

        async def _fetch_coverage() -> tuple:
            from maeris_mcp.maeris.client import MaerisClient
            os.environ["MAERIS_CONFIG_DIR"] = str(config_dir)
            async with MaerisClient() as client:
                tests = await client.get_testcases()
                apis = await client.get_all_apis()
                return tests, apis

        try:
            with _spinner("Checking test coverage…"):

                tests, apis = asyncio.run(_fetch_coverage())
            total_tests = len(tests)
            total_apis = len(apis)

            # Simple coverage metric: tests : APIs ratio
            if total_apis > 0:
                pct = round(min(total_tests / total_apis * 100, 100), 1)
            else:
                pct = 100.0 if total_tests > 0 else 0.0

            coverage_result = {"status": "passed", "coverage_pct": pct,
                               "total_tests": total_tests, "total_apis": total_apis}

            if fail_below > 0 and pct < fail_below:
                click.secho(f"  ✗ Coverage {pct}% is below threshold {fail_below}%.", fg="red")
                coverage_result["status"] = "failed"
                failed = True
            else:
                click.secho(f"  ✓ Coverage: {pct}%", fg="green")

            results["coverage"] = coverage_result
        except Exception as e:
            click.secho(f"  ✗ Coverage check failed: {e}", fg="red")
            results["coverage"] = {"status": "error", "error": str(e)}
            failed = True

    # ── Test run ───────────────────────────────────────────────────────────
    if run_tests:
        click.echo("→ Running tests...")

        async def _run_tests() -> dict:
            from maeris_mcp.maeris.client import MaerisClient
            os.environ["MAERIS_CONFIG_DIR"] = str(config_dir)
            async with MaerisClient() as client:
                testcases = await client.get_testcases()
                if folder:
                    groups = await client.get_testcase_groups()
                    folder_ids = {
                        g.get("testcase_group_id") or g.get("id") or g.get("_id", "")
                        for g in groups
                        if folder.lower() in (g.get("name") or "").lower()
                    }
                    testcases = [t for t in testcases if t.get("testcase_group_id") in folder_ids]
                if not testcases:
                    return {"status": "skipped", "reason": "no testcases matched"}
                ids = [t.get("testcase_id") or t.get("_id") or t.get("id") for t in testcases]
                ids = [i for i in ids if i]
                result = await client.run_testcases(ids, type_of_test_run="ci")
                return result

        try:
            with _spinner("Running tests…"):

                run_result = asyncio.run(_run_tests())
            status = run_result.get("status", "unknown")
            if status in ("passed", "success", "completed"):
                click.secho(f"  ✓ Tests {status}.", fg="green")
                results["tests"] = {"status": "passed", **run_result}
            elif status == "skipped":
                click.secho("  ⚠ No testcases matched — skipped.", fg="yellow")
                results["tests"] = run_result
            else:
                click.secho(f"  ✗ Tests {status}.", fg="red")
                results["tests"] = {"status": "failed", **run_result}
                failed = True
        except Exception as e:
            click.secho(f"  ✗ Test run failed: {e}", fg="red")
            results["tests"] = {"status": "error", "error": str(e)}
            failed = True

    # ── Contract drift check ────────────────────────────────────────────────
    if run_contract:
        click.echo("→ Running contract drift check…")

        async def _detect_drift():
            from maeris_mcp.maeris.client import MaerisClient
            from maeris_mcp.drift_detector import detect_all_drift
            os.environ["MAERIS_CONFIG_DIR"] = str(config_dir)
            async with MaerisClient() as client:
                return await detect_all_drift(client, scan_root)

        try:
            import asyncio as _asyncio
            with _spinner("Checking contract drift…"):

                drift_report = _asyncio.run(_detect_drift())
            if drift_report.has_drift:
                n = len(drift_report.issues)
                click.secho(f"  ✗ {n} drift issue(s) found.", fg="red")
                click.secho("  → Run 'maeris repair contract' to fix.", fg="yellow")
                results["contract"] = {"status": "failed", "issues": n}
                failed = True
            else:
                click.secho("  ✓ No drift detected.", fg="green")
                results["contract"] = {"status": "passed"}
        except Exception as e:
            click.secho(f"  ✗ Contract check error: {e}", fg="red")
            results["contract"] = {"status": "error", "error": str(e)}
            failed = True

    # ── Summary ────────────────────────────────────────────────────────────
    overall = "failed" if failed else "passed"
    summary = {"status": overall, "checks": results}

    click.echo()
    if output_json:
        click.echo(json.dumps(summary, indent=2))
    else:
        status_color = "red" if failed else "green"
        click.secho(f"CI gate: {overall.upper()}", fg=status_color, bold=True)
        for check, res in results.items():
            s = res.get("status", "?") if isinstance(res, dict) else str(res)
            icon = "✓" if s in ("passed", "skipped") else "✗"
            color = "green" if s == "passed" else ("yellow" if s == "skipped" else "red")
            click.secho(f"  {icon} {check}: {s}", fg=color)

    if failed and not output_json:
        # Build recovery hints based on which checks failed
        hints: list[str] = []
        scan_res = results.get("scan", {})
        cov_res = results.get("coverage", {})
        test_res = results.get("tests", {})
        contract_res = results.get("contract", {})
        if isinstance(scan_res, dict) and scan_res.get("status") in ("failed", "error", "timeout"):
            hints.append("maeris scan security          Re-run security scan")
        if isinstance(cov_res, dict) and cov_res.get("status") == "failed":
            hints.append("maeris test create --url <url>   Generate more tests to raise coverage")
        if isinstance(test_res, dict) and test_res.get("status") in ("failed", "error"):
            hints.append("maeris test fix               Auto-fix failing tests")
        if isinstance(contract_res, dict) and contract_res.get("status") == "failed":
            hints.append("maeris repair contract        Fix detected drift")
        if hints:
            _next_steps(*hints)

    sys.exit(1 if failed else 0)
