"""nestdx audit — re-run policy checks against a session."""

from __future__ import annotations

import json
import tempfile
from pathlib import Path

import click

from nestdx.config.parser import load_config
from nestdx.policy.engine import PolicyEngine
from nestdx.reporting.sarif import format_json, format_sarif
from nestdx.session.git import diff_between_refs, read_file_at_ref
from nestdx.session.manager import SessionManager, SessionNotFoundError
from nestdx.session.models import PolicyViolation
from nestdx.utils.console import console, make_violations_panel


def _output_violations(
    violations: list[PolicyViolation],
    output_format: str,
) -> None:
    """Write violations to stdout in the requested format."""
    if output_format == "sarif":
        click.echo(json.dumps(format_sarif(violations), indent=2))
    elif output_format == "json":
        click.echo(json.dumps(format_json(violations), indent=2))
    else:
        # "rich" — use the existing Rich console output
        if violations:
            panel = make_violations_panel(violations)
            if panel:
                console.print(panel)
        else:
            console.print("[green]Audit passed — no violations.[/green]")


def _audit_refs(
    project_root: Path,
    engine: PolicyEngine,
    base_ref: str,
    head_ref: str,
    output_format: str,
    ci: bool,
) -> None:
    """Run policy checks by diffing two git refs (no session needed)."""
    try:
        file_changes = diff_between_refs(project_root, base_ref, head_ref)
    except ValueError as e:
        console.print(f"[red]Error:[/red] {e}")
        raise SystemExit(1)

    # For secrets scanning, we need actual file content.  Write files from
    # head_ref into a temporary directory so the secrets scanner can read them.
    with tempfile.TemporaryDirectory() as tmpdir:
        tmp_root = Path(tmpdir)
        for fc in file_changes:
            if fc.change_type == "deleted":
                continue
            content = read_file_at_ref(project_root, head_ref, fc.path)
            if content is not None:
                dest = tmp_root / fc.path
                dest.parent.mkdir(parents=True, exist_ok=True)
                dest.write_text(content)

        violations = engine.check_post_session(file_changes, tmp_root)

    if output_format == "rich":
        console.print(f"[bold]Auditing refs:[/bold] {base_ref}..{head_ref}")
        console.print(f"  Files changed: {len(file_changes)}")

    _output_violations(violations, output_format)

    if ci:
        errors = [v for v in violations if v.severity == "error"]
        if errors:
            if output_format == "rich":
                console.print(f"\n[red]Audit failed:[/red] {len(errors)} error(s) found.")
            raise SystemExit(1)


@click.command()
@click.option("--session", "-s", "session_id", default=None, help="Session ID (defaults to last completed).")
@click.option("--ci", is_flag=True, help="CI mode: exit 1 on error-level violations.")
@click.option("--base-ref", default=None, help="Base commit/ref for diff-based auditing.")
@click.option("--head-ref", default=None, help="Head commit/ref (defaults to HEAD).")
@click.option(
    "--format",
    "output_format",
    type=click.Choice(["json", "sarif", "rich"]),
    default=None,
    help="Output format (default: rich, or json when --ci without explicit --format).",
)
def audit_cmd(
    session_id: str | None,
    ci: bool,
    base_ref: str | None,
    head_ref: str | None,
    output_format: str | None,
):
    """Re-run policy checks against a session's file changes."""
    project_root = Path.cwd()

    # Resolve output format: explicit > auto-detect
    # When --ci is used with --base-ref (CI pipeline), default to json.
    # For session-based audit (including --ci without --base-ref), default to rich
    # to maintain backward compatibility.
    if output_format is None:
        if ci and base_ref is not None:
            output_format = "json"
        else:
            output_format = "rich"

    try:
        config = load_config()
    except Exception as e:
        console.print(f"[red]Error:[/red] {e}")
        raise SystemExit(1)

    engine = PolicyEngine(config, project_root=project_root)

    # Ref-based auditing (no session needed)
    if base_ref is not None:
        resolved_head = head_ref or "HEAD"
        _audit_refs(project_root, engine, base_ref, resolved_head, output_format, ci)
        return

    # Session-based auditing (original behaviour)
    manager = SessionManager(config, project_root)

    if session_id:
        try:
            session = manager.get_session(session_id)
        except SessionNotFoundError as e:
            console.print(f"[red]Error:[/red] {e}")
            raise SystemExit(1)
    else:
        sessions = manager.list_sessions(limit=1)
        if not sessions:
            console.print("[yellow]No sessions found.[/yellow]")
            return
        session = sessions[0]

    if output_format == "rich":
        console.print(f"[bold]Auditing session:[/bold] {session.id}")
        console.print(f"  Files changed: {len(session.file_changes)}")

    # Re-run policy checks
    violations = engine.check_post_session(session.file_changes, project_root)

    _output_violations(violations, output_format)

    if violations:
        errors = [v for v in violations if v.severity == "error"]
        if output_format == "rich":
            if ci and errors:
                console.print(f"\n[red]Audit failed:[/red] {len(errors)} error(s) found.")
            elif errors:
                console.print(f"\n[red]{len(errors)} error(s) found.[/red]")
            else:
                console.print("\n[yellow]Warnings only — no errors.[/yellow]")

        if ci and errors:
            raise SystemExit(1)
