"""CheckpointAnchorBackend — a REAL self-attested local anchor (NOT a mock).

Honest trust label: **"self-attested log, no external witness yet"** (assurance ``self_attested``). Unlike
``MockAnchorBackend`` (which echoes a fabricated certificate), every anchored ``batch_root`` here is committed
into a durable, append-only, hash-chained log and signed with Ed25519 over ``(height ‖ batch_root ‖
prev_hash)``. The attestation is INDEPENDENTLY verifiable (``verify_checkpoint_entry``): the signature
authenticates the entry and each entry chains to its predecessor (tamper-evident).

There is NO fold transform here (unlike ``NEOAnchorBackend``, which anchors ``fold([batch_root])``) — a local
checkpoint commits the ``batch_root`` directly into the signed chain — so ``opaque_root == batch_root`` is
HONEST, and the proof is the signature + chain, never an echo. This satisfies ``anchor.batch.verify_anchor``
(re-derivation, not echo) because OUR recomputed ``batch_root`` is exactly what was signed.

Deliberately minimal (NCTE is a disposable harness): a single signer, a single-file JSONL log. It is NOT
external-witnessed finality — promote the deployed host to ``NEOAnchorBackend`` once the NEO cluster is
permanently live (directive D-1). The seam (``AnchorBackend``) makes that a deploy-time swap, never a rewrite.
"""

from __future__ import annotations

import json
from dataclasses import dataclass
from pathlib import Path
from typing import Dict, Optional, Union

from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey

from cl.anchor.backend import AnchorAttestation, AnchorBackend, AnchorError
from cl.canonical.hash_profile import DEFAULT_PROFILE
from cl.identity.signatures import Ed25519Verifier

# Domain-separated signing input so a checkpoint signature can never be replayed as any other Ledger signature.
DOMAIN_CHECKPOINT_ENTRY = b"COTRUGLI:CHECKPOINT:ENTRY:V1"
GENESIS_PREV_HASH = b"\x00" * 32
ASSURANCE_SELF_ATTESTED = "self_attested"


@dataclass(frozen=True)
class CheckpointEntry:
    """One signed link in the checkpoint chain. The signed content is EXACTLY ``(height, batch_root,
    prev_hash)`` — no wall-clock, so the entry is deterministic and reproducible by any auditor."""

    height: int
    batch_root: bytes
    prev_hash: bytes
    signer_key_ref: str  # ed25519 public key, hex
    signature: bytes

    def signing_input(self) -> bytes:
        if len(self.batch_root) != 32 or len(self.prev_hash) != 32:
            raise ValueError("checkpoint batch_root and prev_hash must be 32 bytes")
        return (
            DOMAIN_CHECKPOINT_ENTRY
            + self.height.to_bytes(8, "little")
            + self.batch_root
            + self.prev_hash
        )

    def entry_hash(self) -> bytes:
        """The chain link — the next entry's ``prev_hash``. Binds the signature so the chain is tamper-evident."""
        return DEFAULT_PROFILE.digest(self.signing_input() + self.signature)

    def to_json(self) -> dict:
        return {
            "height": self.height,
            "batch_root": self.batch_root.hex(),
            "prev_hash": self.prev_hash.hex(),
            "signer_key_ref": self.signer_key_ref,
            "signature": self.signature.hex(),
        }

    @classmethod
    def from_json(cls, obj: dict) -> "CheckpointEntry":
        return cls(
            height=int(obj["height"]),
            batch_root=bytes.fromhex(obj["batch_root"]),
            prev_hash=bytes.fromhex(obj["prev_hash"]),
            signer_key_ref=obj["signer_key_ref"],
            signature=bytes.fromhex(obj["signature"]),
        )


def verify_checkpoint_entry(
    entry: CheckpointEntry,
    *,
    expected_prev_hash: Optional[bytes] = None,
    verifier: Optional[Ed25519Verifier] = None,
) -> bool:
    """Independently verify a checkpoint entry — fail-closed. True iff the Ed25519 signature authenticates the
    signed ``(height, batch_root, prev_hash)`` and (when supplied) the entry chains to ``expected_prev_hash``.
    Never raises; any malformed input returns False."""
    verifier = verifier or Ed25519Verifier()
    try:
        if expected_prev_hash is not None and entry.prev_hash != expected_prev_hash:
            return False
        return verifier.verify(entry.signing_input(), entry.signature, entry.signer_key_ref) is not None
    except (ValueError, TypeError):
        return False


