"""CLI commands for audit logging.

Provides commands for querying, exporting, and managing audit logs.

Examples:
    oclawma audit query --job-id 123
    oclawma audit export --format json --output audit.json
    oclawma audit stats
    oclawma audit verify
"""

from __future__ import annotations

from datetime import datetime, timedelta
from pathlib import Path

import click

from oclawma.audit import (
    AuditAction,
    AuditEvent,
    AuditLogger,
    RetentionPolicy,
)
from oclawma.cli_ui import (
    accent,
    bullet,
    header,
    highlight,
    key_value,
    muted,
    print_error,
    print_info,
    print_success,
    print_warning,
    subheader,
)


@click.group(name="audit")
@click.option(
    "--db-path",
    type=click.Path(),
    default="~/.oclawma/audit.db",
    help="Path to audit database",
)
@click.pass_context
def audit_cli(ctx: click.Context, db_path: str):
    """Query and manage audit logs.

    Provides commands for querying, exporting, and verifying
    the immutable audit log.

    \b
    Examples:
        oclawma audit query                    # Show recent events
        oclawma audit query --job-id 123       # Events for job 123
        oclawma audit export --format json     # Export to JSON
        oclawma audit verify                   # Check log integrity
    """
    ctx.ensure_object(dict)
    ctx.obj["db_path"] = Path(db_path).expanduser()
    ctx.obj["logger"] = AuditLogger(ctx.obj["db_path"])


@audit_cli.command(name="query")
@click.option(
    "--job-id",
    type=int,
    help="Filter by job ID",
)
@click.option(
    "--action",
    type=click.Choice([a.value for a in AuditAction], case_sensitive=False),
    help="Filter by action type",
)
@click.option(
    "--user",
    help="Filter by user",
)
@click.option(
    "--since",
    help="Show events since (e.g., '2024-01-01', '1h', '1d')",
)
@click.option(
    "--until",
    help="Show events until (e.g., '2024-01-31', '1h', '1d')",
)
@click.option(
    "--limit",
    type=int,
    default=50,
    help="Maximum number of events to show",
)
@click.option(
    "--format",
    "output_format",
    type=click.Choice(["table", "json", "csv"], case_sensitive=False),
    default="table",
    help="Output format",
)
@click.pass_context
def query_cmd(
    ctx: click.Context,
    job_id: int | None,
    action: str | None,
    user: str | None,
    since: str | None,
    until: str | None,
    limit: int,
    output_format: str,
):
    """Query audit events."""
    audit_logger: AuditLogger = ctx.obj["logger"]

    # Parse date filters
    since_dt = _parse_time(since) if since else None
    until_dt = _parse_time(until) if until else None

    # Convert action string to enum
    action_enum = AuditAction(action) if action else None

    # Query events
    events = audit_logger.query(
        action=action_enum,
        user=user,
        job_id=job_id,
        since=since_dt,
        until=until_dt,
        limit=limit,
    )

    if not events:
        print_info("No matching audit events found")
        return

    # Output based on format
    if output_format == "json":
        import json

        for event in events:
            click.echo(json.dumps(event.to_dict()))
    elif output_format == "csv":
        import csv
        import sys

        writer = csv.writer(sys.stdout)
        writer.writerow(["id", "timestamp", "action", "user", "job_id", "details"])
        for event in events:
            writer.writerow(
                [
                    event.id,
                    event.timestamp.isoformat(),
                    event.action.value,
                    event.user,
                    event.job_id,
                    str(event.details),
                ]
            )
    else:  # table format
        click.echo(header("AUDIT EVENTS", width=80))
        click.echo()

        for event in events:
            _print_event(event)
            click.echo()


@audit_cli.command(name="show")
@click.argument("event-id", type=int)
@click.pass_context
def show_cmd(ctx: click.Context, event_id: int):
    """Show details of a specific audit event."""
    audit_logger: AuditLogger = ctx.obj["logger"]

    event = audit_logger.get_event(event_id)
    if not event:
        print_error(f"Event {event_id} not found")
        raise click.ClickException(f"Event {event_id} not found")

    click.echo(header(f"AUDIT EVENT #{event_id}", width=60))
    click.echo()
    click.echo(key_value("ID", str(event.id)))
    click.echo(key_value("Timestamp", event.timestamp.isoformat()))
    click.echo(key_value("Action", accent(event.action.value.upper())))
    click.echo(key_value("User", event.user))
    click.echo(key_value("Job ID", str(event.job_id) if event.job_id else "N/A"))

    if event.details:
        click.echo()
        click.echo(subheader("DETAILS"))
        for key, value in event.details.items():
            click.echo(f"  {bullet(key)}: {value}")

    click.echo()
    click.echo(subheader("HASH CHAIN"))
    click.echo(key_value("Hash", muted(event.hash[:32] + "...")))
    click.echo(key_value("Previous", muted(event.previous_hash[:32] + "...")))


