"""Run compound report presets — multiple commands in one shot.

Output formats: text (default), ``--json``. SARIF is deliberately NOT
emitted because cmd_report is a recipe-composer (chains preset
sub-commands like map + health + weather + layers + coupling into a
single structured-JSON envelope). The composed sub-commands emit their
own ``--sarif`` when applicable; cmd_report rolls them up into an
invocation-scoped multi-section summary — not per-location violations.
See ``cmd_audit`` for the parallel composer disclosure pattern (W1085 /
W1145) + action.yml _SUPPORTED_SARIF allowlist + W1221-audit memo.
"""

from __future__ import annotations

import json
import subprocess
import sys
import time

import click

from roam.ask.workflow import workflow_metadata_for_recipe
from roam.capability import roam_capability
from roam.commands.resolve import ensure_index
from roam.db.connection import find_project_root
from roam.output.formatter import json_envelope, to_json

# ---------------------------------------------------------------------------
# Built-in presets
# ---------------------------------------------------------------------------

PRESETS = {
    "first-contact": {
        "description": "Initial codebase overview — map, health, weather, layers, coupling",
        "sections": [
            {"title": "Map", "command": ["map"]},
            {"title": "Health", "command": ["health"]},
            {"title": "Weather", "command": ["weather"]},
            {"title": "Layers", "command": ["layers"]},
            {"title": "Coupling", "command": ["coupling"]},
        ],
    },
    "security": {
        "description": "Security audit — risk analysis, coverage gaps, secret scan",
        "sections": [
            {"title": "Risk", "command": ["risk", "-n", "20"]},
            {
                "title": "Coverage Gaps",
                "command": ["coverage-gaps", "--gate-pattern", "auth|permission|guard"],
            },
            {
                "title": "Secret Scan",
                "command": ["grep", "password|secret|token|api.key", "--source-only", "-n", "30"],
            },
        ],
    },
    "pre-pr": {
        "description": "Pre-PR checklist — risk, blast radius, coupling check",
        "sections": [
            {"title": "PR Risk", "command": ["pr-risk", "--staged"]},
            {"title": "Blast Radius", "command": ["diff", "--staged"]},
            {"title": "Coupling Check", "command": ["coupling"]},
        ],
    },
    "refactor": {
        "description": "Refactoring analysis — weather, dead code, fan analysis, health",
        "sections": [
            {"title": "Weather", "command": ["weather"]},
            {"title": "Dead Code", "command": ["dead", "--summary"]},
            {"title": "Fan Analysis", "command": ["fan"]},
            {"title": "Health", "command": ["health"]},
        ],
    },
    "guardian": {
        "description": ("Continuous architecture guardian baseline — snapshot, gates, trends, ownership drift"),
        "sections": [
            {"title": "Snapshot", "command": ["trends", "--save", "--tag", "guardian"]},
            {"title": "Health Gate", "command": ["health", "--gate"]},
            {"title": "Trend Analysis", "command": ["trends", "--analyze", "--range", "20"]},
            {"title": "Metric Trends", "command": ["trends", "--days", "30"]},
            {
                "title": "Ownership Drift",
                "command": ["drift", "--threshold", "0.5", "--limit", "20"],
            },
        ],
    },
}

_PRESET_WORKFLOW_RECIPES = {
    "first-contact": "onboard",
    "security": "security-audit",
    "pre-pr": "verify-patch",
    "refactor": "hot-spots",
    "guardian": "what-broke",
}


def _load_custom_presets(config_path: str) -> dict:
    """Load custom report presets from a JSON file.

    Expected format:
    {
        "my-preset": {
            "description": "My custom report",
            "sections": [
                {"title": "Health Check", "command": ["health"]},
                {"title": "Risk Analysis", "command": ["risk", "-n", "20"]}
            ]
        }
    }
    """
    with open(config_path) as f:
        try:
            data = json.load(f)
        except json.JSONDecodeError as exc:
            raise click.BadParameter(f"Invalid JSON in config file: {exc}") from exc

    if not isinstance(data, dict):
        raise click.BadParameter(f"Config must be a JSON object, got {type(data).__name__}")

    for name, preset in data.items():
        if "sections" not in preset:
            raise click.BadParameter(f"Preset '{name}' missing 'sections' key")
        if not isinstance(preset["sections"], list):
            raise click.BadParameter(f"Preset '{name}' sections must be a list")
        for i, section in enumerate(preset["sections"]):
            if "title" not in section or "command" not in section:
                raise click.BadParameter(f"Preset '{name}' section {i} needs 'title' and 'command' keys")
        # Add default description if missing
        if "description" not in preset:
            preset["description"] = f"Custom preset: {name}"

    return data


