"""Report commands: generate exportable reports for tests, coverage, and security."""

import json
import os
import sys
from pathlib import Path

import click

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


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


def _write_output(content: str, output_path: str | None) -> None:
    if output_path:
        Path(output_path).write_text(content)
        click.secho(f"✓ Report written to {output_path}", fg="green")
    else:
        click.echo(content)


def _as_markdown_table(rows: list[dict], columns: list[str]) -> str:
    if not rows:
        return "_No records found._\n"
    header = " | ".join(columns)
    sep = " | ".join("---" for _ in columns)
    lines = [f"| {header} |", f"| {sep} |"]
    for row in rows:
        cells = " | ".join(str(row.get(c, "")) for c in columns)
        lines.append(f"| {cells} |")
    return "\n".join(lines) + "\n"


def _as_html_table(rows: list[dict], columns: list[str], title: str) -> str:
    rows_html = ""
    for row in rows:
        cells = "".join(f"<td>{row.get(c, '')}</td>" for c in columns)
        rows_html += f"<tr>{cells}</tr>\n"
    headers = "".join(f"<th>{c}</th>" for c in columns)
    return f"""<!DOCTYPE html>
<html><head><meta charset="utf-8"><title>{title}</title>
<style>body{{font-family:sans-serif;padding:24px}}table{{border-collapse:collapse;width:100%}}
th,td{{border:1px solid #ddd;padding:8px;text-align:left}}th{{background:#f3f4f6}}</style>
</head><body>
<h2>{title}</h2>
<table><thead><tr>{headers}</tr></thead>
<tbody>{rows_html}</tbody></table>
</body></html>"""


# ---------------------------------------------------------------------------
# report tests
# ---------------------------------------------------------------------------


@report_group.command("tests")
@click.option(
    "--format", "fmt",
    type=click.Choice(["json", "md", "html"]),
    default="json",
    show_default=True,
    help="Output format.",
)
@click.option("--output", "-o", default=None, help="Output file path (default: stdout).")
@click.option("--folder", "-f", "folder_filter", default="", help="Filter by folder/group name.")
@click.option("--name", "-n", "name_filter", default="", help="Filter by testcase name substring.")
def report_tests_cmd(fmt: str, output: str | None, folder_filter: str, name_filter: str) -> None:
    """Generate a report of all testcases.

    \b
    Examples:
      maeris report tests --format md --output tests_report.md
      maeris report tests --format html -o tests.html
      maeris report tests --folder smoke --format json
    """
    import asyncio
    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)

    async def _fetch() -> list[dict]:
        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()
            groups = await client.get_testcase_groups()
            return tests, groups

    try:
        with _spinner("Generating test report…"):

            tests, groups = asyncio.run(_fetch())
    except Exception as e:
        click.secho(f"✗ Failed to fetch testcases: {e}", fg="red")
        sys.exit(1)

    # Build group id → name map
    group_map = {g.get("testcase_group_id") or g.get("id") or g.get("_id", ""): g.get("name", "") for g in groups}

    # Filter
    if name_filter:
        tests = [t for t in tests if name_filter.lower() in (t.get("name") or "").lower()]
    if folder_filter:
        folder_lower = folder_filter.lower()
        valid_ids = {gid for gid, gname in group_map.items() if folder_lower in gname.lower()}
        tests = [t for t in tests if t.get("testcase_group_id") in valid_ids]

    columns = ["name", "testcase_group_id", "origin", "type"]

    # Enrich with folder name
    enriched = []
    for t in tests:
        row = {
            "name": t.get("name", ""),
            "folder": group_map.get(t.get("testcase_group_id", ""), ""),
            "origin": t.get("origin", ""),
            "type": t.get("type", ""),
            "id": t.get("testcase_id") or t.get("_id") or t.get("id", ""),
        }
        enriched.append(row)

    report_columns = ["name", "folder", "origin", "type", "id"]

    if fmt == "json":
        content = json.dumps({"total": len(enriched), "tests": enriched}, indent=2)
    elif fmt == "md":
        content = f"# Maeris Test Report\n\n**Total:** {len(enriched)} testcase(s)\n\n"
        content += _as_markdown_table(enriched, report_columns)
    else:  # html
        content = _as_html_table(enriched, report_columns, f"Maeris Test Report ({len(enriched)} tests)")

    _write_output(content, output)

    if not enriched:
        _next_steps(
            "maeris test create --url <app-url>   Generate tests first",
        )
    elif output:
        _next_steps(
            "maeris report coverage                Check test coverage",
        )


