//! Signed operations.
//!
//! `OpBody` carries the unsigned content (actor, parents, payload, timestamp).
//! `Operation` is `OpBody` plus a signature; its `OpId` is the blake3 hash of
//! the canonical encoding of the whole `Operation` (so the id covers the
//! signature too — distinct signatures over the same body yield distinct ids).
//!
//! Field declaration order is lexicographic to keep canonical CBOR map keys
//! sorted (RFC 8949 §4.2.1).

use ed25519_dalek::{Signer, SigningKey};
use serde::{Deserialize, Serialize};

use crate::canonical;
use crate::hlc::Hlc;
use crate::op_id::OpId;
use crate::signature::{PublicKey, Sig, SignatureError};
use crate::value::{AttrId, EntityId, Value};

/// Operation payload — variant set grows gate by gate.
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum OpPayload {
    /// No-op used as a placeholder until later gates introduce real variants.
    Noop {
        /// Caller-provided label captured into the canonical encoding.
        label: String,
    },
    /// Assert (attribute, value) pairs about an entity. Each pair becomes one
    /// EAV row in the triple store at `tx_time = op.timestamp`. `valid_from`,
    /// if present, declares the bitemporal valid-time at which the assertion
    /// takes effect; otherwise the assertion is valid from `tx_time` onward.
    EntityAssert {
        /// (attribute, value) pairs to assert.
        attrs: Vec<(AttrId, Value)>,
        /// Target entity.
        entity: EntityId,
        /// Optional explicit valid-time at which the assertion takes effect.
        valid_from: Option<Hlc>,
    },
    /// Provenance record: a pipeline witness persisted to the oplog
    /// (ADR-023 / ADR-036). The op asserts nothing about authoritative
    /// state — it attests the `state_root` a pipeline produced — so the
    /// commit, triple, and substrate reducers treat it as state-neutral.
    /// The enclosing `OpBody.parents` + `OpBody.timestamp` bind the
    /// witness to its DAG position (bl-022): reordering the log mutates
    /// the signed body and fails `verify_signature`.
    Witness {
        /// Blake3 hash over the canonical encoding of the attested op's
        /// effects.
        effects_hash: [u8; 32],
        /// Domain payload the op body emitted (canonical bytes for replay).
        extra_payload: Vec<u8>,
        /// Operation id the pipeline witness attests.
        op_id: String,
        /// Substrate state root at the moment of attestation.
        state_root: [u8; 32],
        /// Witness shape tag declared on the attested op.
        witness_shape: String,
        /// Ed25519 signature the pipeline produced over the witness payload.
        witness_sig: Sig,
    },
    /// Tier-descent marker (`payload_kind = tier_descent`, ADR-036): the
    /// final signed oplog entry written before a project leaves Tier2 for
    /// a lower tier. Lets external verifiers distinguish a deliberate
    /// descent from a witness chain that crashed mid-session. State-neutral
    /// — like [`OpPayload::Witness`], reducers assert no triples from it.
    TierDescent {
        /// Tier the project is descending from (always `"Tier2"`).
        from_tier: String,
        /// Tier the project is descending to (`"Tier1"` or `"Tier0"`).
        to_tier: String,
    },
    /// Retract (remove) the named attributes of an entity (ADR-041) — the
    /// inverse of [`OpPayload::EntityAssert`]. Each listed attribute's cell
    /// is removed from the commit layer; the triple store records a
    /// retraction at `tx_time = op.timestamp` so the EAV query view stops
    /// returning it. A retract is a witnessed, signed, DAG-anchored
    /// mutation — removal stays on the witness-as-axis-seam, not a side
    /// channel. Precondition for remove-based CRDT domains (OR-Set).
    Retract {
        /// Attributes to retract for the entity.
        attrs: Vec<AttrId>,
        /// Target entity.
        entity: EntityId,
    },
    /// Actor key succession (bl-017): an unforgeable record that `actor_id`
    /// rotated from `old_key` to `new_key`. MUST be signed by `old_key`
    /// (the enclosing `OpBody.actor` equals `old_key`), so only the holder
    /// of the current key can authorize its successor. Anchored in the
    /// oplog DAG; state-neutral. Verifiers chain trust backwards from the
    /// actor's current key through these records, keeping witnesses signed
    /// by superseded keys verifiable (see [`crate::succession`]).
    KeySuccession {
        /// Actor whose key is rotating.
        actor_id: String,
        /// The successor public key, valid for the actor going forward.
        new_key: PublicKey,
        /// The superseded public key — also the required signer.
        old_key: PublicKey,
    },
}

/// Unsigned operation body — the input to `Operation::sign`.
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct OpBody {
    /// Public key of the actor that authored this operation.
    pub actor: PublicKey,
    /// Causal parents — operation ids that must precede this one.
    pub parents: Vec<OpId>,
    /// Payload describing what this operation asserts.
    pub payload: OpPayload,
    /// Hybrid logical clock timestamp; must be greater than every parent's.
    pub timestamp: Hlc,
}

/// Signed operation.
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Operation {
    /// Unsigned body.
    pub body: OpBody,
    /// Ed25519 signature over `canonical::encode(&body)`.
    pub signature: Sig,
}

impl Operation {
    /// Sign an `OpBody` with `signing_key`. The signature is taken over the
    /// canonical encoding of the body alone.
    pub fn sign(body: OpBody, signing_key: &SigningKey) -> Self {
        let encoded =
            canonical::encode(&body).expect("canonical encoding never fails for owned types");
        let ed_sig = signing_key.sign(&encoded);
        Self {
            body,
            signature: Sig(ed_sig.to_bytes()),
        }
    }

    /// Verify the signature against the actor's public key. Returns
    /// `Ok(())` on a valid signature.
    pub fn verify_signature(&self) -> Result<(), SignatureError> {
        let encoded =
            canonical::encode(&self.body).expect("canonical encoding never fails for owned types");
        self.body.actor.verify(&encoded, &self.signature)
    }

    /// Content-addressed identifier. The id covers the signature, so a
    /// well-formed operation with a different signature would have a
    /// different id.
    pub fn id(&self) -> OpId {
        let encoded =
            canonical::encode(self).expect("canonical encoding never fails for owned types");
        OpId::from_bytes(&encoded)
    }
}