@audit_cli.command(name="export")
@click.option(
    "--format",
    "output_format",
    type=click.Choice(["json", "csv"], case_sensitive=False),
    required=True,
    help="Export format",
)
@click.option(
    "--output",
    "-o",
    type=click.Path(),
    required=True,
    help="Output file path",
)
@click.option(
    "--since",
    help="Export events since (e.g., '2024-01-01', '1d')",
)
@click.option(
    "--until",
    help="Export events until (e.g., '2024-01-31', '1d')",
)
@click.pass_context
def export_cmd(
    ctx: click.Context,
    output_format: str,
    output: str,
    since: str | None,
    until: str | None,
):
    """Export audit log to file."""
    audit_logger: AuditLogger = ctx.obj["logger"]

    # Parse date filters
    since_dt = _parse_time(since) if since else None
    until_dt = _parse_time(until) if until else None

    output_path = Path(output)

    if output_format == "json":
        count = audit_logger.export_json(output_path, since=since_dt, until=until_dt)
    else:  # csv
        count = audit_logger.export_csv(output_path, since=since_dt, until=until_dt)

    print_success(f"Exported {count} events to {output_path}")


@audit_cli.command(name="stats")
@click.pass_context
def stats_cmd(ctx: click.Context):
    """Show audit log statistics."""
    audit_logger: AuditLogger = ctx.obj["logger"]
    stats = audit_logger.get_stats()

    click.echo(header("AUDIT LOG STATISTICS", width=60))
    click.echo()
    click.echo(key_value("Total Events", str(stats["total_events"])))
    click.echo(key_value("Database Path", str(stats["db_path"])))
    click.echo()

    if stats["by_action"]:
        click.echo(subheader("EVENTS BY ACTION"))
        for action, count in sorted(stats["by_action"].items()):
            click.echo(f"  {bullet(action)}: {highlight(str(count))}")
        click.echo()

    if stats["date_range"]["oldest"]:
        click.echo(subheader("DATE RANGE"))
        click.echo(key_value("Oldest", stats["date_range"]["oldest"]))
        click.echo(key_value("Newest", stats["date_range"]["newest"]))
        click.echo()

    if stats["retention_policy"]["max_age_days"]:
        click.echo(subheader("RETENTION POLICY"))
        click.echo(key_value("Max Age", f"{stats['retention_policy']['max_age_days']} days"))
        if stats["retention_policy"]["max_entries"]:
            click.echo(key_value("Max Entries", str(stats["retention_policy"]["max_entries"])))


@audit_cli.command(name="verify")
@click.option(
    "--strict",
    is_flag=True,
    help="Exit with error code if tampering detected",
)
@click.pass_context
def verify_cmd(ctx: click.Context, strict: bool):
    """Verify audit log integrity.

    Checks that the hash chain is intact and no tampering has occurred.
    """
    audit_logger: AuditLogger = ctx.obj["logger"]

    click.echo(header("AUDIT LOG INTEGRITY CHECK", width=60))
    click.echo()

    is_valid, tampered_ids = audit_logger.verify_integrity()

    if is_valid:
        print_success("✓ Audit log integrity verified - no tampering detected")
        stats = audit_logger.get_stats()
        click.echo()
        click.echo(key_value("Total Events", str(stats["total_events"])))
        click.echo(key_value("Hash Chain", "Valid"))
    else:
        print_error("✗ TAMPERING DETECTED!")
        click.echo()
        click.echo(key_value("Tampered Events", str(len(tampered_ids))))
        click.echo(key_value("Event IDs", ", ".join(map(str, tampered_ids))))
        click.echo()
        print_warning("The audit log may have been compromised!")

        if strict:
            raise click.ClickException("Tampering detected in audit log")


