"""Aegis CLI entry point."""

from __future__ import annotations

import argparse
import json
import sys
import time
from pathlib import Path
from typing import TYPE_CHECKING

from aegis.cli import colors
from aegis.runtime.audit import AuditLogger

if TYPE_CHECKING:
    from aegis.core.regulatory import ComplianceGapAnalysis


def _get_version() -> str:
    """Return the package version string."""
    from aegis import __version__

    return __version__


_INIT_POLICY = """\
# Aegis Policy — generated by `aegis init`
# Docs: https://acacian.github.io/aegis/guides/policies/

version: "1"

defaults:
  risk_level: medium
  approval: approve

rules:
  # Read-only operations: auto-execute
  - name: read_auto
    match:
      type: "read*"
    risk_level: low
    approval: auto

  - name: get_auto
    match:
      type: "get*"
    risk_level: low
    approval: auto

  # Write operations: require human approval
  - name: write_approve
    match:
      type: "write*"
    risk_level: medium
    approval: approve

  - name: update_approve
    match:
      type: "update*"
    risk_level: medium
    approval: approve

  # Bulk operations: high risk
  - name: bulk_high
    match:
      type: "bulk_*"
    risk_level: high
    approval: approve

  # Destructive operations: always blocked
  - name: delete_block
    match:
      type: "delete*"
    risk_level: critical
    approval: block
"""