def _run_section(section, root):
    """Run a single report section as a subprocess.

    Returns (title, success, output_data, stderr).
    """
    cmd = [sys.executable, "-m", "roam", "--json"] + section["command"]
    try:
        result = subprocess.run(
            cmd,
            cwd=str(root),
            capture_output=True,
            text=True,
            timeout=180,
            encoding="utf-8",
            errors="replace",
        )
        output = result.stdout.strip()
        try:
            data = json.loads(output) if output else None
        except json.JSONDecodeError:
            data = {"raw": output}

        return (
            section["title"],
            result.returncode == 0,
            data,
            result.stderr.strip() if result.returncode != 0 else "",
        )
    except subprocess.TimeoutExpired:
        return (section["title"], False, None, "timeout (180s)")
    except Exception as e:
        return (section["title"], False, None, str(e))


def _format_markdown(preset_name, results):
    """Format results as GitHub-compatible markdown."""
    lines = [f"## Roam Report: {preset_name}\n"]
    workflow = _workflow_for_preset(preset_name)
    if workflow:
        lines.append(f"Workflow: `{workflow['recipe']}` / `{workflow['phase']}`")
        lines.append("")

    for title, success, data, stderr in results:
        status = "pass" if success else "FAIL"
        lines.append(f"### {title} [{status}]")

        if not success:
            lines.append(f"\n> Error: {stderr}\n")
            continue

        if data and isinstance(data, dict):
            summary = data.get("summary", {})
            if summary:
                parts = [f"**{k}**: {v}" for k, v in summary.items()]
                lines.append(", ".join(parts))
            lines.append("")
            lines.append("<details><summary>Full output</summary>\n")
            lines.append("```json")
            lines.append(json.dumps(data, indent=2, default=str)[:2000])
            lines.append("```")
            lines.append("</details>\n")
        else:
            lines.append("_(no data)_\n")

    return "\n".join(lines)


def _workflow_for_preset(preset: str) -> dict | None:
    recipe = _PRESET_WORKFLOW_RECIPES.get(preset)
    if not recipe:
        return None
    return workflow_metadata_for_recipe(recipe)