# ---------------------------------------------------------------------------
# report coverage
# ---------------------------------------------------------------------------


@report_group.command("coverage")
@click.option(
    "--format", "fmt",
    type=click.Choice(["json", "md", "html"]),
    default=None,
    help="Export format (json, md, html). Omit for terminal output.",
)
@click.option("--source", "-s", default=None, help="UI source directory (default: cwd).")
@click.option("--output", "-o", default=None, help="Output file path (default: stdout).")
def report_coverage_cmd(fmt: str | None, source: str | None, output: str | None) -> None:
    """Generate a coverage report (UI + API).

    \b
    Shows a clean terminal summary by default.
    Use --format to export as json, md, or html.

    \b
    Examples:
      maeris report coverage --format html -o coverage.html
      maeris report coverage --format json > coverage.json
      maeris report coverage --source ./src --format md -o coverage.md
    """
    import asyncio
    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)

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

    try:
        with _spinner("Generating coverage report…"):
            tests, apis, flows, folders = asyncio.run(_fetch())
    except Exception as e:
        click.secho(f"✗ Failed to fetch data: {e}", fg="red")
        sys.exit(1)

    scan_root = Path(source) if source else Path.cwd()

    # ── Test case breakdown ──────────────────────────────────────────────
    total_tests = len(tests)

    # By type (smoke, regression, etc.)
    type_counts: dict[str, int] = {}
    for tc in tests:
        tc_type = (
            tc.get("test_case_type") or tc.get("testcase_type") or "unspecified"
        ).strip().lower()
        type_counts[tc_type] = type_counts.get(tc_type, 0) + 1

    # By folder
    folder_id_to_name: dict[str, str] = {}
    for f in folders:
        fid = str(f.get("testcase_group_id") or f.get("id") or f.get("_id") or "")
        fname = f.get("name") or f.get("group_name") or "Unnamed"
        if fid:
            folder_id_to_name[fid] = fname

    folder_counts: dict[str, int] = {}
    unfiled_count = 0
    for tc in tests:
        gid = str(tc.get("testcase_group_id") or tc.get("folder_id") or "")
        if gid and gid in folder_id_to_name:
            name = folder_id_to_name[gid]
            folder_counts[name] = folder_counts.get(name, 0) + 1
        else:
            unfiled_count += 1

    # By functionality
    func_counts: dict[str, int] = {}
    no_func_count = 0
    for tc in tests:
        func = (
            tc.get("test_case_functionality")
            or tc.get("testcase_functionality")
            or ""
        ).strip()
        if func and func.lower() not in {"unknown", "n/a", "none", "-"}:
            func_counts[func] = func_counts.get(func, 0) + 1
        else:
            no_func_count += 1

    # ── UI coverage (reuse area discovery from coverage_cmds) ────────────
    from .coverage_cmds import _discover_ui_areas, _area_matches_functionality

    ui_areas = _discover_ui_areas(scan_root)
    ui_area_rows: list[dict] = []
    matched_funcs: set[str] = set()
    all_funcs = list(func_counts.keys())

    for area in ui_areas:
        best_func: str | None = None
        best_count = 0
        for func in all_funcs:
            if _area_matches_functionality(area, func):
                n = func_counts[func]
                if best_func is None or n > best_count:
                    best_func = func
                    best_count = n
        if best_func:
            matched_funcs.add(best_func)
            ui_area_rows.append({
                "area": area,
                "functionality": best_func,
                "tests": best_count,
                "covered": True,
            })
        else:
            ui_area_rows.append({
                "area": area,
                "functionality": None,
                "tests": 0,
                "covered": False,
            })

    ui_covered = sum(1 for r in ui_area_rows if r["covered"])
    ui_total = len(ui_area_rows)
    ui_pct = round(ui_covered / ui_total * 100, 1) if ui_total else 0.0

    orphaned_funcs = [
        {"functionality": f, "tests": func_counts[f]}
        for f in func_counts
        if f not in matched_funcs
    ]

    # ── API coverage ─────────────────────────────────────────────────────
    total_apis = len(apis)
    total_flows = len(flows)

    # Build api_id → flow names map
    api_ids_in_flows: dict[str, list[str]] = {}
    for flow in flows:
        flow_name = flow.get("name") or "unnamed"
        for step in flow.get("flow_map", []):
            aid = (
                step.get("child_id") or step.get("api_id")
                or step.get("api") or ""
            )
            if aid:
                api_ids_in_flows.setdefault(aid, []).append(flow_name)

    api_rows: list[dict] = []
    for api in apis:
        aid = str(api.get("api_id") or api.get("id") or api.get("_id") or "")
        name = api.get("name") or api.get("api_name") or aid
        method = (api.get("type") or api.get("method") or "").upper()
        url = api.get("url") or ""
        in_flows = api_ids_in_flows.get(aid, [])
        api_rows.append({
            "name": name,
            "method": method,
            "url": url,
            "in_flow": bool(in_flows),
            "flows": in_flows,
        })

    apis_in_flow = sum(1 for r in api_rows if r["in_flow"])
    api_flow_pct = round(apis_in_flow / total_apis * 100, 1) if total_apis else 0.0

    # ── Assemble full report ─────────────────────────────────────────────
    report = {
        "project": scan_root.name,
        "summary": {
            "total_test_cases": total_tests,
            "total_apis": total_apis,
            "total_flows": total_flows,
            "total_folders": len(folders),
            "ui_coverage_pct": ui_pct,
            "api_flow_coverage_pct": api_flow_pct,
        },
        "test_cases": {
            "by_type": type_counts,
            "by_folder": {**folder_counts, **({"(unfiled)": unfiled_count} if unfiled_count else {})},
            "by_functionality": func_counts,
            "without_functionality": no_func_count,
        },
        "ui_coverage": {
            "areas_discovered": ui_total,
            "areas_covered": ui_covered,
            "coverage_pct": ui_pct,
            "areas": ui_area_rows,
            "orphaned_functionalities": orphaned_funcs,
        },
        "api_coverage": {
            "total_apis": total_apis,
            "apis_in_flows": apis_in_flow,
            "apis_not_in_flows": total_apis - apis_in_flow,
            "coverage_pct": api_flow_pct,
            "apis": api_rows,
        },
    }

    if fmt is None and output is None:
        # Default: rich terminal output
        _coverage_terminal_output(report)
        return

    # Export mode
    effective_fmt = fmt or "json"
    if effective_fmt == "json":
        content = json.dumps(report, indent=2)
    elif effective_fmt == "md":
        content = _coverage_report_to_md(report)
    else:  # html
        content = _coverage_report_to_html(report)

    _write_output(content, output)