def main(argv: list[str] | None = None) -> None:
    """CLI entry point for ``aegis`` command."""
    parser = argparse.ArgumentParser(
        prog="aegis",
        description="Aegis: Policy & approval runtime for AI agents",
    )
    parser.add_argument(
        "--version",
        action="version",
        version=f"%(prog)s {_get_version()}",
    )
    parser.add_argument(
        "--no-color",
        action="store_true",
        default=False,
        help="Disable colored output",
    )
    subparsers = parser.add_subparsers(dest="command")

    # aegis audit
    audit_parser = subparsers.add_parser("audit", help="View the audit log")
    audit_parser.add_argument("--db", default="aegis_audit.db", help="Database path")
    audit_parser.add_argument("--session", help="Filter by session ID")
    audit_parser.add_argument("--action-type", help="Filter by action type")
    audit_parser.add_argument("--risk-level", help="Filter by risk level")
    audit_parser.add_argument(
        "--format",
        choices=["table", "json", "jsonl"],
        default="table",
        dest="fmt",
        help="Output format (default: table)",
    )
    audit_parser.add_argument("--output", "-o", help="Output file path (for jsonl export)")
    audit_parser.add_argument(
        "--tail",
        action="store_true",
        default=False,
        help="Live-tail new audit entries (poll every 1s, Ctrl+C to stop)",
    )
    audit_parser.add_argument(
        "--verify",
        action="store_true",
        default=False,
        help="Verify cryptographic audit chain integrity",
    )
    audit_parser.add_argument(
        "--export-chain",
        metavar="PATH",
        help="Export audit entries as a crypto-signed JSONL chain",
    )
    audit_parser.add_argument(
        "--verify-chain",
        metavar="PATH",
        help="Verify an exported crypto-audit JSONL chain file",
    )
    audit_parser.add_argument(
        "--evidence",
        metavar="PATH",
        help="Generate SOC2/EU AI Act evidence package to directory",
    )

    # aegis validate
    validate_parser = subparsers.add_parser("validate", help="Validate a policy file")
    validate_parser.add_argument("policy_file", help="Path to policy YAML file")

    # aegis schema
    subparsers.add_parser("schema", help="Print the policy JSON Schema")

    # aegis init
    init_parser = subparsers.add_parser("init", help="Generate a starter policy file")
    init_parser.add_argument("--output", "-o", default="policy.yaml", help="Output file path")
    init_parser.add_argument(
        "--with-tests",
        action="store_true",
        default=False,
        help="Also generate a matching test suite YAML",
    )
    init_parser.add_argument(
        "--test-output",
        default="tests.yaml",
        help="Output path for generated test suite (default: tests.yaml)",
    )

    # aegis simulate
    sim_parser = subparsers.add_parser(
        "simulate",
        help="Test actions against a policy without executing",
    )
    sim_parser.add_argument("policy_file", help="Path to policy YAML file")
    sim_parser.add_argument(
        "actions",
        nargs="+",
        help="Actions as type:target (e.g. read:crm delete:db)",
    )
    sim_parser.add_argument(
        "--format",
        choices=["table", "json"],
        default="table",
        dest="fmt",
        help="Output format (default: table)",
    )

    # aegis score
    score_parser = subparsers.add_parser(
        "score",
        help="Calculate governance score for a policy file",
    )
    score_parser.add_argument("policy_file", help="Path to policy YAML file")
    score_parser.add_argument(
        "--format",
        choices=["table", "json"],
        default="table",
        dest="fmt",
        help="Output format (default: table)",
    )
    score_parser.add_argument(
        "--save-baseline",
        action="store_true",
        default=False,
        help="Save current score as baseline to .aegis-baseline.json",
    )
    score_parser.add_argument(
        "--check-regression",
        action="store_true",
        default=False,
        help="Compare current score against saved baseline (exit 1 if dropped)",
    )

    # aegis scan
    scan_parser = subparsers.add_parser(
        "scan",
        help="Scan Python files for ungoverned AI agent tool calls",
    )
    scan_parser.add_argument(
        "directory",
        nargs="?",
        default=".",
        help="Directory to scan (default: current directory)",
    )

    # aegis serve
    serve_parser = subparsers.add_parser(
        "serve",
        help="Start the Aegis REST API server",
    )
    serve_parser.add_argument("policy_file", help="Path to policy YAML file")
    serve_parser.add_argument("--host", default="127.0.0.1", help="Bind host")
    serve_parser.add_argument("--port", type=int, default=8000, help="Bind port")
    serve_parser.add_argument("--audit-db", help="Audit database path")
    serve_parser.add_argument(
        "--no-dashboard", action="store_true", help="Disable the web dashboard"
    )
    serve_parser.add_argument(
        "--seed-demo",
        type=int,
        metavar="N",
        help="Seed N demo audit entries before starting",
    )

    # aegis monitor
    monitor_parser = subparsers.add_parser(
        "monitor",
        help="Live agent monitoring dashboard (poll audit DB)",
    )
    monitor_parser.add_argument("--db", default="aegis_audit.db", help="Audit database path")
    monitor_parser.add_argument(
        "--interval",
        type=float,
        default=2.0,
        help="Refresh interval in seconds (default: 2)",
    )

    # aegis diff
    from aegis.cli.diff import register as _register_diff

    _register_diff(subparsers)

    # aegis plan
    from aegis.cli.plan import register as _register_plan

    _register_plan(subparsers)

    # aegis test
    from aegis.cli.test_cmd import register as _register_test

    _register_test(subparsers)

    # aegis compliance-report  (evidence auto-generation)
    from aegis.cli.compliance_cmd import register as _register_compliance_report

    _register_compliance_report(subparsers)

    # aegis compliance
    compliance_parser = subparsers.add_parser(
        "compliance",
        help="Generate compliance reports from audit logs",
    )
    compliance_parser.add_argument(
        "audit_file",
        help="Path to audit JSONL file (exported via `aegis audit -o`)",
    )
    compliance_parser.add_argument(
        "--type",
        choices=["soc2", "gdpr", "governance"],
        default="governance",
        dest="report_type",
        help="Report type (default: governance)",
    )
    compliance_parser.add_argument(
        "--format",
        choices=["markdown", "json", "html"],
        default="markdown",
        dest="fmt",
        help="Output format (default: markdown)",
    )
    compliance_parser.add_argument(
        "--output",
        "-o",
        default=None,
        help="Output file path (useful for HTML reports)",
    )
    compliance_parser.add_argument(
        "--policy",
        default=None,
        help="Optional policy YAML file for context",
    )

    # aegis regulatory
    reg_parser = subparsers.add_parser(
        "regulatory",
        help="Run regulatory compliance gap analysis",
    )
    reg_parser.add_argument(
        "--framework",
        choices=["eu-ai-act", "nist", "soc2", "iso-42001", "owasp-agentic", "all"],
        default="all",
    )
    reg_parser.add_argument(
        "--format",
        choices=["table", "json", "markdown", "html"],
        default="table",
        dest="fmt",
    )
    reg_parser.add_argument(
        "--output",
        "-o",
        default=None,
        help="Output file path (useful for HTML reports)",
    )
    reg_parser.add_argument(
        "--features",
        help="Comma-separated list of enabled features (default: all)",
    )

    # aegis autopolicy
    auto_parser = subparsers.add_parser(
        "autopolicy",
        help="Generate a YAML policy from natural language description",
    )
    auto_parser.add_argument(
        "description",
        help='Natural language policy description (e.g. "block deletes, allow reads")',
    )
    auto_parser.add_argument(
        "-o",
        "--output",
        default=None,
        help="Output file path (default: print to stdout)",
    )
    auto_parser.add_argument(
        "--format",
        choices=["yaml", "json"],
        default="yaml",
        dest="fmt",
    )

    # aegis probe
    probe_parser = subparsers.add_parser(
        "probe",
        help="Run adversarial tests against a policy to find governance gaps",
    )
    probe_parser.add_argument("policy_file", help="Path to YAML policy file")
    probe_parser.add_argument(
        "--format",
        choices=["table", "json"],
        default="table",
        dest="fmt",
    )

    # aegis stats
    stats_parser = subparsers.add_parser(
        "stats",
        help="Show policy rule statistics from the audit DB",
    )
    stats_parser.add_argument("--db", default="aegis_audit.db", help="Database path")
    stats_parser.add_argument(
        "--format",
        choices=["table", "json"],
        default="table",
        dest="fmt",
        help="Output format (default: table)",
    )

    # aegis uninstall-impact
    impact_parser = subparsers.add_parser(
        "uninstall-impact",
        help="Show what would be lost if Aegis is removed",
    )
    impact_parser.add_argument("--db", default="aegis_audit.db", help="Database path")

    args = parser.parse_args(argv)

    # Handle --no-color flag
    if args.no_color:
        colors.force_color(False)

    if args.command == "audit":
        _cmd_audit(args)
    elif args.command == "validate":
        _cmd_validate(args)
    elif args.command == "schema":
        _cmd_schema()
    elif args.command == "init":
        _cmd_init(args)
    elif args.command == "simulate":
        _cmd_simulate(args)
    elif args.command == "score":
        _cmd_score(args)
    elif args.command == "scan":
        _cmd_scan(args)
    elif args.command == "serve":
        _cmd_serve(args)
    elif args.command == "monitor":
        _cmd_monitor(args)
    elif args.command == "diff":
        from aegis.cli.diff import run as _run_diff

        _run_diff(args)
    elif args.command == "plan":
        from aegis.cli.plan import run as _run_plan

        _run_plan(args)
    elif args.command == "test":
        from aegis.cli.test_cmd import run as _run_test

        _run_test(args)
    elif args.command == "compliance-report":
        from aegis.cli.compliance_cmd import run as _run_compliance_report

        _run_compliance_report(args)
    elif args.command == "compliance":
        _cmd_compliance(args)
    elif args.command == "stats":
        _cmd_stats(args)
    elif args.command == "regulatory":
        _cmd_regulatory(args)
    elif args.command == "autopolicy":
        _cmd_autopolicy(args)
    elif args.command == "probe":
        _cmd_probe(args)
    elif args.command == "uninstall-impact":
        _cmd_uninstall_impact(args)
    else:
        parser.print_help()


def _cmd_audit(args: argparse.Namespace) -> None:
    """Display audit log entries."""
    db_path = Path(args.db)
    if not db_path.exists():
        print(f"Database not found: {db_path}", file=sys.stderr)
        print("Run your agent with Aegis first to generate audit data.", file=sys.stderr)
        sys.exit(1)

    # Crypto-audit chain operations (don't need AuditLogger)
    if args.verify_chain:
        _verify_chain_file(Path(args.verify_chain))
        return
    if args.evidence:
        _generate_evidence(db_path, Path(args.evidence))
        return

    logger = AuditLogger(db_path=args.db)

    # Export or verify crypto chain from DB
    if args.export_chain:
        _export_crypto_chain(logger, Path(args.export_chain))
        logger.close()
        return
    if args.verify:
        _verify_audit_db(logger)
        logger.close()
        return

    if args.tail:
        _audit_tail(logger, args)
        return

    if args.fmt == "jsonl":
        output = args.output or "aegis_audit.jsonl"
        count = logger.export_jsonl(output, session_id=args.session)
        logger.close()
        print(f"Exported {count} entries to {output}")
        return

    entries = logger.get_log(
        session_id=args.session,
        action_type=getattr(args, "action_type", None),
        risk_level=getattr(args, "risk_level", None),
    )
    logger.close()

    if not entries:
        print("No audit entries found.")
        return

    if args.fmt == "json":
        print(json.dumps(entries, indent=2))
        return

    # Table format
    _print_audit_table(entries)


