"""Scan CLI commands: security scanning, API extraction, accessibility, GDPR, and spell/grammar."""

import os
import shutil
import sys
from pathlib import Path

import click

from ._core import push_group, scan_group
from ._utils import _claude_run, _resolve_ai_mode, _run_via_direct_api, _next_steps

# ---------------------------------------------------------------------------
# Depth presets
# ---------------------------------------------------------------------------

# Security scan profiles (from maeris_mcp.scanners.security_checks)
_PROFILE_QUICK = "owasp_top10_static"   # OWASP Top 10, ~140 checks
_PROFILE_DEFAULT = "owasp_top10_static"
_PROFILE_DEEP = "all_static"            # All 600+ static checks

# File batch sizes per Claude invocation
_LIMIT_QUICK = 3
_LIMIT_DEFAULT = 5
_LIMIT_DEEP = 10

def _check_auth() -> None:
    """Exit with a clear error if the user is not authenticated."""
    from maeris_mcp.auth.token_store import TokenStore
    from ._utils import _repo_config_dir

    ts = TokenStore(config_dir=_repo_config_dir())
    if not ts.is_authenticated():
        click.secho("\n✗ Not authenticated. Run 'maeris login' first.", fg="red")
        sys.exit(1)


def _check_scope(tool_name: str) -> None:
    """Exit with a clear error if the current Maeris scope blocks *tool_name*."""
    from maeris_mcp.auth.scopes import load_maeris_scope, is_maeris_tool_allowed, TOOL_SCOPES

    scope = load_maeris_scope()
    if not is_maeris_tool_allowed(tool_name, scope):
        required = TOOL_SCOPES.get(tool_name, "write")
        click.secho(
            f"\n✗ Your current Maeris scope is '{scope}', "
            f"but '{tool_name}' requires '{required}' scope.\n",
            fg="red",
        )
        click.echo(
            f"  Run this to fix it:\n"
            f"    maeris app scope --maeris {required}\n"
        )
        sys.exit(1)


def _resolve_depth(quick: bool, deep: bool) -> tuple[str, int, str]:
    """Return (scan_profile, file_limit, label) from --quick / --deep flags."""
    if quick:
        return _PROFILE_QUICK, _LIMIT_QUICK, "quick"
    if deep:
        return _PROFILE_DEEP, _LIMIT_DEEP, "deep"
    return _PROFILE_DEFAULT, _LIMIT_DEFAULT, "standard"


def _label_to_scan_mode(label: str) -> str:
    """Map CLI depth label to MCP scan_mode parameter."""
    return {"quick": "quick", "deep": "deep"}.get(label, "mid")


def _require_claude() -> str:
    """Return path to the claude binary or exit with a helpful message."""
    claude_bin = shutil.which("claude")
    if not claude_bin:
        click.secho(
            "✗ AI CLI not found on PATH.\n"
            "  Install Claude Code from https://claude.ai/code and run 'claude auth login'.",
            fg="red",
        )
        sys.exit(1)
    return claude_bin


# ---------------------------------------------------------------------------
# maeris scan security
# ---------------------------------------------------------------------------


@scan_group.command("security")
@click.option("--source", "-s", default=None,
              help="Directory to scan (default: cwd).")
@click.option("--quick", "quick", is_flag=True, default=False,
              help="Fast scan: OWASP Top 10 only, 3 files per batch.")
@click.option("--deep", "deep", is_flag=True, default=False,
              help="Thorough scan: all 600+ checks, 10 files per batch.")
@click.option("--profile", "profile", default=None,
              help=(
                  "Security profile to use (overrides --quick/--deep). "
                  "Options: owasp_top10_static, all_static, api_static, "
                  "sast_static, auth_session_static, data_exposure_static, "
                  "infra_static, …"
              ))
@click.option("--model", "model", default="claude-sonnet-4-6", show_default=True,
              help="AI model to use for analysis.")