@audit_cli.command(name="cleanup")
@click.option(
    "--max-age-days",
    type=int,
    help="Remove events older than this many days",
)
@click.option(
    "--max-entries",
    type=int,
    help="Keep only this many most recent events",
)
@click.option(
    "--archive-to",
    type=click.Path(),
    help="Archive deleted events to this database",
)
@click.option(
    "--dry-run",
    is_flag=True,
    help="Show what would be deleted without deleting",
)
@click.pass_context
def cleanup_cmd(
    ctx: click.Context,
    max_age_days: int | None,
    max_entries: int | None,
    archive_to: str | None,
    dry_run: bool,
):
    """Clean up old audit events.

    WARNING: This operation is destructive. Deleted events cannot be
    recovered unless archived.
    """
    audit_logger: AuditLogger = ctx.obj["logger"]

    if not max_age_days and not max_entries:
        print_error("Must specify --max-age-days or --max-entries")
        raise click.ClickException("No retention criteria specified")

    # Get current stats
    stats_before = audit_logger.get_stats()

    if dry_run:
        click.echo(header("DRY RUN - No changes will be made", width=60))
        click.echo()
        click.echo(key_value("Current Events", str(stats_before["total_events"])))

        events = audit_logger.query()

        if max_age_days:
            cutoff = datetime.utcnow() - __import__("datetime").timedelta(days=max_age_days)
            old_events = [e for e in events if e.timestamp < cutoff]
            click.echo(key_value(f"Events older than {max_age_days} days", str(len(old_events))))

        if max_entries and stats_before["total_events"] > max_entries:
            excess = stats_before["total_events"] - max_entries
            click.echo(key_value("Excess events to delete", str(excess)))

        return

    # Confirm destructive operation
    if not click.confirm("This will permanently delete audit events. Continue?"):
        print_info("Cleanup cancelled")
        return

    # Create retention policy and apply
    policy = RetentionPolicy(
        max_age_days=max_age_days,
        max_entries=max_entries,
        archive_path=Path(archive_to) if archive_to else None,
    )

    # Apply by creating new logger with policy
    audit_logger.retention_policy = policy
    audit_logger._apply_retention_policy()

    stats_after = audit_logger.get_stats()
    deleted = stats_before["total_events"] - stats_after["total_events"]

    print_success(f"Deleted {deleted} audit events")
    click.echo(key_value("Events Remaining", str(stats_after["total_events"])))

    if archive_to:
        print_info(f"Archived events to {archive_to}")


def _parse_time(value: str) -> datetime | None:
    """Parse time string to datetime.

    Supports:
    - ISO format: 2024-01-01
    - ISO with time: 2024-01-01T12:00:00
    - Relative: 1h (1 hour ago), 1d (1 day ago), 1w (1 week ago)
    """
    if not value:
        return None

    # Try ISO format
    try:
        return datetime.fromisoformat(value)
    except ValueError:
        pass

    # Try relative format
    now = datetime.utcnow()

    if value.endswith("h"):
        try:
            hours = int(value[:-1])
            return now - timedelta(hours=hours)
        except ValueError:
            pass

    if value.endswith("d"):
        try:
            days = int(value[:-1])
            return now - timedelta(days=days)
        except ValueError:
            pass

    if value.endswith("w"):
        try:
            weeks = int(value[:-1])
            return now - timedelta(weeks=weeks)
        except ValueError:
            pass

    raise click.BadParameter(f"Invalid time format: {value}")


def _print_event(event: AuditEvent) -> None:
    """Print an event in table format."""
    action_color = {
        "create": "green",
        "update": "blue",
        "delete": "red",
        "cancel": "yellow",
        "complete": "green",
        "fail": "red",
    }.get(event.action.value, "white")

    click.echo(f"  {accent(f'#{event.id}', bold=True)} ", nl=False)
    click.echo(f"[{click.style(event.action.value.upper(), fg=action_color)}] ", nl=False)
    click.echo(f"by {highlight(event.user)} ", nl=False)
    if event.job_id:
        click.echo(f"job={event.job_id} ", nl=False)
    click.echo(muted(f"at {event.timestamp.strftime('%Y-%m-%d %H:%M:%S')}"))

    if event.details:
        details_str = ", ".join(f"{k}={v}" for k, v in event.details.items())
        click.echo(f"    {muted(details_str[:100])}")
