"""Tests for audit trail report generator."""

from __future__ import annotations

from orxaq.fabric.audit_report import (
    AuditFilter,
    AuditReport,
    AuditReportGenerator,
    AuditSummary,
    IntegrityResult,
)
from orxaq.kernel.audit import AuditLog


def _populated_log(n: int = 10) -> AuditLog:
    """Create an AuditLog with n entries."""
    log = AuditLog()
    actions = ["create", "update", "delete", "query"]
    actors = ["system", "user:alice", "user:bob"]
    for i in range(n):
        log.append(
            action=actions[i % len(actions)],
            actor=actors[i % len(actors)],
            details=f"operation {i} on entity e{i}",
        )
    return log


# ── AuditFilter ──────────────────────────────────────────


class TestAuditFilter:
    def test_defaults(self):
        f = AuditFilter()
        assert f.start_date is None
        assert f.end_date is None
        assert f.actions == []
        assert f.actors == []
        assert f.limit == 1000

    def test_custom(self):
        f = AuditFilter(
            actions=["create"],
            actors=["system"],
            keyword="entity",
        )
        assert f.actions == ["create"]
        assert f.keyword == "entity"


# ── AuditSummary ─────────────────────────────────────────


class TestAuditSummary:
    def test_defaults(self):
        s = AuditSummary()
        assert s.total_events == 0
        assert s.unique_actors == 0

    def test_to_dict(self):
        s = AuditSummary(
            total_events=5,
            unique_actors=2,
            unique_actions=3,
            action_counts={"create": 3, "update": 2},
        )
        d = s.to_dict()
        assert d["total_events"] == 5
        assert d["unique_actors"] == 2
        assert d["action_counts"]["create"] == 3


# ── IntegrityResult ──────────────────────────────────────


class TestIntegrityResult:
    def test_defaults(self):
        r = IntegrityResult()
        assert r.valid is True
        assert r.total_entries == 0

    def test_to_dict(self):
        r = IntegrityResult(
            valid=False,
            total_entries=10,
            checked_entries=5,
            first_invalid_index=5,
            error_message="Hash mismatch",
        )
        d = r.to_dict()
        assert d["valid"] is False
        assert d["first_invalid_index"] == 5


# ── AuditReport ──────────────────────────────────────────


class TestAuditReport:
    def test_defaults(self):
        r = AuditReport(title="Test", generated_at="2026-01-01T00:00:00")
        assert r.entry_count == 0
        assert r.entries == []

    def test_to_dict(self):
        r = AuditReport(
            title="Test Report",
            generated_at="2026-01-01T00:00:00",
            entry_count=3,
        )
        d = r.to_dict()
        assert d["title"] == "Test Report"
        assert d["entry_count"] == 3
        assert "integrity" not in d

    def test_to_dict_with_integrity(self):
        r = AuditReport(
            title="Test",
            generated_at="2026-01-01T00:00:00",
            integrity=IntegrityResult(valid=True, total_entries=5),
        )
        d = r.to_dict()
        assert d["integrity"]["valid"] is True

    def test_to_json(self):
        r = AuditReport(
            title="Test",
            generated_at="2026-01-01T00:00:00",
        )
        j = r.to_json()
        import json

        parsed = json.loads(j)
        assert parsed["title"] == "Test"


# ── AuditReportGenerator.summarize ───────────────────────


class TestSummarize:
    def test_empty_log(self):
        log = AuditLog()
        gen = AuditReportGenerator(audit_log=log)
        s = gen.summarize()
        assert s.total_events == 0

    def test_populated_log(self):
        log = _populated_log(12)
        gen = AuditReportGenerator(audit_log=log)
        s = gen.summarize()
        assert s.total_events == 12
        assert s.unique_actors == 3
        assert s.unique_actions == 4
        assert "create" in s.action_counts
        assert "system" in s.actor_counts

    def test_filter_by_action(self):
        log = _populated_log(12)
        gen = AuditReportGenerator(audit_log=log)
        s = gen.summarize(AuditFilter(actions=["create"]))
        assert s.total_events == 3  # 12 entries, 4 actions, 3 creates
        assert s.unique_actions == 1

    def test_filter_by_actor(self):
        log = _populated_log(12)
        gen = AuditReportGenerator(audit_log=log)
        s = gen.summarize(AuditFilter(actors=["system"]))
        assert s.total_events == 4  # 12 entries, 3 actors, 4 for system
        assert s.unique_actors == 1

    def test_filter_by_keyword(self):
        log = _populated_log(10)
        gen = AuditReportGenerator(audit_log=log)
        s = gen.summarize(AuditFilter(keyword="entity e5"))
        assert s.total_events == 1

    def test_events_per_day(self):
        log = _populated_log(5)
        gen = AuditReportGenerator(audit_log=log)
        s = gen.summarize()
        # All events created at approximately same time
        assert len(s.events_per_day) >= 1