def _print_audit_table(entries: list[dict[str, object]]) -> None:
    """Print audit entries as a colored table."""
    cols = [
        f"{'ID':>4}",
        f"{'Session':>12}",
        f"{'Action':>15}",
        f"{'Target':>15}",
        f"{'Risk':>8}",
        f"{'Decision':>10}",
        f"{'Result':>10}",
    ]
    header = " ".join(cols)
    print(colors.bold(header))
    print("-" * len(header))
    for e in entries:
        risk_str = str(e.get("risk_level", ""))
        risk_display = colors.risk_color(f"{risk_str:>8}")
        result_str = str(e.get("result_status") or "-")
        result_display = colors.status_color(f"{result_str:>10}")
        decision_str = str(e.get("human_decision") or e.get("approval", ""))
        print(
            f"{e['id']:>4} {e['session_id']:>12} {e['action_type']:>15} "
            f"{e['action_target']:>15} {risk_display} "
            f"{decision_str:>10} "
            f"{result_display}"
        )


def _audit_tail(logger: AuditLogger, args: argparse.Namespace) -> None:
    """Live-tail audit entries, polling every 1 second."""
    last_id = 0

    # Get current max ID to start tailing from
    entries = logger.get_log(
        session_id=args.session,
        action_type=getattr(args, "action_type", None),
        risk_level=getattr(args, "risk_level", None),
    )
    if entries:
        last_id = int(entries[-1].get("id", 0))  # type: ignore[call-overload]

    # Print header
    cols = [
        f"{'ID':>4}",
        f"{'Session':>12}",
        f"{'Action':>15}",
        f"{'Target':>15}",
        f"{'Risk':>8}",
        f"{'Decision':>10}",
        f"{'Result':>10}",
    ]
    header = " ".join(cols)
    print(colors.bold(header))
    print("-" * len(header))
    print(colors.cyan("[tail] Watching for new entries... (Ctrl+C to stop)"))
    sys.stdout.flush()

    try:
        while True:
            new_entries = _poll_new_entries(logger, last_id, args)
            for e in new_entries:
                entry_id = int(e.get("id", 0))  # type: ignore[call-overload]
                if entry_id > last_id:
                    last_id = entry_id
                risk_str = str(e.get("risk_level", ""))
                risk_display = colors.risk_color(f"{risk_str:>8}")
                result_str = str(e.get("result_status") or "-")
                result_display = colors.status_color(f"{result_str:>10}")
                decision_str = str(e.get("human_decision") or e.get("approval", ""))
                print(
                    f"{e['id']:>4} {e['session_id']:>12} {e['action_type']:>15} "
                    f"{e['action_target']:>15} {risk_display} "
                    f"{decision_str:>10} "
                    f"{result_display}"
                )
                sys.stdout.flush()
            time.sleep(1)
    except KeyboardInterrupt:
        print(colors.cyan("\n[tail] Stopped."))
    finally:
        logger.close()


def _poll_new_entries(
    logger: AuditLogger,
    last_id: int,
    args: argparse.Namespace,
) -> list[dict[str, object]]:
    """Query entries with ID > last_id, respecting session/action/risk filters."""
    all_entries = logger.get_log(
        session_id=getattr(args, "session", None),
        action_type=getattr(args, "action_type", None),
        risk_level=getattr(args, "risk_level", None),
    )
    return [e for e in all_entries if int(e.get("id", 0)) > last_id]  # type: ignore[call-overload]


def _cmd_validate(args: argparse.Namespace) -> None:
    """Validate a policy YAML file."""
    from aegis.core.policy import Policy

    policy_path = Path(args.policy_file)
    if not policy_path.exists():
        print(
            colors.red(f"File not found: {policy_path}"),
            file=sys.stderr,
        )
        sys.exit(1)

    try:
        policy = Policy.from_yaml(args.policy_file)
    except TypeError as e:
        print(
            colors.red(f"Invalid policy format: {e}"),
            file=sys.stderr,
        )
        sys.exit(1)
    except Exception as e:
        print(colors.red(f"Policy validation failed: {e}"), file=sys.stderr)
        sys.exit(1)

    if not policy.rules:
        print(
            colors.yellow(
                "Warning: policy has 0 rules. All actions will use defaults "
                f"({policy.default_risk_level.name}/{policy.default_approval.value})."
            )
        )
    else:
        print(colors.green(f"Policy valid: {len(policy.rules)} rule(s) loaded."))
    for rule in policy.rules:
        print(
            f"  - {rule.name}: {rule.match_type}@{rule.match_target} "
            f"-> {colors.risk_color(rule.risk_level.name)}/{rule.approval.value}"
        )


def _cmd_schema() -> None:
    """Print the policy JSON Schema."""
    from aegis.core.schema import get_schema_json

    print(get_schema_json())


def _cmd_init(args: argparse.Namespace) -> None:
    """Generate a starter policy file with auto-detection."""
    output = Path(args.output)
    if output.exists():
        print(f"File already exists: {output}", file=sys.stderr)
        sys.exit(1)

    # Auto-detect frameworks
    detected = _detect_frameworks(Path("."))
    if detected:
        print(colors.green(f"Detected: {', '.join(detected)}"))

    # Generate policy (with framework-specific rules if detected)
    policy_text = _generate_policy(detected)
    output.write_text(policy_text, encoding="utf-8")

    # --with-tests: generate a matching test suite YAML
    if args.with_tests:
        import yaml  # noqa: PLC0415

        from aegis.core.policy import Policy  # noqa: PLC0415
        from aegis.core.policy_test_suite import PolicyTestSuite  # noqa: PLC0415

        test_output = Path(args.test_output)
        if test_output.exists():
            print(f"File already exists: {test_output}", file=sys.stderr)
            sys.exit(1)

        policy = Policy.from_yaml(output)
        suite = PolicyTestSuite.generate_from_policy(policy)

        suite_dict: dict[str, object] = {
            "name": suite.name,
            "tests": [
                {
                    k: v
                    for k, v in {
                        "action": c.action,
                        "expected_decision": c.expected_decision,
                        "expected_risk_level": c.expected_risk_level,
                        "description": c.description,
                    }.items()
                    if v
                }
                for c in suite.cases
            ],
        }
        test_output.write_text(
            yaml.dump(suite_dict, default_flow_style=False, sort_keys=False),
            encoding="utf-8",
        )
        print(f"Generated {output} and {test_output}")
    else:
        print(f"Created {output}")

    # Auto-run scan
    print("")
    from aegis.cli.scan import format_report, scan_directory  # noqa: PLC0415

    file_count, findings = scan_directory(Path(".").resolve())
    if file_count > 0:
        report = format_report(file_count, findings, directory=".")
        print(report)
    else:
        print("No Python files found to scan.")
        print("")
        print("Next steps:")
        print(f"  aegis validate {output}")
        print(f"  aegis score {output}")