def _coverage_terminal_output(report: dict) -> None:
    """Rich, readable terminal output for the coverage report."""
    s = report["summary"]
    tc = report["test_cases"]
    ui = report["ui_coverage"]
    api = report["api_coverage"]

    def bar(pct: float, width: int = 20) -> str:
        filled = int(pct / (100 / width)) if pct > 0 else 0
        return f"{'█' * filled}{'░' * (width - filled)}"

    def color_pct(pct: float) -> str:
        color = "green" if pct >= 70 else "yellow" if pct >= 40 else "red"
        return click.style(f"{pct}%", fg=color)

    # ── Header ───────────────────────────────────────────────────────────
    click.echo()
    click.secho(f"  Coverage Report — {report['project']}", bold=True)
    click.echo(f"  {'─' * 50}")

    # ── Overview cards ───────────────────────────────────────────────────
    click.echo()
    click.echo(
        f"  {click.style(str(s['total_test_cases']), fg='cyan', bold=True)} test cases"
        f"   {click.style(str(s['total_apis']), fg='cyan', bold=True)} APIs"
        f"   {click.style(str(s['total_flows']), fg='cyan', bold=True)} flows"
        f"   {click.style(str(s['total_folders']), fg='cyan', bold=True)} folders"
    )

    # ── Test cases breakdown ─────────────────────────────────────────────
    if tc["by_type"] and not (len(tc["by_type"]) == 1 and "unspecified" in tc["by_type"]):
        click.echo()
        click.secho("  Tests by Type", bold=True)
        for t, c in sorted(tc["by_type"].items(), key=lambda x: -x[1]):
            click.echo(f"    {t.title():<20} {c}")

    if tc["by_folder"]:
        has_real_folders = any(k != "(unfiled)" for k in tc["by_folder"])
        if has_real_folders:
            click.echo()
            click.secho("  Tests by Folder", bold=True)
            for f, c in sorted(tc["by_folder"].items(), key=lambda x: -x[1]):
                click.echo(f"    {f:<30} {c}")

    if tc["by_functionality"]:
        click.echo()
        click.secho("  Tests by Functionality", bold=True)
        for f, c in sorted(tc["by_functionality"].items(), key=lambda x: -x[1]):
            click.echo(f"    {f:<30} {c}")

    # ── UI Coverage ──────────────────────────────────────────────────────
    if ui["areas"]:
        click.echo()
        click.secho("  UI Coverage", bold=True)
        click.echo(f"  {bar(ui['coverage_pct'])}  {color_pct(ui['coverage_pct'])}"
                    f"  ({ui['areas_covered']}/{ui['areas_discovered']} areas)")
        click.echo()

        for r in sorted(ui["areas"], key=lambda x: (not x["covered"], x["area"])):
            if r["covered"]:
                icon = click.style("  ✓", fg="green")
                detail = f"{r['tests']} test{'s' if r['tests'] != 1 else ''}"
            else:
                icon = click.style("  ✗", fg="red")
                detail = click.style("no tests", fg="bright_black")
            click.echo(f"  {icon}  {r['area']:<24} {detail}")

    if ui.get("orphaned_functionalities"):
        click.echo()
        click.secho("  Orphaned (tests without matching source area)", fg="yellow")
        for o in ui["orphaned_functionalities"]:
            click.echo(f"    · {o['functionality']}  ({o['tests']} tests)")

    # ── API Coverage ─────────────────────────────────────────────────────
    if api["apis"]:
        click.echo()
        click.secho("  API Flow Coverage", bold=True)
        click.echo(f"  {bar(api['coverage_pct'])}  {color_pct(api['coverage_pct'])}"
                    f"  ({api['apis_in_flows']}/{api['total_apis']} APIs in flows)")
        click.echo()

        # Show APIs grouped: in flow first, then not
        in_flow = [r for r in api["apis"] if r["in_flow"]]
        not_in_flow = [r for r in api["apis"] if not r["in_flow"]]

        if in_flow:
            for r in sorted(in_flow, key=lambda x: x["name"]):
                icon = click.style("  ✓", fg="green")
                flows_str = ", ".join(r["flows"])
                click.echo(f"  {icon}  {r['method']:<6} {r['name']:<28} → {flows_str}")

        if not_in_flow:
            for r in sorted(not_in_flow, key=lambda x: x["name"]):
                icon = click.style("  ·", fg="bright_black")
                click.echo(f"  {icon}  {r['method']:<6} {r['name']}")

    # ── Footer ───────────────────────────────────────────────────────────
    click.echo(f"\n  {'─' * 50}")
    click.secho(
        "  Export: maeris report coverage --format md -o report.md",
        fg="bright_black",
    )
    click.echo()