def scan_security_cmd(
    source: str | None,
    quick: bool,
    deep: bool,
    profile: str | None,
    model: str,
) -> None:
    """Run an LLM-guided static security scan on your codebase.

    \b
    AI analyzes your source files batch by batch using the security_scan
    MCP tool and reports findings with severity, OWASP category, and
    remediation hints.

    \b
    Depth presets:
      (default)  OWASP Top 10, 5 files/batch   - balanced
      --quick    OWASP Top 10, 3 files/batch   - faster, key findings
      --deep     All 600+ checks, 10 files/batch - thorough, takes longer

    \b
    Examples:
      maeris scan security
      maeris scan security --quick
      maeris scan security --deep --source ../my-backend
      maeris scan security --profile sast_static
    """
    if quick and deep:
        click.secho("✗ --quick and --deep are mutually exclusive.", fg="red")
        sys.exit(1)

    _check_auth()
    claude_bin, ai_mode = _resolve_ai_mode()
    target_path = str(Path(source).resolve()) if source else os.getcwd()
    resolved_profile, limit, label = _resolve_depth(quick, deep)
    if profile:
        resolved_profile = profile
        label = f"custom ({profile})"

    _check_scope("security_scan")

    click.echo(
        f"\n  Security scan — {label}  "
        f"(profile: {resolved_profile}, {limit} files/batch)\n"
        f"  Target: {target_path}\n"
    )

    prompt = (
        f"Run a security scan on: {target_path}\n\n"
        f"Use the security_scan MCP tool with these parameters:\n"
        f"  target_path    = {target_path!r}\n"
        f"  scan_profile   = {resolved_profile!r}\n"
        f"  limit          = {limit}   (files per batch)\n\n"
        "Workflow:\n"
        "1. Call security_scan with include_content=true, offset=0, "
        "and the parameters above to get the first file batch.\n"
        "2. Analyze every file in the batch for the security issues covered "
        "by the profile. Identify real findings only — avoid false positives.\n"
        "3. On the next call pass findings=[...] from this batch alongside "
        "the next offset (pagination.nextOffset).\n"
        "4. Repeat until pagination.hasMore is false.\n"
        "5. On the final call pass findings=[...] AND is_last=true to "
        "finalize the scan and receive the full report.\n"
        "6. After the scan finalizes, print a clear summary:\n"
        "   - Total findings by severity (critical / high / medium / low / info)\n"
        "     IMPORTANT: Use the severity counts from the finalization response's "
        "summary (summary.by_severity), NOT your own count of findings you submitted. "
        "The finalization response includes findings from automated scanners "
        "(depScanResults, infraScanResults, jsxScanResults, secretScanResults, "
        "patternScanResults) that run in addition to your analysis. "
        "The summary.total_findings is the true total.\n"
        "   - If automated scanner results are present, list them: "
        "e.g. 'Automated: 12 dependency CVEs, 5 hardcoded secrets, 8 pattern issues'\n"
        "   IMPORTANT: Do NOT use emoji in the output — use plain text labels only "
        "(e.g. 'Critical: 3' not '🔴 Critical: 3'). Emoji render as garbled text on Windows terminals.\n"
        "   Do NOT list individual findings, file paths, or issue descriptions — only show headline counts. "
        "Full details are available in the Maeris portal.\n"
        "7. After printing the summary, call push_to_maeris with "
        "data_type='vulnerabilities' and the scan_id from the finalized scan "
        "to push findings to the Maeris portal.\n"
        "8. Print how many findings were pushed.\n"
        "STOP after printing the result. Do not call any other tools."
    )

    if claude_bin:
        rc = _claude_run(claude_bin, prompt, model, target_path)
    else:
        rc = _run_via_direct_api(prompt, target_path, spinner_message="Running security scan…")
    if rc != 0:
        click.secho(f"\n✗ Security scan failed (exit code {rc}).", fg="red")
        click.secho("  Try 'maeris doctor' to check your setup.", fg="bright_black")
        sys.exit(rc)

    _next_steps(
        "maeris report security --format html -o security.html   Export report",
        "maeris repair contract --dry-run                        Check for drift",
    )


# ---------------------------------------------------------------------------
# maeris scan api
# ---------------------------------------------------------------------------


@scan_group.command("api")
@click.option("--source", "-s", default=None,
              help="Directory to scan (default: cwd).")
@click.option("--quick", "quick", is_flag=True, default=False,
              help="Fast scan: 3 files per batch (hits main API files quickly).")
@click.option("--deep", "deep", is_flag=True, default=False,
              help="Thorough scan: 10 files per batch (scans every source file).")
@click.option("--project-name", "project_name", default=None,
              help="Name for the API collection (defaults to directory name).")
@click.option("--model", "model", default="claude-sonnet-4-6", show_default=True,
              help="AI model to use for analysis.")