def _detect_frameworks(directory: Path) -> list[str]:
    """Scan Python files for AI framework imports."""
    frameworks: set[str] = set()
    _FRAMEWORK_IMPORTS = {
        "openai": "OpenAI",
        "anthropic": "Anthropic",
        "langchain": "LangChain",
        "crewai": "CrewAI",
        "google.adk": "Google ADK",
        "google.generativeai": "Google Gemini",
        "mcp": "MCP",
        "autogen": "AutoGen",
    }
    try:
        for py_file in directory.rglob("*.py"):
            parts = py_file.relative_to(directory).parts
            if any(
                p.startswith(".") or p in ("__pycache__", "node_modules", "venv", ".venv")
                for p in parts
            ):
                continue
            try:
                source = py_file.read_text(encoding="utf-8")
            except (OSError, UnicodeDecodeError):
                continue
            for import_key, name in _FRAMEWORK_IMPORTS.items():
                if f"import {import_key}" in source or f"from {import_key}" in source:
                    frameworks.add(name)
    except OSError:
        pass
    return sorted(frameworks)


def _generate_policy(detected: list[str]) -> str:
    """Generate a policy YAML tailored to detected frameworks."""
    rules: list[str] = []

    # Base safe rules (always included)
    rules.append(
        "  # Read-only operations: auto-execute\n"
        "  - name: read_auto\n"
        "    match:\n"
        '      type: "read*"\n'
        "    risk_level: low\n"
        "    approval: auto\n"
    )
    rules.append(
        "  - name: get_auto\n"
        "    match:\n"
        '      type: "get*"\n'
        "    risk_level: low\n"
        "    approval: auto\n"
    )

    # Framework-specific rules
    if "MCP" in detected:
        rules.append(
            "  # MCP tool calls: require approval\n"
            "  - name: mcp_tools\n"
            "    match:\n"
            '      target: "mcp_*"\n'
            "    risk_level: medium\n"
            "    approval: approve\n"
        )

    if "OpenAI" in detected or "Anthropic" in detected:
        rules.append(
            "  # LLM API calls: auto with logging\n"
            "  - name: llm_calls\n"
            "    match:\n"
            '      type: "chat*"\n'
            "    risk_level: low\n"
            "    approval: auto\n"
        )

    # Dangerous operations (always included)
    rules.append(
        "  # Write operations: require approval\n"
        "  - name: write_approve\n"
        "    match:\n"
        '      type: "write*"\n'
        "    risk_level: medium\n"
        "    approval: approve\n"
    )
    rules.append(
        "  # Destructive operations: always blocked\n"
        "  - name: delete_block\n"
        "    match:\n"
        '      type: "delete*"\n'
        "    risk_level: critical\n"
        "    approval: block\n"
    )
    rules.append(
        "  # Shell execution: blocked by default\n"
        "  - name: shell_block\n"
        "    match:\n"
        '      type: "exec*"\n'
        "    risk_level: critical\n"
        "    approval: block\n"
    )

    framework_comment = ""
    if detected:
        framework_comment = f"# Detected frameworks: {', '.join(detected)}\n"

    return (
        f"# Aegis Policy — generated by `aegis init`\n"
        f"# Docs: https://acacian.github.io/aegis/guides/policies/\n"
        f"{framework_comment}\n"
        f'version: "1"\n\n'
        f"defaults:\n"
        f"  risk_level: medium\n"
        f"  approval: approve\n\n"
        f"rules:\n" + "\n".join(rules)
    )


def _cmd_simulate(args: argparse.Namespace) -> None:
    """Test actions against a policy without executing."""
    from aegis.core.action import Action
    from aegis.core.plan import ExecutionPlan
    from aegis.core.policy import Policy

    try:
        policy = Policy.from_yaml(args.policy_file)
    except Exception as e:
        print(f"Failed to load policy: {e}", file=sys.stderr)
        sys.exit(1)

    actions: list[Action] = []
    for spec in args.actions:
        parts = spec.split(":", 1)
        if len(parts) == 2:
            actions.append(Action(parts[0], parts[1]))
        else:
            actions.append(Action(parts[0], "*"))

    decisions = [policy.evaluate(a) for a in actions]
    plan = ExecutionPlan(decisions=decisions)

    if args.fmt == "json":
        print(json.dumps(plan.to_dict(), indent=2))
        return

    # Table format
    _icons = {"auto": "AUTO", "approve": "APPROVE", "block": "BLOCK"}
    print(f"Policy: {args.policy_file} ({len(policy.rules)} rules)")
    print(f"Actions: {len(actions)}\n")

    blocked = 0
    approval_needed = 0

    for i, d in enumerate(plan.decisions, 1):
        icon = _icons.get(d.approval.value, "?")
        status = "ALLOWED" if d.is_allowed else "BLOCKED"
        if not d.is_allowed:
            blocked += 1
        elif d.approval.value == "approve":
            approval_needed += 1

        colored_status = colors.status_color(status)
        colored_risk = colors.risk_color(d.risk_level.name)

        print(
            f"  {i}. {d.action.type}:{d.action.target}"
            f"  [{icon}]  risk={colored_risk}"
            f"  rule={d.matched_rule}  -> {colored_status}"
        )

    print()
    total = len(plan.decisions)
    auto = total - blocked - approval_needed
    print(f"Summary: {total} actions")
    print(
        f"  {colors.green(str(auto))} auto-execute, "
        f"{colors.yellow(str(approval_needed))} need approval, "
        f"{colors.red(str(blocked))} blocked"
    )


