"""Audit trail report generator — compliance-ready audit reports.

Generates structured audit reports from the hash-chained AuditLog
and SQLiteStore audit events. Supports filtering, aggregation,
integrity verification, and export. Stdlib-only (ADR-021).
"""

from __future__ import annotations

import json
from collections import defaultdict
from dataclasses import dataclass, field
from datetime import UTC, datetime
from typing import Any


@dataclass
class AuditFilter:
    """Filter criteria for audit report queries."""

    start_date: str | None = None  # ISO format
    end_date: str | None = None
    actions: list[str] = field(default_factory=list)
    actors: list[str] = field(default_factory=list)
    keyword: str | None = None
    limit: int = 1000


@dataclass
class AuditSummary:
    """Aggregate summary of audit activity."""

    total_events: int = 0
    unique_actors: int = 0
    unique_actions: int = 0
    action_counts: dict[str, int] = field(default_factory=dict)
    actor_counts: dict[str, int] = field(default_factory=dict)
    first_event_time: str = ""
    last_event_time: str = ""
    events_per_day: dict[str, int] = field(default_factory=dict)

    def to_dict(self) -> dict:
        return {
            "total_events": self.total_events,
            "unique_actors": self.unique_actors,
            "unique_actions": self.unique_actions,
            "action_counts": dict(self.action_counts),
            "actor_counts": dict(self.actor_counts),
            "first_event_time": self.first_event_time,
            "last_event_time": self.last_event_time,
            "events_per_day": dict(self.events_per_day),
        }


@dataclass
class IntegrityResult:
    """Result of audit chain integrity verification."""

    valid: bool = True
    total_entries: int = 0
    checked_entries: int = 0
    first_invalid_index: int | None = None
    error_message: str = ""

    def to_dict(self) -> dict:
        return {
            "valid": self.valid,
            "total_entries": self.total_entries,
            "checked_entries": self.checked_entries,
            "first_invalid_index": self.first_invalid_index,
            "error_message": self.error_message,
        }


@dataclass
class AuditReport:
    """A complete audit trail report."""

    title: str
    generated_at: str
    filter_criteria: dict = field(default_factory=dict)
    summary: AuditSummary = field(default_factory=AuditSummary)
    integrity: IntegrityResult | None = None
    entries: list[dict] = field(default_factory=list)
    entry_count: int = 0

    def to_dict(self) -> dict:
        result: dict = {
            "title": self.title,
            "generated_at": self.generated_at,
            "filter_criteria": self.filter_criteria,
            "summary": self.summary.to_dict(),
            "entry_count": self.entry_count,
        }
        if self.integrity is not None:
            result["integrity"] = self.integrity.to_dict()
        if self.entries:
            result["entries"] = self.entries
        return result

    def to_json(self, indent: int = 2) -> str:
        return json.dumps(self.to_dict(), indent=indent)


def _ts_to_iso(ts: float) -> str:
    """Convert Unix timestamp to ISO format."""
    return datetime.fromtimestamp(ts, tz=UTC).isoformat()


def _ts_to_day(ts: float) -> str:
    """Convert Unix timestamp to YYYY-MM-DD."""
    return datetime.fromtimestamp(ts, tz=UTC).strftime("%Y-%m-%d")