def scan_api_cmd(
    source: str | None,
    quick: bool,
    deep: bool,
    project_name: str | None,
    model: str,
) -> None:
    """Extract API definitions from your codebase using LLM analysis.

    \b
    AI scans your source for HTTP calls, endpoints, headers, auth patterns,
    and request/response payloads using the api_scan MCP tool, then produces
    a structured API collection you can import into Maeris.

    \b
    Depth presets:
      (default)  5 files/batch  - balanced
      --quick    3 files/batch  - faster, hits main API files
      --deep     10 files/batch - thorough, scans everything

    \b
    Examples:
      maeris scan api
      maeris scan api --quick
      maeris scan api --deep --source ../my-frontend
      maeris scan api --project-name "My App v2"
    """
    if quick and deep:
        click.secho("✗ --quick and --deep are mutually exclusive.", fg="red")
        sys.exit(1)

    _check_auth()
    claude_bin, ai_mode = _resolve_ai_mode()
    target_path = str(Path(source).resolve()) if source else os.getcwd()
    _, limit, label = _resolve_depth(quick, deep)
    proj_name = project_name or Path(target_path).name

    _check_scope("api_scan")

    click.echo(
        f"\n  API scan — {label}  ({limit} files/batch)\n"
        f"  Target: {target_path}\n"
        f"  Project: {proj_name}\n"
    )

    prompt = (
        f"Extract all API definitions from: {target_path}\n\n"
        f"Use the api_scan MCP tool with these parameters:\n"
        f"  target_path  = {target_path!r}\n"
        f"  project_name = {proj_name!r}\n"
        f"  limit        = {limit}   (files per batch)\n\n"
        "Workflow:\n"
        "1. Call api_scan with include_content=true, offset=0, and the "
        "parameters above to get the first file batch.\n"
        "2. Analyze every file for HTTP API calls: endpoint, method, headers, "
        "auth, request/response payload, query params, path params, base URL.\n"
        "3. On the next call pass apis=[...] from this batch alongside the "
        "next offset (pagination.nextOffset). Also pass "
        "http_clients=[...] listing every HTTP library detected "
        "(axios, fetch, requests, httpx, got, superagent, …).\n"
        "4. Repeat until pagination.hasMore is false.\n"
        "5. On the final call pass apis=[...] AND is_last=true to finalize.\n"
        "6. After the scan finalizes, print a clear summary:\n"
        "   - Total APIs by method (GET / POST / PUT / PATCH / DELETE)\n"
        "   - HTTP clients detected\n"
        "   - Auth patterns found (bearer, API key, basic auth, OAuth, …)\n"
        "7. After printing the summary, call get_stored_apis to retrieve "
        "the scanned APIs, then call add_apis_bulk with:\n"
        f"   - collection_name = {proj_name!r}\n"
        "   - apis = the full list of scanned APIs\n"
        "   - auto_environment = true\n"
        f"   - project_name = {proj_name!r}\n"
        "   - extracted_environment = the extractedEnvironment array from the "
        "api_scan finalization response\n"
        "   This automatically creates/reuses an environment, populates it with "
        "base_url and other env fields, parameterizes all API URLs as "
        "{{base_url}}/path, and selects the environment.\n"
        "8. Print how many APIs were imported and the environment setup result.\n"
        "STOP after printing the result. Do not call any other tools. "
        "Do not ask the user any questions."
    )

    if claude_bin:
        rc = _claude_run(claude_bin, prompt, model, target_path)
    else:
        rc = _run_via_direct_api(prompt, target_path, spinner_message="Running API scan…")
    if rc != 0:
        click.secho(f"\n✗ API scan failed (exit code {rc}).", fg="red")
        click.secho("  Try 'maeris doctor' to check your setup.", fg="bright_black")
        sys.exit(rc)

    _next_steps(
        "maeris api list                         View extracted APIs",
        "maeris flow create --name <flow>        Create an API flow",
        "maeris report coverage                  Check test coverage",
    )


# ---------------------------------------------------------------------------
# maeris scan push  — retry pushing the last security scan to Maeris
# ---------------------------------------------------------------------------


@scan_group.command("push")
@click.option("--source", "-s", default=None,
              help="Directory that was scanned (default: cwd).")