def _cmd_score(args: argparse.Namespace) -> None:
    """Calculate and display a governance score for a policy file."""
    from aegis.cli.score import calculate_score, check_regression, save_baseline

    policy_path = Path(args.policy_file)
    if not policy_path.exists():
        print(
            colors.red(f"File not found: {policy_path}"),
            file=sys.stderr,
        )
        sys.exit(1)

    try:
        result = calculate_score(policy_path)
    except Exception as e:
        print(colors.red(f"Failed to score policy: {e}"), file=sys.stderr)
        sys.exit(1)

    if getattr(args, "save_baseline", False):
        baseline_path = save_baseline(result)
        print(f"Baseline saved to {baseline_path} (score={result.total}, grade={result.grade})")
        return

    if getattr(args, "check_regression", False):
        regressed, message = check_regression(result)
        print(message)
        if regressed:
            sys.exit(1)
        return

    if args.fmt == "json":
        data = {
            "score": result.total,
            "grade": result.grade,
            "rule_count": result.rule_count,
            "breakdown": [
                {
                    "label": b.label,
                    "points": b.points,
                    "max_points": b.max_points,
                    "passed": b.passed,
                }
                for b in result.breakdown
            ],
            "badge_url": result.badge_url,
            "badge_markdown": result.badge_markdown,
        }
        print(json.dumps(data, indent=2))
        return

    # Table format
    print(f"Aegis Governance Score: {result.grade} ({result.total}/100)")
    print("=" * 48)

    for b in result.breakdown:
        icon = colors.green("V") if b.passed else colors.red("X")
        points_str = f"+{b.points}"
        print(f"  {icon} {b.label:<36} {points_str:>4}")

    print("=" * 48)
    print()
    print(f"Badge: ![Aegis Score]({result.badge_url})")
    print()
    print("Markdown:")
    print(result.badge_markdown)


def _cmd_scan(args: argparse.Namespace) -> None:
    """Scan for ungoverned AI tool calls."""
    from aegis.cli.scan import run_scan

    exit_code = run_scan(args.directory)
    sys.exit(exit_code)


def _seed_demo_data(n: int, audit_db: str | None) -> None:
    """Seed *n* random audit entries for demo/evaluation purposes."""
    import random

    from aegis.core.action import Action
    from aegis.core.policy import Approval, PolicyDecision
    from aegis.core.result import Result, ResultStatus
    from aegis.core.risk import RiskLevel

    db_path = audit_db or "aegis_audit.db"
    logger = AuditLogger(db_path=db_path)

    agents = ["agent-web", "agent-data", "agent-crm", "agent-ops", "agent-ml"]
    targets = ["/users", "/orders", "/db", "/api", "/files", "/reports"]
    scenarios: list[tuple[str, RiskLevel, Approval, str, ResultStatus]] = [
        ("read_users", RiskLevel.LOW, Approval.AUTO, "read_auto", ResultStatus.SUCCESS),
        ("get_orders", RiskLevel.LOW, Approval.AUTO, "get_auto", ResultStatus.SUCCESS),
        (
            "write_config",
            RiskLevel.MEDIUM,
            Approval.APPROVE,
            "write_approve",
            ResultStatus.SUCCESS,
        ),
        (
            "update_record",
            RiskLevel.MEDIUM,
            Approval.APPROVE,
            "update_approve",
            ResultStatus.SUCCESS,
        ),
        ("bulk_export", RiskLevel.HIGH, Approval.APPROVE, "bulk_high", ResultStatus.SUCCESS),
        (
            "delete_records",
            RiskLevel.CRITICAL,
            Approval.BLOCK,
            "delete_block",
            ResultStatus.BLOCKED,
        ),
        ("read_reports", RiskLevel.LOW, Approval.AUTO, "read_auto", ResultStatus.SUCCESS),
        ("post_webhook", RiskLevel.MEDIUM, Approval.APPROVE, "write_approve", ResultStatus.FAILED),
        ("drop_table", RiskLevel.CRITICAL, Approval.BLOCK, "delete_block", ResultStatus.BLOCKED),
        ("get_metrics", RiskLevel.LOW, Approval.AUTO, "get_auto", ResultStatus.SUCCESS),
    ]

    for _i in range(n):
        action_type, risk, approval, rule, status = random.choice(scenarios)
        agent = random.choice(agents)
        target = random.choice(targets)
        action = Action(type=action_type, target=target, agent_id=agent)
        decision = PolicyDecision(
            action=action,
            risk_level=risk,
            approval=approval,
            matched_rule=rule,
        )
        result = Result(action=action, status=status)
        logger.log(f"session-{agent}", decision, result=result)

    logger.close()
    print(f"Seeded {n} demo audit entries into {db_path}")


def _cmd_serve(args: argparse.Namespace) -> None:
    """Start the Aegis REST API server with governance dashboard."""
    try:
        import uvicorn
    except ImportError:
        print(
            "uvicorn is required: pip install 'agent-aegis[server]'",
            file=sys.stderr,
        )
        sys.exit(1)

    from aegis.server.app import create_app

    enable_dashboard = not getattr(args, "no_dashboard", False)
    audit_db = getattr(args, "audit_db", None)
    seed_n = getattr(args, "seed_demo", None)

    if seed_n and seed_n > 0:
        _seed_demo_data(seed_n, audit_db)

    app = create_app(
        policy_path=args.policy_file,
        audit_db_path=audit_db,
        enable_dashboard=enable_dashboard,
    )
    base_url = f"http://{args.host}:{args.port}"
    print(f"Aegis server starting on {base_url}")
    print(f"Policy: {args.policy_file}")
    if enable_dashboard:
        print(f"Dashboard: {base_url}")
    print(f"API: {base_url}/api/v1/")
    uvicorn.run(app, host=args.host, port=args.port)