# ── AuditReportGenerator.verify_integrity ────────────────


class TestVerifyIntegrity:
    def test_valid_chain(self):
        log = _populated_log(5)
        gen = AuditReportGenerator(audit_log=log)
        result = gen.verify_integrity()
        assert result.valid is True
        assert result.total_entries == 5
        assert result.checked_entries == 5

    def test_empty_log(self):
        log = AuditLog()
        gen = AuditReportGenerator(audit_log=log)
        result = gen.verify_integrity()
        assert result.valid is True
        assert result.total_entries == 0

    def test_no_audit_log(self):
        gen = AuditReportGenerator()
        result = gen.verify_integrity()
        assert result.valid is False
        assert "No in-memory" in result.error_message


# ── AuditReportGenerator.full_report ─────────────────────


class TestFullReport:
    def test_basic_report(self):
        log = _populated_log(8)
        gen = AuditReportGenerator(audit_log=log)
        report = gen.full_report()
        assert report.title == "Audit Trail Report"
        assert report.entry_count == 8
        assert len(report.entries) == 8
        assert report.summary.total_events == 8
        assert report.integrity is not None
        assert report.integrity.valid is True

    def test_report_no_entries(self):
        log = _populated_log(8)
        gen = AuditReportGenerator(audit_log=log)
        report = gen.full_report(include_entries=False)
        assert report.entry_count == 8
        assert len(report.entries) == 0

    def test_report_no_integrity(self):
        log = _populated_log(5)
        gen = AuditReportGenerator(audit_log=log)
        report = gen.full_report(include_integrity=False)
        assert report.integrity is None

    def test_custom_title(self):
        log = _populated_log(3)
        gen = AuditReportGenerator(audit_log=log)
        report = gen.full_report(title="Custom Title")
        assert report.title == "Custom Title"

    def test_filtered_report(self):
        log = _populated_log(12)
        gen = AuditReportGenerator(audit_log=log)
        filt = AuditFilter(actions=["create"])
        report = gen.full_report(filt=filt)
        assert report.entry_count == 3

    def test_report_to_json(self):
        log = _populated_log(5)
        gen = AuditReportGenerator(audit_log=log)
        report = gen.full_report()
        j = report.to_json()
        import json

        parsed = json.loads(j)
        assert parsed["entry_count"] == 5
        assert parsed["integrity"]["valid"] is True


# ── Specialized reports ──────────────────────────────────


class TestSpecializedReports:
    def test_compliance_report(self):
        log = _populated_log(10)
        gen = AuditReportGenerator(audit_log=log)
        report = gen.compliance_report()
        assert "SR11-7" in report.title
        assert report.integrity is not None

    def test_compliance_custom_framework(self):
        log = _populated_log(5)
        gen = AuditReportGenerator(audit_log=log)
        report = gen.compliance_report(framework="ccar")
        assert "CCAR" in report.title

    def test_actor_report(self):
        log = _populated_log(12)
        gen = AuditReportGenerator(audit_log=log)
        report = gen.actor_report("system")
        assert "system" in report.title
        assert report.entry_count == 4

    def test_action_report(self):
        log = _populated_log(12)
        gen = AuditReportGenerator(audit_log=log)
        report = gen.action_report("delete")
        assert "delete" in report.title
        assert report.entry_count == 3

    def test_actor_report_empty(self):
        log = _populated_log(5)
        gen = AuditReportGenerator(audit_log=log)
        report = gen.actor_report("nonexistent")
        assert report.entry_count == 0