def scan_push_cmd(source: str | None) -> None:
    """Push the last security scan findings to Maeris.

    \b
    Use this if a previous 'maeris scan security' completed the scan
    but failed to push (e.g. expired auth, network error).

    \b
    Examples:
      maeris scan push
      maeris scan push --source ../my-backend
    """
    import asyncio
    import hashlib
    import json
    from uuid import uuid4
    from datetime import datetime, timezone

    from rich.console import Console
    from maeris_mcp.auth.token_store import TokenStore
    from ._utils import _repo_config_dir, _spinner

    _check_auth()

    target_path = str(Path(source).resolve()) if source else os.getcwd()

    # Locate the cached findings
    root_hash = hashlib.sha256(target_path.encode()).hexdigest()[:16]
    config_dir = os.getenv("MAERIS_CONFIG_DIR") or str(Path.home() / ".maeris")
    cache_path = Path(config_dir) / "security_scans" / root_hash / "findings.json"

    if not cache_path.exists():
        click.secho(
            f"\n✗ No cached scan found for: {target_path}\n"
            "  Run 'maeris scan security' first.",
            fg="red",
        )
        sys.exit(1)

    try:
        data = json.loads(cache_path.read_text())
        raw_findings = data if isinstance(data, list) else data.get("findings", [])
    except Exception as e:
        click.secho(f"\n✗ Failed to read cached scan: {e}", fg="red")
        sys.exit(1)

    if not raw_findings:
        click.secho("\n✗ Cached scan has no findings to push.", fg="red")
        sys.exit(1)

    click.echo(f"\n  Found {len(raw_findings)} cached findings for: {target_path}\n")

    async def _push() -> None:
        from maeris_mcp.types.security import SecurityFinding, SeverityCounts, ScanSummary, SecurityScanResult
        from maeris_mcp.storage.security_store import SecurityScanStore
        from maeris_mcp.tools.maeris_sync import handle_push_to_maeris

        # Rebuild findings
        findings = [SecurityFinding.model_validate(f) for f in raw_findings]

        # Compute summary
        sev_counts = {"low": 0, "medium": 0, "high": 0, "critical": 0}
        owasp_counts: dict[str, int] = {}
        for f in findings:
            sev = f.severity.value if f.severity else "medium"
            sev_counts[sev] = sev_counts.get(sev, 0) + 1
            if f.owasp_category:
                code = f.owasp_category.value
                owasp_counts[code] = owasp_counts.get(code, 0) + 1

        scan_id = str(uuid4())
        result = SecurityScanResult(
            scan_id=scan_id,
            target_path=target_path,
            scan_timestamp=datetime.now(timezone.utc).isoformat(),
            findings=findings,
            summary=ScanSummary(
                total_findings=len(findings),
                by_severity=SeverityCounts(**sev_counts),
                by_owasp_category=owasp_counts,
                files_scanned=0,
            ),
            errors=[],
        )

        store = SecurityScanStore()
        store.store(result)
        store.finalize_scan(scan_id)

        responses = await handle_push_to_maeris(
            store, {"data_type": "vulnerabilities", "scan_id": scan_id}
        )

        for r in responses:
            resp = json.loads(r.text)
            status = resp.get("status", "unknown")
            created = resp.get("findings_created", [])
            if status == "pushed":
                click.secho(
                    f"\n  ✓ {len(created)} findings pushed to Maeris.\n",
                    fg="green",
                )
            else:
                click.secho(
                    f"\n  ⚠ Push completed with status: {status}\n"
                    f"    Created: {len(created)} findings\n"
                    f"    Failures: {resp.get('findings_failed', [])}\n",
                    fg="yellow",
                )

    with _spinner("Pushing findings to Maeris…"):
        asyncio.run(_push())

    _next_steps(
        "maeris report security --format html -o security.html   Export report",
    )


# ---------------------------------------------------------------------------
# maeris scan wcag
# ---------------------------------------------------------------------------


@scan_group.command("wcag")
@click.option("--source", "-s", default=None,
              help="Directory to scan (default: cwd).")
@click.option("--quick", "quick", is_flag=True, default=False,
              help="Quick scan: ~100 files, ~2 min.")
@click.option("--deep", "deep", is_flag=True, default=False,
              help="Deep scan: ~500 files, ~10 min.")
@click.option("--model", "model", default="claude-sonnet-4-6", show_default=True,
              help="AI model to use for analysis.")
def scan_wcag_cmd(
    source: str | None,
    quick: bool,
    deep: bool,
    model: str,
) -> None:
    """Run an LLM-guided WCAG accessibility scan on your codebase.

    \b
    AI analyzes your source files batch by batch using the accessibility_scan
    MCP tool and reports WCAG 2.0/2.1/2.2 compliance issues.

    \b
    Depth presets:
      (default)  ~300 files, ~6 min   - balanced
      --quick    ~100 files, ~2 min   - faster, key findings
      --deep     ~500 files, ~10 min  - thorough, takes longer

    \b
    Examples:
      maeris scan wcag
      maeris scan wcag --quick
      maeris scan wcag --deep --source ../my-frontend
    """
    if quick and deep:
        click.secho("✗ --quick and --deep are mutually exclusive.", fg="red")
        sys.exit(1)

    _check_auth()
    claude_bin, ai_mode = _resolve_ai_mode()
    target_path = str(Path(source).resolve()) if source else os.getcwd()
    _, limit, label = _resolve_depth(quick, deep)

    _check_scope("accessibility_scan")

    click.echo(
        f"\n  WCAG scan — {label}  ({limit} files/batch)\n"
        f"  Target: {target_path}\n"
    )

    scan_mode = _label_to_scan_mode(label)
    prompt = (
        f"Run a WCAG accessibility scan on: {target_path}\n\n"
        f"Use the accessibility_scan MCP tool with these parameters:\n"
        f"  target_path    = {target_path!r}\n"
        f"  scan_mode      = {scan_mode!r}\n"
        f"  limit          = {limit}   (files per batch)\n\n"
        "Workflow:\n"
        "1. Call accessibility_scan with include_content=true, offset=0 to get the first file batch and WCAG checklist.\n"
        "2. Analyze every file for accessibility issues per the checklist. Check: alt text, ARIA, form labels, heading order, "
        "color contrast hints, keyboard accessibility, focus management, error identification.\n"
        "3. On the next call pass findings=[...] from this batch alongside the next offset.\n"
        "4. Repeat until pagination.hasMore is false.\n"
        "5. On the final call pass findings=[...] AND is_last=true to finalize.\n"
        "6. Print a clear summary: total violations by impact (critical/serious/moderate/minor) and compliance score.\n"
        "   IMPORTANT: Do NOT use emoji in the output — use plain text labels only. "
        "Emoji render as garbled text on Windows terminals.\n"
        "   Do NOT list individual findings, file paths, or issue descriptions — only show headline counts. "
        "Full details are available in the Maeris portal.\n"
        "STOP after printing the summary. Do NOT call push tools or ask the user about pushing. "
        "The user can push later with 'maeris push wcag'."
    )

    if claude_bin:
        rc = _claude_run(claude_bin, prompt, model, target_path)
    else:
        rc = _run_via_direct_api(prompt, target_path, spinner_message="Running WCAG scan…")
    if rc != 0:
        click.secho(f"\n✗ WCAG scan failed (exit code {rc}).", fg="red")
        sys.exit(rc)

    _next_steps(
        "maeris push wcag                                Push findings to Maeris portal",
        "maeris report wcag --format html -o wcag.html   Export report",
    )