def _cmd_compliance(args: argparse.Namespace) -> None:
    """Generate a compliance report from a JSONL audit file."""
    import json as _json

    from aegis.core.compliance import ReportGenerator
    from aegis.core.policy import Policy

    audit_path = Path(args.audit_file)
    if not audit_path.exists():
        print(f"Audit file not found: {audit_path}", file=sys.stderr)
        sys.exit(1)

    # Load audit entries from JSONL
    entries: list[dict[str, object]] = []
    with audit_path.open() as f:
        for line_no, line in enumerate(f, 1):
            line = line.strip()
            if not line:
                continue
            try:
                entries.append(_json.loads(line))
            except _json.JSONDecodeError as e:
                print(
                    f"Invalid JSON on line {line_no}: {e}",
                    file=sys.stderr,
                )
                sys.exit(1)

    # Load policy if provided
    policy_path = getattr(args, "policy", None)
    if policy_path:
        try:
            policy = Policy.from_yaml(policy_path)
        except Exception as e:
            print(f"Failed to load policy: {e}", file=sys.stderr)
            sys.exit(1)
    else:
        policy = Policy()

    gen = ReportGenerator(policy)
    report = gen.generate(entries, report_type=args.report_type)

    if args.fmt == "json":
        output_text = _json.dumps(gen.to_dict(report), indent=2)
    elif args.fmt == "html":
        output_text = gen.to_html(report)
    else:
        output_text = gen.to_markdown(report)

    output_path = getattr(args, "output", None)
    if output_path:
        Path(output_path).write_text(output_text, encoding="utf-8")
        print(f"Report written to {output_path}")
    else:
        print(output_text)


def _cmd_stats(args: argparse.Namespace) -> None:
    """Show policy rule statistics from the audit DB."""
    import sqlite3

    db_path = Path(args.db)
    if not db_path.exists():
        print(f"Database not found: {db_path}", file=sys.stderr)
        print("Run your agent with Aegis first to generate audit data.", file=sys.stderr)
        sys.exit(1)

    conn = sqlite3.connect(str(db_path))

    # Total actions
    total = conn.execute("SELECT COUNT(*) FROM audit_log").fetchone()[0]

    # By risk level
    risk_rows = conn.execute(
        "SELECT risk_level, COUNT(*) FROM audit_log GROUP BY risk_level ORDER BY COUNT(*) DESC"
    ).fetchall()

    # By result status
    status_rows = conn.execute(
        "SELECT result_status, COUNT(*) FROM audit_log "
        "WHERE result_status IS NOT NULL "
        "GROUP BY result_status ORDER BY COUNT(*) DESC"
    ).fetchall()

    # Top 5 action types
    action_rows = conn.execute(
        "SELECT action_type, COUNT(*) FROM audit_log "
        "GROUP BY action_type ORDER BY COUNT(*) DESC LIMIT 5"
    ).fetchall()

    # Top 5 matched rules
    rule_rows = conn.execute(
        "SELECT matched_rule, COUNT(*) FROM audit_log "
        "WHERE matched_rule IS NOT NULL "
        "GROUP BY matched_rule ORDER BY COUNT(*) DESC LIMIT 5"
    ).fetchall()

    conn.close()

    if args.fmt == "json":
        data = {
            "total_actions": total,
            "by_risk_level": {row[0]: row[1] for row in risk_rows},
            "by_result_status": {row[0]: row[1] for row in status_rows},
            "top_action_types": {row[0]: row[1] for row in action_rows},
            "top_matched_rules": {row[0]: row[1] for row in rule_rows},
        }
        print(json.dumps(data, indent=2))
        return

    # Table format
    print(colors.bold("=== Aegis Audit Statistics ==="))
    print(f"\nTotal actions processed: {colors.bold(str(total))}")

    print(colors.bold("\n--- Risk Level Breakdown ---"))
    for level, count in risk_rows:
        print(f"  {colors.risk_color(f'{level:<10}')} {count}")

    print(colors.bold("\n--- Result Status Breakdown ---"))
    for status, count in status_rows:
        print(f"  {colors.status_color(f'{status:<10}')} {count}")

    print(colors.bold("\n--- Top 5 Action Types ---"))
    for action_type, count in action_rows:
        print(f"  {action_type:<20} {count}")

    print(colors.bold("\n--- Top 5 Matched Rules ---"))
    for rule, count in rule_rows:
        print(f"  {rule:<20} {count}")


def _cmd_regulatory(args: argparse.Namespace) -> None:
    """Run regulatory compliance gap analysis."""
    from aegis.core.regulatory import ComplianceMapper, RegulatoryFramework

    _fw_map: dict[str, RegulatoryFramework] = {
        "eu-ai-act": RegulatoryFramework.EU_AI_ACT,
        "nist": RegulatoryFramework.NIST_AI_RMF,
        "soc2": RegulatoryFramework.SOC2,
        "iso-42001": RegulatoryFramework.ISO_42001,
        "owasp-agentic": RegulatoryFramework.OWASP_AGENTIC,
    }

    # Parse features
    features: dict[str, bool] | None = None
    if args.features:
        features = {f.strip(): True for f in args.features.split(",") if f.strip()}

    # Determine frameworks to analyze
    if args.framework == "all":
        frameworks = list(RegulatoryFramework)
    else:
        fw = _fw_map.get(args.framework)
        if fw is None:
            print(f"Unknown framework: {args.framework}", file=sys.stderr)
            sys.exit(1)
        frameworks = [fw]

    mapper = ComplianceMapper()
    analyses = [mapper.analyze(fw, features=features) for fw in frameworks]

    output_path = getattr(args, "output", None)

    if args.fmt == "json":
        data = []
        for analysis in analyses:
            data.append(mapper.generate_evidence_map(analysis))
        output_text = json.dumps(data if len(data) > 1 else data[0], indent=2)
        if output_path:
            Path(output_path).write_text(output_text, encoding="utf-8")
            print(f"Report written to {output_path}")
        else:
            print(output_text)
        return

    if args.fmt == "markdown":
        parts = [mapper.generate_report(a) for a in analyses]
        output_text = "\n".join(parts)
        if output_path:
            Path(output_path).write_text(output_text, encoding="utf-8")
            print(f"Report written to {output_path}")
        else:
            print(output_text)
        return

    if args.fmt == "html":
        parts = [mapper.generate_html_report(a) for a in analyses]
        output_text = "\n".join(parts)
        if output_path:
            Path(output_path).write_text(output_text, encoding="utf-8")
            print(f"Report written to {output_path}")
        else:
            print(output_text)
        return

    # Table format
    for analysis in analyses:
        _print_regulatory_table(analysis)
        print()


