"""Audit log exporter -- JSONL and CSV with HMAC chain verification.

Supports filtered export of the audit trail in machine-readable formats
suitable for compliance archiving, SIEM ingestion, and offline analysis.
"""

from __future__ import annotations

import asyncio
import csv
import json
import logging
from datetime import datetime, timezone
from io import StringIO
from typing import TYPE_CHECKING, Any

import aiosqlite
from pydantic import BaseModel

if TYPE_CHECKING:
    from pathlib import Path

logger = logging.getLogger(__name__)


class ExportFilter(BaseModel):
    """Filter criteria for audit log exports."""

    since: datetime | None = None
    until: datetime | None = None
    event_type: str | None = None  # filters on routing_tier
    model: str | None = None
    min_threat_score: float | None = None
    status_code: int | None = None


class AuditExporter:
    """Exports audit logs in JSONL or CSV format with optional filtering.

    Reads directly from the SQLite audit database.  All export methods are
    async to avoid blocking the event loop on large result sets.
    """

    def __init__(self, db_path: Path) -> None:
        self._db_path = db_path

    # ------------------------------------------------------------------
    # Public API
    # ------------------------------------------------------------------

    async def export_jsonl(self, output_path: Path, filters: ExportFilter | None = None) -> int:
        """Export audit log to JSONL format.

        Each line is a self-contained JSON object representing one audit
        entry.  The ``hmac`` field is included when present in the database.

        Args:
            output_path: Destination file path.
            filters: Optional filter criteria.

        Returns:
            Number of exported records.
        """
        where, params = self._build_where_clause(filters)
        count = 0

        async with aiosqlite.connect(str(self._db_path)) as db:
            db.row_factory = aiosqlite.Row
            sql = f"SELECT * FROM audit_log WHERE {where} ORDER BY timestamp ASC"  # nosec B608
            async with db.execute(sql, params) as cursor:
                with open(output_path, "w", encoding="utf-8") as f:
                    async for row in cursor:
                        row_dict = self._row_to_dict(row)
                        f.write(json.dumps(row_dict, default=str) + "\n")
                        count += 1

        logger.info("Exported %d audit entries to JSONL: %s", count, output_path)
        return count

    async def export_csv(self, output_path: Path, filters: ExportFilter | None = None) -> int:
        """Export audit log to CSV format.

        Writes a header row followed by one row per audit entry.

        Args:
            output_path: Destination file path.
            filters: Optional filter criteria.

        Returns:
            Number of exported records (excluding header).
        """
        where, params = self._build_where_clause(filters)
        count = 0

        async with aiosqlite.connect(str(self._db_path)) as db:
            db.row_factory = aiosqlite.Row
            sql = f"SELECT * FROM audit_log WHERE {where} ORDER BY timestamp ASC"  # nosec B608
            async with db.execute(sql, params) as cursor:
                first_row = await cursor.fetchone()
                if first_row is None:
                    # Write empty file (header only if we can determine columns)
                    output_path.write_text("", encoding="utf-8")
                    return 0

                # Determine column names from the first row
                columns = list(first_row.keys())
                buf = StringIO()
                writer = csv.DictWriter(buf, fieldnames=columns)
                writer.writeheader()

                # Write the first row
                writer.writerow(self._row_to_dict(first_row))
                count += 1

                # Write remaining rows
                async for row in cursor:
                    writer.writerow(self._row_to_dict(row))
                    count += 1

                output_path.write_text(buf.getvalue(), encoding="utf-8")

        logger.info("Exported %d audit entries to CSV: %s", count, output_path)
        return count

    async def get_stats(self) -> dict[str, Any]:
        """Return summary statistics about the audit log.

        Returns:
            Dictionary with keys: total_entries, date_range, top_models,
            cache_hit_rate, avg_latency.
        """
        async with aiosqlite.connect(str(self._db_path)) as db:
            # Total entries
            async with db.execute("SELECT COUNT(*) FROM audit_log") as cur:
                total = (await cur.fetchone())[0]  # type: ignore[index]

            # Date range
            async with db.execute("SELECT MIN(timestamp), MAX(timestamp) FROM audit_log") as cur:
                row = await cur.fetchone()
                date_range = {
                    "earliest": row[0] if row else None,  # type: ignore[index]
                    "latest": row[1] if row else None,  # type: ignore[index]
                }

            # Top models by request count
            top_models: dict[str, int] = {}
            async with db.execute(
                "SELECT model_used, COUNT(*) as cnt FROM audit_log GROUP BY model_used ORDER BY cnt DESC LIMIT 10"
            ) as cur:
                async for row in cur:
                    top_models[row[0]] = row[1]

            # Cache hit rate
            async with db.execute("SELECT COALESCE(SUM(cached), 0), COUNT(*) FROM audit_log") as cur:
                row_cache = await cur.fetchone()
                hits = row_cache[0] if row_cache else 0  # type: ignore[index]
                total_for_rate = row_cache[1] if row_cache else 0  # type: ignore[index]
            cache_hit_rate = round(hits / total_for_rate, 4) if total_for_rate > 0 else 0.0

            # Average latency
            async with db.execute("SELECT COALESCE(AVG(latency_ms), 0.0) FROM audit_log") as cur:
                avg_latency = (await cur.fetchone())[0]  # type: ignore[index]

        return {
            "total_entries": total,
            "date_range": date_range,
            "top_models": top_models,
            "cache_hit_rate": cache_hit_rate,
            "avg_latency": round(avg_latency, 2),
        }

    async def stream_live(self):  # type: ignore[no-untyped-def]
        """Async generator that yields new audit entries as they're written.

        Polls the database every second for entries with a timestamp newer
        than the last seen entry.  Yields each new entry as a dict.

        Yields:
            dict: A single audit entry.
        """
        last_seen_ts = datetime.now(timezone.utc).isoformat()

        while True:
            new_entries: list[dict[str, Any]] = []
            async with aiosqlite.connect(str(self._db_path)) as db:
                db.row_factory = aiosqlite.Row
                async with db.execute(
                    "SELECT * FROM audit_log WHERE timestamp > ? ORDER BY timestamp ASC",
                    (last_seen_ts,),
                ) as cursor:
                    async for row in cursor:
                        entry = self._row_to_dict(row)
                        new_entries.append(entry)

            for entry in new_entries:
                last_seen_ts = entry.get("timestamp", last_seen_ts)
                yield entry

            await asyncio.sleep(1.0)

    # ------------------------------------------------------------------
    # Internal helpers
    # ------------------------------------------------------------------

    @staticmethod
    def _row_to_dict(row: aiosqlite.Row) -> dict[str, Any]:
        """Convert an aiosqlite.Row to a plain dict."""
        return {key: row[key] for key in row.keys()}  # noqa: SIM118 -- sqlite3.Row iterates values, not keys

    @staticmethod
    def _build_where_clause(filters: ExportFilter | None) -> tuple[str, list[Any]]:
        """Build SQL WHERE clause and parameters from filters.

        Args:
            filters: Optional filter criteria.

        Returns:
            Tuple of (SQL WHERE clause string, parameter list).
        """
        conditions: list[str] = []
        params: list[Any] = []

        if filters:
            if filters.since is not None:
                conditions.append("timestamp >= ?")
                params.append(filters.since.isoformat())
            if filters.until is not None:
                conditions.append("timestamp <= ?")
                params.append(filters.until.isoformat())
            if filters.event_type is not None:
                conditions.append("routing_tier = ?")
                params.append(filters.event_type)
            if filters.model is not None:
                conditions.append("model_used = ?")
                params.append(filters.model)
            if filters.status_code is not None:
                conditions.append("status_code = ?")
                params.append(filters.status_code)

        where = " AND ".join(conditions) if conditions else "1=1"
        return where, params