# ---------------------------------------------------------------------------
# maeris scan gdpr
# ---------------------------------------------------------------------------


@scan_group.command("gdpr")
@click.option("--source", "-s", default=None,
              help="Directory to scan (default: cwd).")
@click.option("--quick", "quick", is_flag=True, default=False,
              help="Quick scan: ~100 files, ~2 min.")
@click.option("--deep", "deep", is_flag=True, default=False,
              help="Deep scan: ~500 files, ~10 min.")
@click.option("--model", "model", default="claude-sonnet-4-6", show_default=True,
              help="AI model to use for analysis.")
def scan_gdpr_cmd(
    source: str | None,
    quick: bool,
    deep: bool,
    model: str,
) -> None:
    """Run an LLM-guided GDPR/privacy compliance scan on your codebase.

    \b
    AI analyzes your source for privacy violations: pre-consent cookies,
    PII in localStorage, third-party trackers, sensitive data exposure,
    and HTTPS enforcement.

    \b
    Depth presets:
      (default)  ~300 files, ~6 min   - balanced
      --quick    ~100 files, ~2 min   - faster, key findings
      --deep     ~500 files, ~10 min  - thorough, takes longer

    \b
    Examples:
      maeris scan gdpr
      maeris scan gdpr --quick
      maeris scan gdpr --deep --source ../my-app
    """
    if quick and deep:
        click.secho("✗ --quick and --deep are mutually exclusive.", fg="red")
        sys.exit(1)

    _check_auth()
    claude_bin, ai_mode = _resolve_ai_mode()
    target_path = str(Path(source).resolve()) if source else os.getcwd()
    _, limit, label = _resolve_depth(quick, deep)

    _check_scope("gdpr_scan")

    scan_mode = _label_to_scan_mode(label)
    click.echo(
        f"\n  GDPR scan — {label}  ({limit} files/batch)\n"
        f"  Target: {target_path}\n"
    )

    prompt = (
        f"Run a GDPR/privacy compliance scan on: {target_path}\n\n"
        f"Use the gdpr_scan MCP tool with these parameters:\n"
        f"  target_path    = {target_path!r}\n"
        f"  scan_mode      = {scan_mode!r}\n"
        f"  limit          = {limit}   (files per batch)\n\n"
        "Workflow:\n"
        "1. Call gdpr_scan with include_content=true, offset=0 to get the first file batch and GDPR checklist.\n"
        "2. Analyze every file for privacy issues: pre-consent cookies/tracking, PII in storage, "
        "third-party tracker scripts, sensitive data exposure, HTTP URLs.\n"
        "3. On the next call pass findings=[...] from this batch alongside the next offset.\n"
        "4. Repeat until pagination.hasMore is false.\n"
        "5. On the final call pass findings=[...] AND is_last=true to finalize.\n"
        "6. Print a clear summary: total findings by severity, by category, and compliance score.\n"
        "   IMPORTANT: Do NOT use emoji in the output — use plain text labels only. "
        "Emoji render as garbled text on Windows terminals.\n"
        "   Do NOT list individual findings, file paths, or issue descriptions — only show headline counts. "
        "Full details are available in the Maeris portal.\n"
        "STOP after printing the summary. Do NOT call push tools or ask the user about pushing. "
        "The user can push later with 'maeris push gdpr'."
    )

    if claude_bin:
        rc = _claude_run(claude_bin, prompt, model, target_path)
    else:
        rc = _run_via_direct_api(prompt, target_path, spinner_message="Running GDPR scan…")
    if rc != 0:
        click.secho(f"\n✗ GDPR scan failed (exit code {rc}).", fg="red")
        sys.exit(rc)

    _next_steps(
        "maeris push gdpr                                Push findings to Maeris portal",
        "maeris report gdpr --format html -o gdpr.html   Export report",
    )