def _pct_bar(pct: float, width: int = 20) -> str:
    """Return a text-based progress bar: [████░░░░] 65%."""
    filled = int(pct / (100 / width)) if pct > 0 else 0
    return f"{'█' * filled}{'░' * (width - filled)}"


def _coverage_report_to_md(report: dict) -> str:
    """Render the coverage report as Markdown."""
    s = report["summary"]
    lines = [
        f"# Coverage Report — {report['project']}\n",
        "## Summary\n",
        f"| Metric | Value |",
        f"|---|---|",
        f"| Test cases | {s['total_test_cases']} |",
        f"| APIs | {s['total_apis']} |",
        f"| Flows | {s['total_flows']} |",
        f"| Folders | {s['total_folders']} |",
        f"| UI coverage | {s['ui_coverage_pct']}% |",
        f"| API flow coverage | {s['api_flow_coverage_pct']}% |",
        "",
    ]

    # Test cases by type
    tc = report["test_cases"]
    if tc["by_type"]:
        lines += ["## Test Cases by Type\n", "| Type | Count |", "|---|---|"]
        for t, c in sorted(tc["by_type"].items(), key=lambda x: -x[1]):
            lines.append(f"| {t.title()} | {c} |")
        lines.append("")

    # Test cases by folder
    if tc["by_folder"]:
        lines += ["## Test Cases by Folder\n", "| Folder | Count |", "|---|---|"]
        for f, c in sorted(tc["by_folder"].items(), key=lambda x: -x[1]):
            lines.append(f"| {f} | {c} |")
        lines.append("")

    # UI coverage
    ui = report["ui_coverage"]
    if ui["areas"]:
        lines += [
            f"## UI Coverage — {ui['areas_covered']}/{ui['areas_discovered']} areas ({ui['coverage_pct']}%)\n",
            "| Area | Functionality | Tests | Covered |",
            "|---|---|---|---|",
        ]
        for r in sorted(ui["areas"], key=lambda x: (not x["covered"], x["area"])):
            func = r["functionality"] or "—"
            covered = "Yes" if r["covered"] else "No"
            lines.append(f"| {r['area']} | {func} | {r['tests']} | {covered} |")
        lines.append("")

    if ui["orphaned_functionalities"]:
        lines += [
            "### Orphaned Functionalities (tests with no matching source area)\n",
            "| Functionality | Tests |",
            "|---|---|",
        ]
        for o in ui["orphaned_functionalities"]:
            lines.append(f"| {o['functionality']} | {o['tests']} |")
        lines.append("")

    # API coverage
    api = report["api_coverage"]
    if api["apis"]:
        lines += [
            f"## API Flow Coverage — {api['apis_in_flows']}/{api['total_apis']} APIs ({api['coverage_pct']}%)\n",
            "| API | Method | URL | In Flow | Flows |",
            "|---|---|---|---|---|",
        ]
        for r in sorted(api["apis"], key=lambda x: (not x["in_flow"], x["name"])):
            in_f = "Yes" if r["in_flow"] else "No"
            flow_str = ", ".join(r["flows"]) if r["flows"] else "—"
            lines.append(f"| {r['name']} | {r['method']} | {r['url']} | {in_f} | {flow_str} |")
        lines.append("")

    return "\n".join(lines)