class CheckpointAnchorBackend(AnchorBackend):
    """Append-only, Ed25519-signed local checkpoint. ``anchor(batch_root)`` signs the root into the chain and
    returns an A2 attestation backed by a durable signed entry (NOT a fabricated cert)."""

    def __init__(
        self,
        signing_key: Ed25519PrivateKey,
        *,
        log_path: Optional[Union[str, Path]] = None,
    ) -> None:
        self._sk = signing_key
        self._key_ref = signing_key.public_key().public_bytes_raw().hex()
        self._log_path = Path(log_path) if log_path is not None else None
        self._prev_hash = GENESIS_PREV_HASH
        self._next_height = 0
        self._by_root: Dict[bytes, CheckpointEntry] = {}
        if self._log_path is not None and self._log_path.exists():
            self._recover()

    @classmethod
    def from_seed(cls, seed: bytes, *, log_path: Optional[Union[str, Path]] = None) -> "CheckpointAnchorBackend":
        """Deterministic signer from a 32-byte seed — for demo-trust hosts and reproducible tests."""
        return cls(Ed25519PrivateKey.from_private_bytes(seed), log_path=log_path)

    @property
    def signer_key_ref(self) -> str:
        return self._key_ref

    def _recover(self) -> None:
        """Resume the chain from a durable log: replay entries, restoring ``prev_hash`` and the next height."""
        last: Optional[CheckpointEntry] = None
        with self._log_path.open("r", encoding="utf-8") as fh:
            for line in fh:
                line = line.strip()
                if not line:
                    continue
                entry = CheckpointEntry.from_json(json.loads(line))
                if not verify_checkpoint_entry(entry, expected_prev_hash=self._prev_hash):
                    raise AnchorError(f"checkpoint log corrupt at height {entry.height} (chain/signature)")
                self._by_root[entry.batch_root] = entry
                self._prev_hash = entry.entry_hash()
                last = entry
        if last is not None:
            self._next_height = last.height + 1

    def anchor(self, batch_root: bytes) -> AnchorAttestation:
        if len(batch_root) != 32:
            raise AnchorError("checkpoint anchor requires a 32-byte batch_root")
        height = self._next_height
        entry = CheckpointEntry(
            height=height,
            batch_root=batch_root,
            prev_hash=self._prev_hash,
            signer_key_ref=self._key_ref,
            signature=b"",  # filled below; signing_input excludes the signature
        )
        signature = self._sk.sign(entry.signing_input())
        entry = CheckpointEntry(
            height=height,
            batch_root=batch_root,
            prev_hash=self._prev_hash,
            signer_key_ref=self._key_ref,
            signature=signature,
        )
        # durable BEFORE we advance in-memory state (fail-closed: a write error must not yield an attestation)
        if self._log_path is not None:
            self._log_path.parent.mkdir(parents=True, exist_ok=True)
            with self._log_path.open("a", encoding="utf-8") as fh:
                fh.write(json.dumps(entry.to_json(), sort_keys=True) + "\n")
        self._by_root[batch_root] = entry
        self._prev_hash = entry.entry_hash()
        self._next_height = height + 1
        return AnchorAttestation(
            opaque_root=batch_root,  # honest: a local checkpoint commits the root directly (no fold)
            anchored_at_height=height,
            finality_certificate_hash=DEFAULT_PROFILE.digest(b"checkpoint-cert:" + signature),
            assurance_level=ASSURANCE_SELF_ATTESTED,
            block_hash=entry.entry_hash(),
        )

    def entry_for_root(self, batch_root: bytes) -> Optional[CheckpointEntry]:
        """The signed chain entry for a previously-anchored root — the artifact a relying party re-verifies."""
        return self._by_root.get(batch_root)