# ---------------------------------------------------------------------------
# maeris scan spelling
# ---------------------------------------------------------------------------


@scan_group.command("spelling")
@click.option("--source", "-s", default=None,
              help="Directory to scan (default: cwd).")
@click.option("--quick", "quick", is_flag=True, default=False,
              help="Quick scan: ~100 files, ~2 min.")
@click.option("--deep", "deep", is_flag=True, default=False,
              help="Deep scan: ~500 files, ~10 min.")
@click.option("--model", "model", default="claude-sonnet-4-6", show_default=True,
              help="AI model to use for analysis.")
def scan_spelling_cmd(
    source: str | None,
    quick: bool,
    deep: bool,
    model: str,
) -> None:
    """Run an LLM-guided spell and grammar check on UI text in your codebase.

    \b
    AI scans JSX, TSX, HTML, Vue, Svelte templates for visible text
    (button labels, headings, placeholders, error messages) and identifies
    spelling errors and grammar issues.

    \b
    Depth presets:
      (default)  ~300 files, ~6 min   - balanced
      --quick    ~100 files, ~2 min   - faster, key findings
      --deep     ~500 files, ~10 min  - thorough, takes longer

    \b
    Examples:
      maeris scan spelling
      maeris scan spelling --quick
      maeris scan spelling --deep --source ../my-frontend
    """
    if quick and deep:
        click.secho("✗ --quick and --deep are mutually exclusive.", fg="red")
        sys.exit(1)

    _check_auth()
    claude_bin, ai_mode = _resolve_ai_mode()
    target_path = str(Path(source).resolve()) if source else os.getcwd()
    _, limit, label = _resolve_depth(quick, deep)

    _check_scope("spell_grammar_scan")

    scan_mode = _label_to_scan_mode(label)
    click.echo(
        f"\n  Spell/grammar scan — {label}  ({limit} files/batch)\n"
        f"  Target: {target_path}\n"
    )

    prompt = (
        f"Run a spell and grammar check on UI text in: {target_path}\n\n"
        f"Use the spell_grammar_scan MCP tool with these parameters:\n"
        f"  target_path    = {target_path!r}\n"
        f"  scan_mode      = {scan_mode!r}\n"
        f"  limit          = {limit}   (files per batch)\n\n"
        "Workflow:\n"
        "1. Call spell_grammar_scan with include_content=true, offset=0 to get the first file batch.\n"
        "2. Extract all visible UI text (button labels, headings, placeholders, tooltips, error messages, "
        "descriptions) and check for spelling errors and grammar issues.\n"
        "3. On the next call pass findings=[...] from this batch alongside the next offset.\n"
        "4. Repeat until pagination.hasMore is false.\n"
        "5. On the final call pass findings=[...] AND is_last=true to finalize.\n"
        "6. Print a clear summary: total corrections, spelling vs grammar count, "
        "and list all corrections found.\n"
        "   IMPORTANT: Do NOT use emoji in the output — use plain text labels only. "
        "Emoji render as garbled text on Windows terminals.\n"
        "STOP after printing the summary. Do NOT call push tools or ask the user about pushing. "
        "The user can push later with 'maeris push spelling'."
    )

    if claude_bin:
        rc = _claude_run(claude_bin, prompt, model, target_path)
    else:
        rc = _run_via_direct_api(prompt, target_path, spinner_message="Running spell/grammar scan…")
    if rc != 0:
        click.secho(f"\n✗ Spell/grammar scan failed (exit code {rc}).", fg="red")
        sys.exit(rc)

    _next_steps(
        "maeris push spelling                                    Push corrections to Maeris portal",
        "maeris report spelling --format html -o spelling.html   Export report",
    )


# ---------------------------------------------------------------------------
# maeris push gdpr
# ---------------------------------------------------------------------------


@push_group.command("gdpr")
@click.option("--source", "-s", default=None,
              help="Directory that was scanned (default: cwd).")