class AuditReportGenerator:
    """Generate compliance-ready audit trail reports.

    Works with both in-memory AuditLog (hash-chained) and
    SQLiteStore audit events (persistent).

    Usage::

        from orxaq.kernel.audit import AuditLog
        gen = AuditReportGenerator(audit_log=my_log)
        report = gen.full_report()
        print(report.to_json())
    """

    def __init__(
        self,
        audit_log: Any | None = None,
        store: Any | None = None,
    ) -> None:
        """Initialize report generator.

        audit_log: AuditLog instance (in-memory, hash-chained).
        store: SQLiteStore instance (persistent events).
        """
        self._audit_log = audit_log
        self._store = store

    def _get_entries(self, filt: AuditFilter | None = None) -> list[dict]:
        """Collect entries from available sources."""
        entries: list[dict] = []
        f = filt or AuditFilter()

        # From in-memory AuditLog
        if self._audit_log is not None:
            raw = self._audit_log.entries()  # type: ignore[union-attr]
            for e in raw:
                entry = {
                    "timestamp": e.timestamp,
                    "iso_time": _ts_to_iso(e.timestamp),
                    "action": e.action,
                    "actor": e.actor,
                    "details": e.details,
                    "sequence": e.sequence,
                    "source": "audit_log",
                }
                entries.append(entry)

        # From SQLiteStore
        if self._store is not None:
            events = self._store.query_events(limit=f.limit)  # type: ignore[union-attr]
            for ev in events:
                ts_str = ev.get("created_at", "")
                try:
                    ts = datetime.fromisoformat(ts_str).timestamp()
                except (ValueError, TypeError):
                    ts = 0.0
                entry = {
                    "timestamp": ts,
                    "iso_time": ts_str,
                    "action": ev.get("event_type", ""),
                    "actor": ev.get("actor", ""),
                    "details": ev.get("details", ""),
                    "sequence": ev.get("id", 0),
                    "source": "sqlite",
                }
                entries.append(entry)

        # Apply filters
        if f.start_date:
            try:
                start_ts = datetime.fromisoformat(f.start_date).timestamp()
                entries = [e for e in entries if e["timestamp"] >= start_ts]
            except ValueError:
                pass

        if f.end_date:
            try:
                end_ts = datetime.fromisoformat(f.end_date).timestamp()
                entries = [e for e in entries if e["timestamp"] <= end_ts]
            except ValueError:
                pass

        if f.actions:
            entries = [e for e in entries if e["action"] in f.actions]

        if f.actors:
            entries = [e for e in entries if e["actor"] in f.actors]

        if f.keyword:
            kw = f.keyword.lower()
            entries = [
                e
                for e in entries
                if kw in e["details"].lower()
                or kw in e["action"].lower()
                or kw in e["actor"].lower()
            ]

        entries.sort(key=lambda e: e["timestamp"])
        return entries[: f.limit]

    def summarize(self, filt: AuditFilter | None = None) -> AuditSummary:
        """Generate aggregate summary of audit activity."""
        entries = self._get_entries(filt)
        summary = AuditSummary()

        if not entries:
            return summary

        summary.total_events = len(entries)

        action_counts: dict[str, int] = defaultdict(int)
        actor_counts: dict[str, int] = defaultdict(int)
        day_counts: dict[str, int] = defaultdict(int)

        for e in entries:
            action_counts[e["action"]] += 1
            actor_counts[e["actor"]] += 1
            day = _ts_to_day(e["timestamp"]) if e["timestamp"] > 0 else "unknown"
            day_counts[day] += 1

        summary.action_counts = dict(action_counts)
        summary.actor_counts = dict(actor_counts)
        summary.unique_actors = len(actor_counts)
        summary.unique_actions = len(action_counts)
        summary.events_per_day = dict(day_counts)
        summary.first_event_time = entries[0]["iso_time"]
        summary.last_event_time = entries[-1]["iso_time"]

        return summary

    def verify_integrity(self) -> IntegrityResult:
        """Verify hash chain integrity of in-memory AuditLog."""
        result = IntegrityResult()

        if self._audit_log is None:
            result.error_message = "No in-memory audit log available"
            result.valid = False
            return result

        entries = self._audit_log.entries()  # type: ignore[union-attr]
        result.total_entries = len(entries)

        if not entries:
            result.checked_entries = 0
            return result

        valid = self._audit_log.verify()  # type: ignore[union-attr]
        result.valid = valid
        result.checked_entries = len(entries)

        if not valid:
            # Find first invalid entry
            for i, entry in enumerate(entries):
                from orxaq.kernel.audit import AuditEntry

                expected = AuditEntry(
                    sequence=entry.sequence,
                    timestamp=entry.timestamp,
                    action=entry.action,
                    actor=entry.actor,
                    details=entry.details,
                    prev_hash=entry.prev_hash,
                ).compute_hash()
                if entry.entry_hash != expected:
                    result.first_invalid_index = i
                    result.error_message = f"Hash mismatch at index {i}"
                    break
                if i > 0 and entry.prev_hash != entries[i - 1].entry_hash:
                    result.first_invalid_index = i
                    result.error_message = f"Chain break at index {i}"
                    break

        return result

    def full_report(
        self,
        title: str = "Audit Trail Report",
        filt: AuditFilter | None = None,
        include_entries: bool = True,
        include_integrity: bool = True,
    ) -> AuditReport:
        """Generate a complete audit report.

        title: report title.
        filt: filter criteria.
        include_entries: include individual entries in report.
        include_integrity: run integrity verification.
        """
        entries = self._get_entries(filt)
        summary = self.summarize(filt)

        integrity = None
        if include_integrity and self._audit_log is not None:
            integrity = self.verify_integrity()

        report = AuditReport(
            title=title,
            generated_at=datetime.now(tz=UTC).isoformat(),
            filter_criteria={
                "start_date": filt.start_date if filt else None,
                "end_date": filt.end_date if filt else None,
                "actions": filt.actions if filt else [],
                "actors": filt.actors if filt else [],
                "keyword": filt.keyword if filt else None,
                "limit": filt.limit if filt else 1000,
            },
            summary=summary,
            integrity=integrity,
            entries=entries if include_entries else [],
            entry_count=len(entries),
        )

        return report

    def compliance_report(
        self,
        framework: str = "sr11-7",
    ) -> AuditReport:
        """Generate a compliance-focused audit report.

        framework: regulatory framework identifier.
        """
        title = f"Compliance Audit Report — {framework.upper()}"
        report = self.full_report(
            title=title,
            include_entries=True,
            include_integrity=True,
        )
        return report

    def actor_report(self, actor: str) -> AuditReport:
        """Generate a report for a specific actor's activity."""
        filt = AuditFilter(actors=[actor])
        return self.full_report(
            title=f"Actor Audit Report — {actor}",
            filt=filt,
        )

    def action_report(self, action: str) -> AuditReport:
        """Generate a report for a specific action type."""
        filt = AuditFilter(actions=[action])
        return self.full_report(
            title=f"Action Audit Report — {action}",
            filt=filt,
        )


__all__ = [
    "AuditFilter",
    "AuditReport",
    "AuditReportGenerator",
    "AuditSummary",
    "IntegrityResult",
]