def _coverage_report_to_html(report: dict) -> str:
    """Render the coverage report as a standalone HTML page."""
    s = report["summary"]
    tc = report["test_cases"]
    ui = report["ui_coverage"]
    api = report["api_coverage"]

    def _table(headers: list[str], rows: list[list[str]]) -> str:
        h = "".join(f"<th>{h}</th>" for h in headers)
        body = ""
        for row in rows:
            body += "<tr>" + "".join(f"<td>{c}</td>" for c in row) + "</tr>\n"
        return f"<table><thead><tr>{h}</tr></thead><tbody>{body}</tbody></table>"

    sections = []

    # Summary
    sections.append("<h2>Summary</h2>")
    sections.append(_table(
        ["Metric", "Value"],
        [
            ["Test cases", str(s["total_test_cases"])],
            ["APIs", str(s["total_apis"])],
            ["Flows", str(s["total_flows"])],
            ["Folders", str(s["total_folders"])],
            ["UI coverage", f"{s['ui_coverage_pct']}%"],
            ["API flow coverage", f"{s['api_flow_coverage_pct']}%"],
        ],
    ))

    # Test cases by type
    if tc["by_type"]:
        sections.append("<h2>Test Cases by Type</h2>")
        rows = [[t.title(), str(c)] for t, c in sorted(tc["by_type"].items(), key=lambda x: -x[1])]
        sections.append(_table(["Type", "Count"], rows))

    # Test cases by folder
    if tc["by_folder"]:
        sections.append("<h2>Test Cases by Folder</h2>")
        rows = [[f, str(c)] for f, c in sorted(tc["by_folder"].items(), key=lambda x: -x[1])]
        sections.append(_table(["Folder", "Count"], rows))

    # UI coverage
    if ui["areas"]:
        sections.append(
            f"<h2>UI Coverage — {ui['areas_covered']}/{ui['areas_discovered']} areas"
            f" ({ui['coverage_pct']}%)</h2>"
        )
        rows = []
        for r in sorted(ui["areas"], key=lambda x: (not x["covered"], x["area"])):
            color = "#2e7d32" if r["covered"] else "#c62828"
            status = "Yes" if r["covered"] else "No"
            rows.append([
                r["area"],
                r["functionality"] or "—",
                str(r["tests"]),
                f'<span style="color:{color}">{status}</span>',
            ])
        sections.append(_table(["Area", "Functionality", "Tests", "Covered"], rows))

    # API coverage
    if api["apis"]:
        sections.append(
            f"<h2>API Flow Coverage — {api['apis_in_flows']}/{api['total_apis']}"
            f" ({api['coverage_pct']}%)</h2>"
        )
        rows = []
        for r in sorted(api["apis"], key=lambda x: (not x["in_flow"], x["name"])):
            color = "#2e7d32" if r["in_flow"] else "#c62828"
            status = "Yes" if r["in_flow"] else "No"
            rows.append([
                r["name"],
                r["method"],
                r["url"],
                f'<span style="color:{color}">{status}</span>',
                ", ".join(r["flows"]) if r["flows"] else "—",
            ])
        sections.append(_table(["API", "Method", "URL", "In Flow", "Flows"], rows))

    body = "\n".join(sections)
    return f"""<!DOCTYPE html>
<html><head>
<meta charset="utf-8">
<title>Coverage Report — {report['project']}</title>
<style>
  body {{ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
         max-width: 960px; margin: 40px auto; padding: 0 20px; color: #333; }}
  h1 {{ border-bottom: 2px solid #1a73e8; padding-bottom: 8px; }}
  h2 {{ color: #1a73e8; margin-top: 32px; }}
  table {{ border-collapse: collapse; width: 100%; margin: 12px 0; }}
  th, td {{ border: 1px solid #ddd; padding: 8px 12px; text-align: left; }}
  th {{ background: #f5f5f5; font-weight: 600; }}
  tr:nth-child(even) {{ background: #fafafa; }}
</style>
</head><body>
<h1>Coverage Report — {report['project']}</h1>
{body}
</body></html>"""