def _print_regulatory_table(analysis: ComplianceGapAnalysis) -> None:
    """Print a colored summary table for a single framework analysis."""
    fw_names: dict[str, str] = {
        "eu_ai_act": "EU AI Act",
        "nist_ai_rmf": "NIST AI RMF",
        "soc2": "SOC2",
        "iso_42001": "ISO 42001",
        "owasp_agentic": "OWASP Agentic AI",
    }
    name = fw_names.get(analysis.framework.value, analysis.framework.value)

    print(colors.bold(f"=== {name} Compliance Gap Analysis ==="))
    print()

    score = analysis.coverage_score
    if score >= 80:
        score_str = colors.green(f"{score:.1f}%")
    elif score >= 50:
        score_str = colors.yellow(f"{score:.1f}%")
    else:
        score_str = colors.red(f"{score:.1f}%")

    print(f"  Coverage Score: {score_str}")
    print(
        f"  Requirements:   {analysis.total_requirements} total, "
        f"{colors.green(str(analysis.fully_covered))} full, "
        f"{colors.yellow(str(analysis.partially_covered))} partial, "
        f"{colors.red(str(analysis.not_covered))} gaps"
    )
    print()

    if analysis.gaps:
        print(colors.bold("  Gaps:"))
        for gap in analysis.gaps:
            mandatory = colors.red(" [MANDATORY]") if gap.mandatory else ""
            print(f"    - {gap.requirement_id}: {gap.title}{mandatory}")
        print()

    if analysis.recommendations:
        print(colors.bold("  Recommendations:"))
        for i, rec in enumerate(analysis.recommendations, 1):
            print(f"    {i}. {rec}")
        print()


def _export_crypto_chain(
    logger: AuditLogger,
    out_path: Path,
) -> None:
    """Export audit DB entries as a cryptographic hash chain."""
    from aegis.core.crypto_audit import CryptoAuditChain

    entries = logger.get_log()
    if not entries:
        print("No audit entries to export.")
        return

    chain = CryptoAuditChain()
    for entry in entries:
        chain.append(
            agent_id=str(entry.get("agent_id", "unknown")),
            action_type=str(entry.get("action_type", "unknown")),
            action_target=str(entry.get("target", "unknown")),
            decision=str(entry.get("decision", "unknown")),
            risk_level=str(entry.get("risk_level", "unknown")),
            matched_rule=str(entry.get("matched_rule", "")),
            metadata={
                "original_id": entry.get("id"),
                "session_id": entry.get("session_id"),
            },
        )

    count = chain.export_jsonl(out_path)
    print(f"Exported {count} entries as crypto-audit chain to {out_path}")
    result = chain.verify()
    if result.valid:
        print(colors.green(f"Chain integrity verified ({result.chain_length} entries)"))
    else:
        print(colors.red(f"Chain verification FAILED: {result.error_message}"))


def _verify_chain_file(path: Path) -> None:
    """Verify an exported crypto-audit chain JSONL file."""
    from aegis.core.crypto_audit import CryptoAuditChain

    if not path.exists():
        print(colors.red(f"File not found: {path}"), file=sys.stderr)
        sys.exit(1)

    chain = CryptoAuditChain()
    try:
        chain.import_jsonl(path)
    except Exception as exc:
        print(colors.red(f"Import failed: {exc}"), file=sys.stderr)
        sys.exit(1)

    result = chain.verify()
    if result.valid:
        print(colors.green("PASSED — Chain integrity verified"))
        print(f"  Entries: {result.chain_length}")
        print(f"  Verified: {result.verified_entries}")
    else:
        print(colors.bright_red("FAILED — Chain integrity broken"))
        print(f"  Broke at entry: {result.first_broken_at}")
        print(f"  Error: {result.error_message}")
        sys.exit(1)


def _verify_audit_db(logger: AuditLogger) -> None:
    """Build crypto chain from audit DB and verify it."""
    from aegis.core.crypto_audit import CryptoAuditChain

    entries = logger.get_log()
    if not entries:
        print("No audit entries found.")
        return

    chain = CryptoAuditChain()
    for entry in entries:
        chain.append(
            agent_id=str(entry.get("agent_id", "unknown")),
            action_type=str(entry.get("action_type", "unknown")),
            action_target=str(entry.get("target", "unknown")),
            decision=str(entry.get("decision", "unknown")),
            risk_level=str(entry.get("risk_level", "unknown")),
            matched_rule=str(entry.get("matched_rule", "")),
            metadata={
                "original_id": entry.get("id"),
                "session_id": entry.get("session_id"),
            },
        )

    result = chain.verify()
    if result.valid:
        print(colors.green("PASSED — Audit log integrity verified"))
        print(f"  Entries: {result.chain_length}")
    else:
        print(colors.bright_red("FAILED — Audit log integrity compromised"))
        print(f"  Broke at entry: {result.first_broken_at}")
        print(f"  Error: {result.error_message}")
        sys.exit(1)


def _generate_evidence(db_path: Path, out_dir: Path) -> None:
    """Generate SOC2/EU AI Act evidence package."""
    from aegis.core.crypto_audit import CryptoAuditChain

    if not db_path.exists():
        print(colors.red(f"Database not found: {db_path}"), file=sys.stderr)
        sys.exit(1)

    logger = AuditLogger(db_path=str(db_path))
    entries = logger.get_log()
    logger.close()

    if not entries:
        print("No audit entries found.")
        return

    chain = CryptoAuditChain()
    for entry in entries:
        chain.append(
            agent_id=str(entry.get("agent_id", "unknown")),
            action_type=str(entry.get("action_type", "unknown")),
            action_target=str(entry.get("target", "unknown")),
            decision=str(entry.get("decision", "unknown")),
            risk_level=str(entry.get("risk_level", "unknown")),
            matched_rule=str(entry.get("matched_rule", "")),
            metadata={
                "original_id": entry.get("id"),
                "session_id": entry.get("session_id"),
            },
        )

    out_dir.mkdir(parents=True, exist_ok=True)
    package = chain.generate_evidence_package(out_dir)
    print(colors.green("Evidence package generated"))
    print(f"  Directory: {out_dir}")
    print(f"  Chain length: {package.chain_length}")
    print(f"  Algorithm: {package.algorithm}")
    vr = package.verification_result
    status = "PASSED" if vr.valid else "FAILED"
    print(f"  Verification: {status}")
    for note in package.compliance_notes:
        print(f"  - {note}")


def _cmd_monitor(args: argparse.Namespace) -> None:
    """Start the live agent monitoring dashboard."""
    db_path = Path(args.db)
    if not db_path.exists():
        print(f"Database not found: {db_path}", file=sys.stderr)
        print("Run your agent with Aegis first to generate audit data.", file=sys.stderr)
        sys.exit(1)

    from aegis.cli.monitor import run_monitor

    run_monitor(str(db_path), interval=args.interval)