class _MockStore:
    """Mock SQLiteStore for testing store-based entries."""

    def __init__(self, events: list[dict]) -> None:
        self._events = events

    def query_events(self, limit: int = 100) -> list[dict]:
        return self._events[:limit]


class TestSQLiteStoreSource:
    """Cover lines 164-180 (SQLiteStore path in _get_entries)."""

    def test_store_entries_included(self):
        store = _MockStore(
            [
                {
                    "created_at": "2025-06-01T12:00:00",
                    "event_type": "create",
                    "actor": "admin",
                    "details": "created item",
                    "id": 1,
                },
            ]
        )
        gen = AuditReportGenerator(store=store)
        report = gen.full_report()
        assert report.entry_count == 1

    def test_store_invalid_timestamp_fallback(self):
        store = _MockStore(
            [
                {"created_at": "not-a-date", "event_type": "x", "id": 1},
            ]
        )
        gen = AuditReportGenerator(store=store)
        report = gen.full_report()
        assert report.entry_count == 1

    def test_store_missing_fields_default(self):
        store = _MockStore([{}])
        gen = AuditReportGenerator(store=store)
        report = gen.full_report()
        assert report.entry_count == 1

    def test_combined_audit_log_and_store(self):
        log = _populated_log(3)
        store = _MockStore(
            [
                {
                    "created_at": "2025-06-01T12:00:00",
                    "event_type": "store_event",
                    "id": 99,
                },
            ]
        )
        gen = AuditReportGenerator(audit_log=log, store=store)
        report = gen.full_report()
        assert report.entry_count == 4


class TestDateFilters:
    """Cover lines 184-195 (start_date and end_date filtering)."""

    def test_start_date_excludes_old(self):
        log = _populated_log(5)
        gen = AuditReportGenerator(audit_log=log)
        f = AuditFilter(start_date="2099-01-01T00:00:00")
        report = gen.full_report(filt=f)
        assert report.entry_count == 0

    def test_end_date_excludes_future(self):
        log = _populated_log(5)
        gen = AuditReportGenerator(audit_log=log)
        f = AuditFilter(end_date="2000-01-01T00:00:00")
        report = gen.full_report(filt=f)
        assert report.entry_count == 0

    def test_invalid_start_date_ignored(self):
        log = _populated_log(5)
        gen = AuditReportGenerator(audit_log=log)
        f = AuditFilter(start_date="bad-date")
        report = gen.full_report(filt=f)
        assert report.entry_count == 5

    def test_invalid_end_date_ignored(self):
        log = _populated_log(5)
        gen = AuditReportGenerator(audit_log=log)
        f = AuditFilter(end_date="bad-date")
        report = gen.full_report(filt=f)
        assert report.entry_count == 5


class TestInvalidChainDetection:
    """Cover lines 267-285 (integrity check invalid chain)."""

    def test_tampered_hash_detected(self):
        log = AuditLog()
        for i in range(5):
            log.append(action=f"action_{i}", actor="test", details=f"d{i}")
        object.__setattr__(log._entries[2], "entry_hash", "tampered")
        gen = AuditReportGenerator(audit_log=log)
        result = gen.verify_integrity()
        assert not result.valid
        assert result.first_invalid_index is not None
        assert "mismatch" in (result.error_message or "").lower()

    def test_chain_break_detected(self):
        from orxaq.kernel.audit import AuditEntry

        log = AuditLog()
        for i in range(5):
            log.append(action=f"action_{i}", actor="test", details=f"d{i}")
        # Set wrong prev_hash then recompute entry_hash so the entry is
        # self-consistent but the chain link is broken.
        entry = log._entries[3]
        object.__setattr__(entry, "prev_hash", "wrong_prev")
        new_hash = AuditEntry(
            sequence=entry.sequence,
            timestamp=entry.timestamp,
            action=entry.action,
            actor=entry.actor,
            details=entry.details,
            prev_hash="wrong_prev",
        ).compute_hash()
        object.__setattr__(entry, "entry_hash", new_hash)
        gen = AuditReportGenerator(audit_log=log)
        result = gen.verify_integrity()
        assert not result.valid
        assert result.first_invalid_index is not None
        assert "chain break" in (result.error_message or "").lower()