def push_gdpr_cmd(source: str | None) -> None:
    """Push the last GDPR scan findings to Maeris.

    \b
    Use this if a previous 'maeris scan gdpr' completed the scan
    but failed to push (e.g. expired auth, network error).

    \b
    Examples:
      maeris push gdpr
      maeris push gdpr --source ../my-app
    """
    import asyncio

    from ._utils import _repo_config_dir, _spinner

    _check_auth()

    target_path = str(Path(source).resolve()) if source else os.getcwd()

    from maeris_mcp.storage.gdpr_store import load_gdpr_from_disk, save_gdpr_to_disk

    stored = load_gdpr_from_disk(target_path)
    if not stored:
        click.secho(
            f"\n✗ No cached GDPR scan found for: {target_path}\n"
            "  Run 'maeris scan gdpr' first.",
            fg="red",
        )
        sys.exit(1)

    if stored.status != "completed":
        click.secho(
            f"\n✗ GDPR scan is not finalized (status: {stored.status}).\n"
            "  Run 'maeris scan gdpr' to completion first.",
            fg="red",
        )
        sys.exit(1)

    if stored.pushed_to_maeris:
        click.secho(
            f"\n✗ GDPR scan was already pushed to Maeris on {stored.pushed_at}.",
            fg="yellow",
        )
        sys.exit(0)

    findings_count = len(stored.result.findings)
    click.echo(f"\n  Found {findings_count} cached GDPR findings for: {target_path}\n")

    async def _push() -> None:
        from maeris_mcp.auth.token_store import TokenStore
        from maeris_mcp.maeris.client import MaerisClient

        config_dir = _repo_config_dir()
        os.environ["MAERIS_CONFIG_DIR"] = str(config_dir)
        token_store = TokenStore(config_dir=config_dir)
        application_id = token_store.get_app_id() or ""

        compliance_score = stored.result.summary.compliance_score or 0.0
        gdpr_results = []
        for finding in stored.result.findings:
            gdpr_results.append({
                "appid": application_id,
                "page_id": finding.page_id or "",
                "page_url": finding.page_url or finding.file_path,
                "page_gdpr_compliance_score": finding.page_gdpr_compliance_score,
                "category": finding.category,
                "severity": finding.severity.value,
                "issue": finding.issue,
                "details": finding.details,
                "suggested_fix": finding.suggested_fix,
            })

        async with MaerisClient() as client:
            await client.push_gdpr_results(gdpr_results, compliance_score=compliance_score)

        # Mark as pushed in disk cache
        stored.pushed_to_maeris = True
        from datetime import datetime, timezone
        stored.pushed_at = datetime.now(timezone.utc).isoformat()
        save_gdpr_to_disk(target_path, stored)

        click.secho(f"\n  ✓ {len(gdpr_results)} GDPR findings pushed to Maeris.\n", fg="green")

    try:
        with _spinner("Pushing GDPR findings to Maeris…"):
            asyncio.run(_push())
    except Exception as e:
        click.secho(f"\n✗ Failed to push GDPR findings: {e}", fg="red")
        sys.exit(1)

    _next_steps(
        "maeris report gdpr --format html -o gdpr.html   Export report",
    )


# ---------------------------------------------------------------------------
# maeris push wcag
# ---------------------------------------------------------------------------


@push_group.command("wcag")
@click.option("--source", "-s", default=None,
              help="Directory that was scanned (default: cwd).")
def push_wcag_cmd(source: str | None) -> None:
    """Push the last WCAG accessibility scan findings to Maeris.

    \b
    Use this if a previous 'maeris scan wcag' completed the scan
    but failed to push (e.g. expired auth, network error).

    \b
    Examples:
      maeris push wcag
      maeris push wcag --source ../my-frontend
    """
    import asyncio

    from ._utils import _repo_config_dir, _spinner

    _check_auth()

    target_path = str(Path(source).resolve()) if source else os.getcwd()

    from maeris_mcp.storage.accessibility_store import load_wcag_from_disk, save_wcag_to_disk

    stored = load_wcag_from_disk(target_path)
    if not stored:
        click.secho(
            f"\n✗ No cached WCAG scan found for: {target_path}\n"
            "  Run 'maeris scan wcag' first.",
            fg="red",
        )
        sys.exit(1)

    if stored.status != "completed":
        click.secho(
            f"\n✗ WCAG scan is not finalized (status: {stored.status}).\n"
            "  Run 'maeris scan wcag' to completion first.",
            fg="red",
        )
        sys.exit(1)

    if stored.pushed_to_maeris:
        click.secho(
            f"\n✗ WCAG scan was already pushed to Maeris on {stored.pushed_at}.",
            fg="yellow",
        )
        sys.exit(0)

    findings_count = len(stored.result.findings)
    click.echo(f"\n  Found {findings_count} cached WCAG findings for: {target_path}\n")

    async def _push() -> None:
        from maeris_mcp.auth.token_store import TokenStore
        from maeris_mcp.maeris.client import MaerisClient

        config_dir = _repo_config_dir()
        os.environ["MAERIS_CONFIG_DIR"] = str(config_dir)
        token_store = TokenStore(config_dir=config_dir)
        application_id = token_store.get_app_id() or ""

        compliance_score = stored.result.summary.compliance_score or 0.0
        wcag_results = []
        for finding in stored.result.findings:
            wcag_results.append({
                "app_id": application_id,
                "component_id": finding.component_id or "",
                "page_id": finding.page_id or "",
                "impact": finding.impact.value,
                "rule_id": finding.rule_id,
                "tags": finding.tags,
                "description": finding.description,
                "help_url": finding.help_url,
                "nodes": finding.nodes,
                "result_type": finding.result_type.value,
            })

        async with MaerisClient() as client:
            await client.push_wcag_results(wcag_results, compliance_score=compliance_score)

        # Mark as pushed in disk cache
        stored.pushed_to_maeris = True
        from datetime import datetime, timezone
        stored.pushed_at = datetime.now(timezone.utc).isoformat()
        save_wcag_to_disk(target_path, stored)

        click.secho(f"\n  ✓ {len(wcag_results)} WCAG findings pushed to Maeris.\n", fg="green")

    try:
        with _spinner("Pushing WCAG findings to Maeris…"):
            asyncio.run(_push())
    except Exception as e:
        click.secho(f"\n✗ Failed to push WCAG findings: {e}", fg="red")
        sys.exit(1)

    _next_steps(
        "maeris report wcag --format html -o wcag.html   Export report",
    )


