"""Tests for ARIAAuditBackend and the audit outbox."""

from __future__ import annotations

import concurrent.futures
import json

import httpx
import pytest
from agent_os.audit_logger import AuditEntry

from aria_agentkit.audit.backend import ARIAAuditBackend
from aria_agentkit.audit.receipt import AuditReceipt


class TestAuditBackendWrite:
    def test_write_queues_entry(self, mock_receipt_vault: httpx.Client) -> None:
        backend = ARIAAuditBackend(
            receipt_vault_url="http://localhost:9000",
            http_client=mock_receipt_vault,
        )
        entry = AuditEntry(
            event_type="governance_decision",
            agent_id="a1",
            action="tool.invoke",
            decision="allow",
        )
        backend.write(entry)
        assert backend.pending_count() == 1

    def test_flush_exports_entries(self, mock_receipt_vault: httpx.Client) -> None:
        backend = ARIAAuditBackend(
            receipt_vault_url="http://localhost:9000",
            http_client=mock_receipt_vault,
        )
        for i in range(5):
            backend.write(
                AuditEntry(
                    event_type="test",
                    agent_id=f"agent-{i}",
                    action="test",
                    decision="allow",
                )
            )
        assert backend.pending_count() == 5
        backend.flush()
        assert backend.pending_count() == 0


class TestAuditBackendRedaction:
    def test_sensitive_keys_are_redacted(self, mock_receipt_vault: httpx.Client) -> None:
        captured: list[bytes] = []

        def capturing_handler(request: httpx.Request) -> httpx.Response:
            captured.append(request.content)
            return httpx.Response(status_code=201, json={"status": "ok"})

        client = httpx.Client(transport=httpx.MockTransport(capturing_handler))
        backend = ARIAAuditBackend(
            receipt_vault_url="http://localhost:9000",
            http_client=client,
        )
        backend.write(
            AuditEntry(
                event_type="test",
                agent_id="a1",
                action="test",
                decision="allow",
                metadata={"password": "secret123", "safe_key": "visible"},
            )
        )
        backend.flush()

        assert len(captured) == 1
        body = json.loads(captured[0])
        receipt = body[0]
        payload = receipt["payload"]
        assert payload["metadata"]["password"] == "***REDACTED***"
        assert payload["metadata"]["safe_key"] == "visible"


class TestAuditBackendHashChain:
    def test_receipts_are_hash_chained(self, mock_receipt_vault: httpx.Client) -> None:
        captured: list[bytes] = []

        def capturing_handler(request: httpx.Request) -> httpx.Response:
            captured.append(request.content)
            return httpx.Response(status_code=201, json={"status": "ok"})

        client = httpx.Client(transport=httpx.MockTransport(capturing_handler))
        backend = ARIAAuditBackend(
            receipt_vault_url="http://localhost:9000",
            http_client=client,
        )
        for i in range(3):
            backend.write(
                AuditEntry(event_type="test", agent_id=f"a{i}", action="t", decision="d")
            )
        backend.flush()

        assert len(captured) == 1
        receipts = json.loads(captured[0])
        assert len(receipts) == 3

        assert receipts[0]["prev_hash"] == "0" * 64
        assert receipts[1]["prev_hash"] == receipts[0]["receipt_hash"]
        assert receipts[2]["prev_hash"] == receipts[1]["receipt_hash"]


class TestAuditBackendRetry:
    def test_retries_on_failure_then_dead_letters(self) -> None:
        dead_letters: list[tuple[list[AuditReceipt], str]] = []

        def dead_letter_cb(batch: list[AuditReceipt], reason: str) -> None:
            dead_letters.append((batch, reason))

        def failing_handler(request: httpx.Request) -> httpx.Response:
            return httpx.Response(status_code=500, text="fail")

        client = httpx.Client(transport=httpx.MockTransport(failing_handler))
        backend = ARIAAuditBackend(
            receipt_vault_url="http://localhost:9000",
            http_client=client,
            max_retries=2,
            dead_letter_callback=dead_letter_cb,
            sleep_fn=lambda _: None,
        )
        backend.write(AuditEntry(event_type="test", agent_id="a1", action="t", decision="d"))
        backend.flush()

        assert len(dead_letters) == 1
        assert len(dead_letters[0][0]) == 1
        assert "max retries" in dead_letters[0][1]


class TestAuditBackendContextManager:
    def test_context_manager_flushes(self, mock_receipt_vault: httpx.Client) -> None:
        with ARIAAuditBackend(
            receipt_vault_url="http://localhost:9000",
            http_client=mock_receipt_vault,
        ) as backend:
            backend.write(AuditEntry(event_type="test", agent_id="a1", action="t", decision="d"))
        assert backend.pending_count() == 0


class TestAuditBackendRepr:
    def test_repr(self) -> None:
        backend = ARIAAuditBackend(receipt_vault_url="http://localhost:9000")
        r = repr(backend)
        assert "ARIAAuditBackend" in r
        assert "http://localhost:9000" in r


class TestAuditBackendConcurrency:
    @pytest.mark.slow
    def test_100_concurrent_writers(self, mock_receipt_vault: httpx.Client) -> None:
        backend = ARIAAuditBackend(
            receipt_vault_url="http://localhost:9000",
            http_client=mock_receipt_vault,
        )

        def write_entry(i: int) -> None:
            backend.write(
                AuditEntry(
                    event_type="concurrent",
                    agent_id=f"agent-{i}",
                    action="test",
                    decision="allow",
                )
            )

        with concurrent.futures.ThreadPoolExecutor(max_workers=100) as pool:
            futures = [pool.submit(write_entry, i) for i in range(100)]
            concurrent.futures.wait(futures)

        assert backend.pending_count() == 100
        backend.flush()
        assert backend.pending_count() == 0