def _cmd_autopolicy(args: argparse.Namespace) -> None:
    """Generate a policy from natural language description."""
    from aegis.core.autopolicy import generate_policy, generate_policy_yaml

    description = args.description

    if args.fmt == "json":
        policy = generate_policy(description)
        data = {
            "version": "1",
            "defaults": {
                "risk_level": policy.default_risk_level.name.lower(),
                "approval": policy.default_approval.value,
            },
            "rules": [
                {
                    "name": r.name,
                    "match_type": r.match_type,
                    "match_target": r.match_target,
                    "risk_level": r.risk_level.name.lower(),
                    "approval": r.approval.value,
                }
                for r in policy.rules
            ],
        }
        output = json.dumps(data, indent=2)
    else:
        output = generate_policy_yaml(description)

    if args.output:
        Path(args.output).write_text(output, encoding="utf-8")
        print(f"Policy written to {args.output}")
    else:
        print(output)


def _cmd_probe(args: argparse.Namespace) -> None:
    """Run adversarial probes against a policy."""
    from aegis.core.policy import Policy
    from aegis.core.probe import PolicyProbe

    policy_path = Path(args.policy_file)
    if not policy_path.exists():
        print(colors.red(f"File not found: {policy_path}"), file=sys.stderr)
        sys.exit(1)

    try:
        policy = Policy.from_yaml(args.policy_file)
    except Exception as e:
        print(colors.red(f"Failed to load policy: {e}"), file=sys.stderr)
        sys.exit(1)

    probe = PolicyProbe()
    report = probe.run(policy)

    if args.fmt == "json":
        data = {
            "total_probes": report.total_probes,
            "gap_count": report.gap_count,
            "critical_count": report.critical_count,
            "robustness_score": report.score,
            "findings": [
                {
                    "severity": f.severity,
                    "category": f.category,
                    "description": f.description,
                    "action_type": f.action.type,
                    "action_target": f.action.target,
                    "recommendation": f.recommendation,
                }
                for f in report.findings
            ],
        }
        print(json.dumps(data, indent=2))
        return

    # Table format
    print(colors.bold(f"Aegis Policy Probe — {report.total_probes} probes"))
    print("=" * 50)

    score = report.score
    if score >= 80:
        score_str = colors.green(f"{score}/100")
    elif score >= 50:
        score_str = colors.yellow(f"{score}/100")
    else:
        score_str = colors.red(f"{score}/100")
    print(f"  Robustness score: {score_str}")
    print(f"  Findings: {report.gap_count}")
    print()

    if report.findings:
        for f in report.findings:
            sev_color = {
                "critical": colors.bright_red,
                "high": colors.red,
                "medium": colors.yellow,
                "low": colors.cyan,
            }.get(f.severity, str)
            print(f"  {sev_color(f.severity.upper())} [{f.category}]")
            print(f"    {f.description}")
            if f.recommendation:
                print(f"    -> {f.recommendation}")
            print()
    else:
        print(colors.green("  No governance gaps found."))


def _cmd_uninstall_impact(args: argparse.Namespace) -> None:
    """Show the impact of removing Aegis."""
    import sqlite3

    db_path = Path(args.db)
    if not db_path.exists():
        print("No audit database found. Aegis can be removed safely.")
        return

    conn = sqlite3.connect(str(db_path))

    total_entries = conn.execute("SELECT COUNT(*) FROM audit_log").fetchone()[0]

    agent_row = conn.execute(
        "SELECT COUNT(DISTINCT agent_id) FROM audit_log WHERE agent_id IS NOT NULL"
    ).fetchone()
    unique_agents = agent_row[0] if agent_row else 0

    date_row = conn.execute("SELECT MIN(timestamp), MAX(timestamp) FROM audit_log").fetchone()
    first_date = date_row[0] if date_row else None

    blocked_count = conn.execute(
        "SELECT COUNT(*) FROM audit_log WHERE approval = 'block' "
        "OR result_status IN ('blocked', 'denied')"
    ).fetchone()[0]

    # Check crypto audit chain
    chain_length = 0
    try:
        cursor = conn.execute("PRAGMA table_info(governance_tenure)")
        has_tenure = cursor.fetchall()
        if has_tenure:
            conn.execute("SELECT first_activated FROM governance_tenure").fetchone()
    except sqlite3.OperationalError:
        pass

    # Try to detect frameworks from audit log action types
    # Check for crypto chain entries
    try:
        from aegis.core.crypto_audit import CryptoAuditChain

        logger = AuditLogger(db_path=str(db_path))
        entries = logger.get_log()
        if entries:
            chain = CryptoAuditChain()
            for entry in entries:
                chain.append(
                    agent_id=str(entry.get("agent_id", "unknown")),
                    action_type=str(entry.get("action_type", "unknown")),
                    action_target=str(entry.get("target", "unknown")),
                    decision=str(entry.get("decision", "unknown")),
                    risk_level=str(entry.get("risk_level", "unknown")),
                    matched_rule=str(entry.get("matched_rule", "")),
                )
            chain_length = len(entries)
        logger.close()
    except Exception:
        chain_length = total_entries

    conn.close()

    # Calculate days active
    days_active = 0
    if first_date:
        from datetime import UTC, datetime

        try:
            first_dt = datetime.fromisoformat(first_date)
            days_active = (datetime.now(UTC) - first_dt).days
        except (ValueError, TypeError):
            pass

    print()
    print(colors.bold("AEGIS REMOVAL IMPACT REPORT"))
    print("=" * 28)
    print()
    print(colors.bold("Data Loss:"))
    print(f"  - {total_entries} audit log entries (irrecoverable)")
    if chain_length > 0:
        print(f"  - {chain_length}-entry cryptographic hash chain (cannot be rebuilt)")
    print()
    print(colors.bold("Compliance Impact:"))
    print("  - Audit trail continuity: BROKEN (gap from removal date)")
    print()
    print(colors.bold("Security Regression:"))
    print(f"  - {blocked_count} blocked actions will no longer be blocked")
    print(f"  - {unique_agents} AI frameworks will lose runtime protection")
    print()
    if first_date:
        print(f"Governance active since: {first_date[:10]} ({days_active} days)")
    print()
    print("To proceed: pip uninstall agent-aegis")


if __name__ == "__main__":
    main()
