"""
StepEnvelope — Canonical envelope for PG-DAG step execution.

The envelope is the *input* to the ProofWriter.  It captures:
  - What step is being run (step_id, spec_version)
  - What data goes in (inputs + inputs_fingerprint)
  - What data comes out (outputs + outputs_fingerprint)
  - Who is running it (actor)
  - When it was created

The StepEnvelopeBuilder uses SnapChore (or local fallback) to compute
deterministic fingerprints.  This is what makes idempotency safe by
construction — same inputs always produce the same fingerprint.
"""

from __future__ import annotations

from dataclasses import dataclass, field
from datetime import datetime, timezone
from typing import Any, Dict, Optional, Protocol, runtime_checkable

from .artifact import canonical_fingerprint


# ---------------------------------------------------------------------------
# SnapChore capture protocol
# ---------------------------------------------------------------------------

@runtime_checkable
class SnapChoreCapture(Protocol):
    """Protocol for SnapChore capture operations.

    Any object with a `capture()` method satisfies this.
    The SBN SDK's `SnapChoreClient` implements this natively.
    """
    def capture(
        self,
        payload: Dict[str, Any],
        volatile_keys: Optional[list] = None,
    ) -> Dict[str, Any]:
        """Capture a payload and return {hash, ...}."""
        ...


# ---------------------------------------------------------------------------
# Step envelope
# ---------------------------------------------------------------------------

@dataclass
class StepEnvelope:
    """Canonical envelope for a single DAG step execution.

    Created by the StepEnvelopeBuilder before execution.
    Finalized by the builder after execution (outputs + outputs_fingerprint).
    Then handed to the ProofWriter for sealing.
    """
    step_id: str
    spec_version: str
    inputs: Dict[str, Any]
    config: Dict[str, Any] = field(default_factory=dict)
    actor: str = ""
    inputs_fingerprint: str = ""
    outputs: Optional[Dict[str, Any]] = None
    outputs_fingerprint: str = ""
    created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))

    def to_seal_payload(self) -> Dict[str, Any]:
        """Produce the dict that gets sealed by SnapChore / ProofWriter."""
        return {
            "step_id": self.step_id,
            "spec_version": self.spec_version,
            "inputs_fingerprint": self.inputs_fingerprint,
            "outputs_fingerprint": self.outputs_fingerprint,
            "actor": self.actor,
            "created_at": self.created_at.isoformat(),
        }


# ---------------------------------------------------------------------------
# Envelope builder
# ---------------------------------------------------------------------------

class StepEnvelopeBuilder:
    """Builds and finalizes StepEnvelopes with deterministic fingerprints.

    Uses SnapChore capture when available, falls back to local SHA-256.

    Usage:
        builder = StepEnvelopeBuilder(snapchore=sbn_client.snapchore)
        envelope = builder.build("reserve_float", "1.0", inputs, actor="sp-engine-yield")
        # ... execute step ...
        envelope = builder.finalize(envelope, outputs)
        # ... hand to ProofWriter ...
    """

    def __init__(self, snapchore: Optional[SnapChoreCapture] = None) -> None:
        self._snapchore = snapchore

    def _fingerprint(self, data: Dict[str, Any]) -> str:
        """Compute a deterministic fingerprint for data."""
        if self._snapchore is not None:
            try:
                result = self._snapchore.capture(data)
                return result.get("hash", result.get("snapchore_hash", ""))
            except Exception:
                pass
        return canonical_fingerprint(data)

    def build(
        self,
        step_id: str,
        spec_version: str,
        inputs: Dict[str, Any],
        *,
        config: Optional[Dict[str, Any]] = None,
        actor: str = "",
    ) -> StepEnvelope:
        """Build a pre-execution envelope with inputs fingerprint."""
        return StepEnvelope(
            step_id=step_id,
            spec_version=spec_version,
            inputs=inputs,
            config=config or {},
            actor=actor,
            inputs_fingerprint=self._fingerprint(inputs),
        )

    def finalize(
        self,
        envelope: StepEnvelope,
        outputs: Dict[str, Any],
    ) -> StepEnvelope:
        """Finalize an envelope after step execution with outputs fingerprint."""
        envelope.outputs = outputs
        envelope.outputs_fingerprint = self._fingerprint(outputs)
        return envelope