# ---------------------------------------------------------------------------
# maeris push spelling
# ---------------------------------------------------------------------------


@push_group.command("spelling")
@click.option("--source", "-s", default=None,
              help="Directory that was scanned (default: cwd).")
def push_spelling_cmd(source: str | None) -> None:
    """Push the last spell/grammar scan corrections to Maeris.

    \b
    Use this if a previous 'maeris scan spelling' completed the scan
    but failed to push (e.g. expired auth, network error).

    \b
    Examples:
      maeris push spelling
      maeris push spelling --source ../my-frontend
    """
    import asyncio

    from ._utils import _repo_config_dir, _spinner

    _check_auth()

    target_path = str(Path(source).resolve()) if source else os.getcwd()

    from maeris_mcp.storage.spell_grammar_store import load_spell_grammar_from_disk, save_spell_grammar_to_disk

    stored = load_spell_grammar_from_disk(target_path)
    if not stored:
        click.secho(
            f"\n✗ No cached spell/grammar scan found for: {target_path}\n"
            "  Run 'maeris scan spelling' first.",
            fg="red",
        )
        sys.exit(1)

    if stored.status != "completed":
        click.secho(
            f"\n✗ Spell/grammar scan is not finalized (status: {stored.status}).\n"
            "  Run 'maeris scan spelling' to completion first.",
            fg="red",
        )
        sys.exit(1)

    if stored.pushed_to_maeris:
        click.secho(
            f"\n✗ Spell/grammar scan was already pushed to Maeris on {stored.pushed_at}.",
            fg="yellow",
        )
        sys.exit(0)

    corrections_count = len(stored.result.corrections)
    click.echo(f"\n  Found {corrections_count} cached corrections for: {target_path}\n")

    async def _push() -> None:
        from maeris_mcp.auth.token_store import TokenStore
        from maeris_mcp.maeris.client import MaerisClient

        config_dir = _repo_config_dir()
        os.environ["MAERIS_CONFIG_DIR"] = str(config_dir)
        token_store = TokenStore(config_dir=config_dir)
        application_id = token_store.get_app_id() or ""

        corrections = []
        for c in stored.result.corrections:
            corrections.append({
                "app_id": application_id,
                "component_zoomed_image_path": "",
                "component_cropped_image_path": "",
                "page_image_path": "",
                "page_id": c.page_id or "",
                "page_name": c.page_name or "",
                "original_text": c.original_text,
                "corrected_text": c.corrected_text,
                "correction_type": c.correction_type,
                "original": c.incorrect,
                "corrected": c.correct,
            })

        async with MaerisClient() as client:
            await client.push_spell_grammar_results(corrections)

        # Mark as pushed in disk cache
        stored.pushed_to_maeris = True
        from datetime import datetime, timezone
        stored.pushed_at = datetime.now(timezone.utc).isoformat()
        save_spell_grammar_to_disk(target_path, stored)

        click.secho(f"\n  ✓ {len(corrections)} spell/grammar corrections pushed to Maeris.\n", fg="green")

    try:
        with _spinner("Pushing spell/grammar corrections to Maeris…"):
            asyncio.run(_push())
    except Exception as e:
        click.secho(f"\n✗ Failed to push spell/grammar corrections: {e}", fg="red")
        sys.exit(1)

    _next_steps(
        "maeris report spelling --format html -o spelling.html   Export report",
    )