@roam_capability(
    name="report",
    category="reports",
    summary="Run a compound report preset — multiple commands in one shot",
    maturity="stable",
    mcp_expose=True,
    mcp_preset=("core",),
    side_effect=False,
    task_required=False,
    destructive=False,
    stale_sensitive=True,
    ai_safe=True,
    requires_index=True,
)
@click.command()
@click.argument("preset", required=False, default=None)
@click.option("--list", "list_presets", is_flag=True, help="List available presets")
@click.option("--strict", is_flag=True, help="Exit non-zero if any section fails")
@click.option("--md", "markdown", is_flag=True, help="Output GitHub-compatible markdown")
@click.option(
    "--config",
    "config_path",
    type=click.Path(exists=True),
    help="Load custom presets from a JSON file",
)
@click.pass_context
def report(ctx, preset, list_presets, strict, markdown, config_path):
    """Run a compound report preset — multiple commands in one shot.

    Built-in presets: first-contact, security, pre-pr, refactor, guardian.

    Unlike running individual commands, this command orchestrates multiple
    analysis commands into compound report presets (first-contact, security,
    pre-pr, refactor, guardian) with ``--md`` for CI-friendly Markdown and
    ``--strict`` for exit-code gating.
    """
    json_mode = ctx.obj.get("json") if ctx.obj else False

    all_presets = dict(PRESETS)  # copy built-in
    if config_path:
        custom = _load_custom_presets(config_path)
        all_presets.update(custom)

    if list_presets:
        if json_mode:
            click.echo(
                to_json(
                    json_envelope(
                        "report",
                        summary={
                            "verdict": f"{len(all_presets)} presets available",
                            "presets": len(all_presets),
                        },
                        presets={name: p["description"] for name, p in all_presets.items()},
                        workflows={
                            name: workflow
                            for name in all_presets
                            if (workflow := _workflow_for_preset(name)) is not None
                        },
                    )
                )
            )
        else:
            click.echo(f"VERDICT: {len(all_presets)} presets available")
            click.echo()
            click.echo("=== Available Report Presets ===\n")
            for name, p in all_presets.items():
                sections = ", ".join(s["title"] for s in p["sections"])
                click.echo(f"  {name:<16s}  {p['description']}")
                click.echo(f"    sections: {sections}")
                workflow = _workflow_for_preset(name)
                if workflow:
                    click.echo(
                        f"    workflow: {workflow['recipe']} "
                        f"({workflow['phase']}; {', '.join(workflow['perspectives'])})"
                    )
        return

    if not preset:
        if json_mode:
            click.echo(
                to_json(
                    json_envelope(
                        "report",
                        summary={
                            "verdict": "no report preset provided",
                            "state": "usage_error",
                            "partial_success": True,
                        },
                        status="usage_error",
                        isError=True,
                        error_code="USAGE_ERROR",
                        error="no report preset provided",
                        hint="Pass a preset name, e.g. 'roam report <preset>'.",
                        available_presets=list(all_presets.keys()),
                    )
                )
            )
            raise SystemExit(1)
        click.echo("Usage: roam report <preset>")
        click.echo("Available presets: " + ", ".join(all_presets.keys()))
        click.echo("Use --list for details.")
        raise SystemExit(1)

    if preset not in all_presets:
        click.echo(f"Unknown preset: {preset}")
        click.echo("Available: " + ", ".join(all_presets.keys()))
        raise SystemExit(1)

    ensure_index()
    root = find_project_root()
    preset_data = all_presets[preset]
    workflow = _workflow_for_preset(preset)
    t0 = time.monotonic()

    results = []
    for section in preset_data["sections"]:
        title, success, data, stderr = _run_section(section, root)
        results.append((title, success, data, stderr))

    elapsed = time.monotonic() - t0
    ok_count = sum(1 for _, s, _, _ in results if s)
    fail_count = sum(1 for _, s, _, _ in results if not s)

    if markdown:
        click.echo(_format_markdown(preset, results))
        if strict and fail_count > 0:
            raise SystemExit(1)
        return

    _report_verdict = (
        f"report '{preset}': {ok_count}/{len(results)} sections OK"
        + (f", {fail_count} failed" if fail_count > 0 else "")
        + f" in {elapsed:.1f}s"
    )

    if json_mode:
        click.echo(
            to_json(
                json_envelope(
                    "report",
                    summary={
                        "verdict": _report_verdict,
                        "preset": preset,
                        "sections_ok": ok_count,
                        "sections_failed": fail_count,
                        "elapsed_s": round(elapsed, 1),
                    },
                    preset=preset,
                    workflow=workflow,
                    sections=[
                        {
                            "title": title,
                            "success": success,
                            "data": data,
                            "error": stderr if not success else None,
                        }
                        for title, success, data, stderr in results
                    ],
                )
            )
        )
        if strict and fail_count > 0:
            raise SystemExit(1)
        return

    # --- Text output ---
    click.echo(f"VERDICT: {_report_verdict}")
    click.echo()
    click.echo(f"=== Report: {preset} ({ok_count}/{len(results)} OK, {elapsed:.1f}s) ===\n")

    for title, success, data, stderr in results:
        status = "OK" if success else "FAIL"
        click.echo(f"--- {title} [{status}] ---")

        if not success:
            click.echo(f"  Error: {stderr}")
            click.echo()
            continue

        if data and isinstance(data, dict):
            summary = data.get("summary", {})
            if summary:
                parts = [f"{k}={v}" for k, v in summary.items()]
                click.echo(f"  {', '.join(parts)}")
            else:
                click.echo("  (completed)")
        else:
            click.echo("  (completed)")
        click.echo()

    if strict and fail_count > 0:
        click.echo(f"\nSTRICT: {fail_count} section(s) failed.")
        raise SystemExit(1)
