"""HMAC log integrity for audit events (NIST 800-53 AU-9).

Provides tamper-evident audit logging by signing each log line with HMAC-SHA256.
Each entry includes an HMAC digest computed from the event data, enabling
downstream systems to verify that log entries have not been modified.

The HMAC key should be stored securely (e.g., environment variable, secrets
manager) and rotated periodically.

Example:
    from pyauthz.audit.integrity import HmacExporter

    exporter = HmacExporter(path="logs/signed-audit.jsonl", key=b"your-secret-key")
    audit = AuditLogger(exporters=[exporter])
    rp.set_audit_logger(audit)
"""

from __future__ import annotations

import hashlib
import hmac
import json
from pathlib import Path

from pyauthz.audit.models import AuditEvent, MutationEvent


class HmacExporter:
    """Exports audit events as HMAC-signed JSON lines.

    Each line contains the event data plus a ``_hmac`` field with the
    HMAC-SHA256 digest. This allows downstream systems to verify log integrity.

    The HMAC is computed over the canonical JSON serialization of the event
    (sorted keys, no extra whitespace).

    Example:
        exporter = HmacExporter(path="logs/signed.jsonl", key=b"secret")
    """

    def __init__(self, path: str, *, key: bytes) -> None:
        if not key:
            raise ValueError("HMAC key must not be empty.")
        self._path = Path(path)
        self._path.parent.mkdir(parents=True, exist_ok=True)
        self._key = key

    def export(self, event: AuditEvent | MutationEvent) -> None:
        """Sign and write the event as a JSON line."""
        payload = event.model_dump_json()
        digest = hmac.new(self._key, payload.encode(), hashlib.sha256).hexdigest()

        record = json.loads(payload)
        record["_hmac"] = digest

        with open(self._path, "a") as f:
            f.write(json.dumps(record, default=str) + "\n")

    @staticmethod
    def verify(line: str, key: bytes) -> bool:
        """Verify the HMAC signature of a JSON log line.

        Args:
            line: A single JSON line from an HMAC-signed log file.
            key: The HMAC key used when the line was written.

        Returns:
            True if the signature is valid, False if tampered.
        """
        record = json.loads(line)
        stored_hmac = record.pop("_hmac", None)
        if stored_hmac is None:
            return False

        # Reconstruct the original payload by re-serializing without _hmac
        # We need the original Pydantic JSON serialization, so we compare
        # against the canonical form
        from pydantic import TypeAdapter

        # Try AuditEvent first, then MutationEvent
        for model_class in (AuditEvent, MutationEvent):
            try:
                adapter = TypeAdapter(model_class)
                obj = adapter.validate_python(record)
                payload = obj.model_dump_json()
                expected = hmac.new(key, payload.encode(), hashlib.sha256).hexdigest()
                if hmac.compare_digest(stored_hmac, expected):
                    return True
            except Exception:
                continue

        return False