# ---------------------------------------------------------------------------
# report security
# ---------------------------------------------------------------------------


@report_group.command("security")
@click.option(
    "--format", "fmt",
    type=click.Choice(["json", "md", "html"]),
    default="json",
    show_default=True,
    help="Output format.",
)
@click.option("--output", "-o", default=None, help="Output file path (default: stdout).")
def report_security_cmd(fmt: str, output: str | None) -> None:
    """Generate a formatted security scan report.

    Run 'maeris scan security' first to populate the latest results,
    then use this command to export them in a shareable format.

    \b
    Examples:
      maeris report security --format html -o security.html
      maeris report security --format md -o SECURITY_REPORT.md
    """
    import asyncio
    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)

    async def _fetch() -> list[dict]:
        from maeris_mcp.maeris.client import MaerisClient
        os.environ["MAERIS_CONFIG_DIR"] = str(config_dir)
        async with MaerisClient() as client:
            return await client.get_security_issues()

    try:
        with _spinner("Generating security report…"):

            issues = asyncio.run(_fetch())
    except Exception as e:
        # Security issues endpoint may not exist — advise the user
        click.secho(
            f"✗ Could not fetch security issues: {e}\n"
            "  Run 'maeris scan security' first to generate results.",
            fg="red",
        )
        sys.exit(1)

    columns = ["severity", "category", "file", "line", "description"]

    if fmt == "json":
        content = json.dumps({"total": len(issues), "issues": issues}, indent=2)
    elif fmt == "md":
        content = f"# Maeris Security Report\n\n**Issues found:** {len(issues)}\n\n"
        content += _as_markdown_table(issues, columns)
    else:  # html
        content = _as_html_table(issues, columns, f"Maeris Security Report ({len(issues)} issues)")

    _write_output(content, output)

    if not issues:
        _next_steps(
            "maeris scan security   Run a security scan first",
        )
    elif output:
        _next_steps(
            "maeris scan security   Re-scan after applying fixes",
        )
