"""
IdempotencyGate — Content-addressed duplicate detection.

Before running a step, the gate checks:
  inputs_fingerprint + step_id + spec_version → prior ProofArtifact?

If yes: return the prior proof and skip execution.
If no: let execution proceed, then record the new proof.

This makes retries safe by construction.  Duplicate wires, duplicate
transfers, duplicate orders are structurally impossible.

Two layers:
  1. Local in-memory cache (always available)
  2. Remote SnapChore discovery (optional, for cross-process dedup)
"""

from __future__ import annotations

import logging
from typing import Any, Dict, Optional, Protocol, runtime_checkable

from .artifact import ProofArtifact

logger = logging.getLogger(__name__)


# ---------------------------------------------------------------------------
# SnapChore discovery protocol
# ---------------------------------------------------------------------------

@runtime_checkable
class SnapChoreDiscover(Protocol):
    """Protocol for remote proof discovery via SnapChore."""
    def discover(self, snapchore_hash: str) -> Dict[str, Any]:
        ...


# ---------------------------------------------------------------------------
# IdempotencyGate
# ---------------------------------------------------------------------------

class IdempotencyGate:
    """Content-addressed proof cache with optional remote discovery.

    Key format: "{inputs_fingerprint}:{step_id}:{spec_version}"

    Usage:
        gate = IdempotencyGate(snapchore=sbn_client.snapchore)

        prior = gate.check(fp, step_id, version)
        if prior:
            return prior  # short-circuit

        # ... execute step ...
        gate.store(proof)
    """

    def __init__(self, snapchore: Optional[SnapChoreDiscover] = None) -> None:
        self._cache: Dict[str, ProofArtifact] = {}
        self._snapchore = snapchore

    def check(
        self,
        inputs_fingerprint: str,
        step_id: str,
        spec_version: str,
    ) -> Optional[ProofArtifact]:
        """Check if a proof exists for this (fingerprint, step, version) triple."""
        key = f"{inputs_fingerprint}:{step_id}:{spec_version}"

        # Local cache first
        if key in self._cache:
            logger.debug("Idempotency gate: local hit for %s", key[:40])
            return self._cache[key]

        # Remote discovery (optional)
        if self._snapchore is not None:
            try:
                result = self._snapchore.discover(inputs_fingerprint)
                if result and result.get("hash"):
                    # Reconstruct minimal ProofArtifact from discovery
                    proof = ProofArtifact(
                        step_id=step_id,
                        spec_version=spec_version,
                        inputs_fingerprint=inputs_fingerprint,
                        outputs_fingerprint=result.get("outputs_fingerprint", ""),
                        proof_hash=result["hash"],
                        block_ref=result.get("block_ref", ""),
                        status=result.get("status", "completed"),
                        metrics=result.get("metrics", {}),
                    )
                    self._cache[key] = proof
                    logger.debug("Idempotency gate: remote hit for %s", key[:40])
                    return proof
            except Exception as exc:
                logger.debug("SnapChore discovery failed: %s", exc)

        return None

    def store(self, proof: ProofArtifact) -> None:
        """Record a proof in the local cache."""
        self._cache[proof.idempotency_key] = proof

    def clear(self) -> None:
        """Clear the local cache (useful between runs)."""
        self._cache.clear()

    @property
    def size(self) -> int:
        return len(self._cache)
