"""HMAC chain for tamper-evident audit logs.

Each audit entry gets an HMAC that chains to the previous entry,
creating a tamper-evident sequence. Any modification to a historical
entry breaks all subsequent HMACs.
"""

from __future__ import annotations

import hashlib
import hmac
import json
import logging

from pydantic import BaseModel

logger = logging.getLogger(__name__)

GENESIS_SEED = b"llmhost-trustledger-genesis-v1"


class VerificationResult(BaseModel):
    """Result of verifying an HMAC chain."""

    valid: bool
    total_entries: int
    verified_entries: int
    first_break_index: int | None = None  # index where chain breaks (None if valid)
    first_break_request_id: str | None = None
    message: str


class HMACChain:
    """Manages HMAC chain computation and verification.

    Each entry's HMAC is computed over the concatenation of the previous
    entry's HMAC and the current entry's JSON serialization.  This creates
    a hash chain where tampering with any entry invalidates all subsequent
    HMACs.
    """

    def __init__(self, key: bytes) -> None:
        """Initialize with a secret key (derived from master encryption key)."""
        self._key = key

    @classmethod
    def derive_key(cls, master_key: bytes) -> bytes:
        """Derive HMAC chain key from master encryption key using HKDF-like approach.

        Uses PBKDF2 with the genesis seed as a fixed salt to produce a key
        that is deterministically tied to the master key but
        cryptographically independent from it.
        """
        return hashlib.pbkdf2_hmac("sha256", master_key, GENESIS_SEED, 100_000)

    def compute_hmac(self, previous_hmac: str, entry_json: str) -> str:
        """Compute HMAC for a new entry in the chain.

        Args:
            previous_hmac: Hex-encoded HMAC of the preceding entry.
            entry_json: Canonical JSON serialization of the current entry
                (must NOT include the ``hmac`` field itself).

        Returns:
            Hex-encoded HMAC-SHA256 digest.
        """
        message = f"{previous_hmac}:{entry_json}".encode()
        return hmac.new(self._key, message, hashlib.sha256).hexdigest()

    def genesis_hmac(self) -> str:
        """Compute the HMAC for the genesis (anchor) of the chain.

        This is the "previous HMAC" value used when computing the HMAC of
        the very first entry.  It is deterministic for a given key.
        """
        return hmac.new(self._key, GENESIS_SEED, hashlib.sha256).hexdigest()

    def verify_chain(self, entries: list[dict]) -> VerificationResult:
        """Verify the integrity of an HMAC chain.

        Each entry dict must have an ``hmac`` key.  The entry is serialized
        without the ``hmac`` field for verification.

        Args:
            entries: Ordered list of entry dicts (oldest first).

        Returns:
            A :class:`VerificationResult` describing the outcome.
        """
        if not entries:
            return VerificationResult(
                valid=True,
                total_entries=0,
                verified_entries=0,
                message="Empty chain",
            )

        previous_hmac = self.genesis_hmac()

        for i, entry in enumerate(entries):
            stored_hmac = entry.get("hmac", "")
            # Serialize entry WITHOUT the hmac field for verification
            entry_without_hmac = {k: v for k, v in entry.items() if k != "hmac"}
            entry_json = json.dumps(entry_without_hmac, sort_keys=True, default=str)

            computed = self.compute_hmac(previous_hmac, entry_json)
            if not hmac.compare_digest(computed, stored_hmac):
                request_id = entry.get("request_id")
                logger.warning(
                    "HMAC chain broken at index %d (request_id=%s)",
                    i,
                    request_id,
                )
                return VerificationResult(
                    valid=False,
                    total_entries=len(entries),
                    verified_entries=i,
                    first_break_index=i,
                    first_break_request_id=request_id,
                    message=f"Chain broken at entry {i} (request_id={request_id})",
                )
            previous_hmac = stored_hmac

        return VerificationResult(
            valid=True,
            total_entries=len(entries),
            verified_entries=len(entries),
            message=f"Chain verified: {len(entries)} entries intact",
        )
