//! The operation — extension §5.
//!
//! One definition serves three products: an input event is an op from
//! one actor (single-player), a sealed log of ops is a replay, and N
//! actors' interleaved logs are a shared world. Identity is
//! `(actor, lamport)`; ordering is the content-derived canonical key
//! `(effect_tick, lamport, actor)`; causality is the `deps` list; and
//! every op is Ed25519-signed under the `wai:world-op` domain.
//!
//! Wire layout (all integers little-endian):
//! ```text
//! actor(32) | lamport u64 | effect_tick u64
//! n_deps u16 | deps: n × (actor 32 + lamport u64)
//! target_node(16) | mutation_kind u16 | mut_len u32 | mutation bytes
//! signature(64)  — Ed25519 over DOMAIN_OP || all preceding bytes
//! ```

use super::{DOMAIN_NODE, DOMAIN_OP, WorldError};
use ed25519_dalek::{Signer, SigningKey};

/// Op identity: `(actor public key, actor-local Lamport counter)`.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct OpId {
    pub actor: [u8; 32],
    pub lamport: u64,
}

/// A signed structural mutation bound to an effect tick.
#[derive(Clone, Debug, PartialEq)]
pub struct CausalOp {
    pub actor: [u8; 32],
    pub lamport: u64,
    pub effect_tick: u64,
    pub deps: Vec<OpId>,
    pub target: [u8; 16],
    pub mutation_kind: u16,
    pub mutation: Vec<u8>,
    pub signature: [u8; 64],
}

impl CausalOp {
    pub fn id(&self) -> OpId {
        OpId { actor: self.actor, lamport: self.lamport }
    }

    /// Canonical total order (extension §5.2): a pure function of op
    /// content, so two sinks holding the same op set hold the same
    /// order — always, regardless of arrival.
    pub fn order_key(&self) -> (u64, u64, [u8; 32]) {
        (self.effect_tick, self.lamport, self.actor)
    }

    /// The signed bytes: everything except the signature itself.
    fn preimage(&self) -> Vec<u8> {
        let mut out = Vec::with_capacity(128 + self.deps.len() * 40 + self.mutation.len());
        out.extend_from_slice(&self.actor);
        out.extend_from_slice(&self.lamport.to_le_bytes());
        out.extend_from_slice(&self.effect_tick.to_le_bytes());
        out.extend_from_slice(&(self.deps.len() as u16).to_le_bytes());
        for d in &self.deps {
            out.extend_from_slice(&d.actor);
            out.extend_from_slice(&d.lamport.to_le_bytes());
        }
        out.extend_from_slice(&self.target);
        out.extend_from_slice(&self.mutation_kind.to_le_bytes());
        out.extend_from_slice(&(self.mutation.len() as u32).to_le_bytes());
        out.extend_from_slice(&self.mutation);
        out
    }

    /// Build and sign an op. The actor field is derived from the key —
    /// an op can only ever claim the identity that signed it.
    #[allow(clippy::too_many_arguments)]
    pub fn sign(
        key: &SigningKey,
        lamport: u64,
        effect_tick: u64,
        deps: Vec<OpId>,
        target: [u8; 16],
        mutation_kind: u16,
        mutation: Vec<u8>,
    ) -> CausalOp {
        let mut op = CausalOp {
            actor: key.verifying_key().to_bytes(),
            lamport,
            effect_tick,
            deps,
            target,
            mutation_kind,
            mutation,
            signature: [0u8; 64],
        };
        let mut msg = DOMAIN_OP.to_vec();
        msg.extend_from_slice(&op.preimage());
        op.signature = key.sign(&msg).to_bytes();
        op
    }

    /// Verify the signature against the op's own actor key.
    pub fn verify(&self) -> Result<(), WorldError> {
        let mut msg = DOMAIN_OP.to_vec();
        msg.extend_from_slice(&self.preimage());
        crate::sig::verify_strict(&self.actor, &msg, &self.signature).map_err(|e| match e {
            crate::sig::SigError::BadKey => WorldError::BadActorKey,
            crate::sig::SigError::BadSignature => WorldError::BadSignature,
        })
    }

    pub fn to_bytes(&self) -> Vec<u8> {
        let mut out = self.preimage();
        out.extend_from_slice(&self.signature);
        out
    }

    /// Parse one op from the front of `bytes`; returns the op and the
    /// number of bytes consumed (op logs are plain concatenation).
    pub fn from_bytes(bytes: &[u8]) -> Result<(CausalOp, usize), WorldError> {
        let mut c = Cursor { bytes, pos: 0 };
        let actor = c.take_array::<32>()?;
        let lamport = c.take_u64()?;
        let effect_tick = c.take_u64()?;
        let n_deps = c.take_u16()? as usize;
        let mut deps = Vec::with_capacity(n_deps);
        for _ in 0..n_deps {
            let a = c.take_array::<32>()?;
            let l = c.take_u64()?;
            deps.push(OpId { actor: a, lamport: l });
        }
        let target = c.take_array::<16>()?;
        let mutation_kind = c.take_u16()?;
        let mut_len = c.take_u32()? as usize;
        let mutation = c.take_slice(mut_len)?.to_vec();
        let signature = c.take_array::<64>()?;
        Ok((
            CausalOp { actor, lamport, effect_tick, deps, target, mutation_kind, mutation, signature },
            c.pos,
        ))
    }

    /// Parse a concatenated op log to completion.
    pub fn parse_log(mut bytes: &[u8]) -> Result<Vec<CausalOp>, WorldError> {
        let mut ops = Vec::new();
        while !bytes.is_empty() {
            let (op, used) = CausalOp::from_bytes(bytes)?;
            ops.push(op);
            bytes = &bytes[used..];
        }
        Ok(ops)
    }
}

/// Deterministic, coordination-free node minting (extension §5.4):
/// `BLAKE3(domain || actor || lamport_le)[0..16]`. Two actors creating
/// nodes concurrently can never collide, and the id is checkable by
/// every sink.
pub fn mint_node_id(actor: &[u8; 32], lamport: u64) -> [u8; 16] {
    let mut h = blake3::Hasher::new();
    h.update(DOMAIN_NODE);
    h.update(actor);
    h.update(&lamport.to_le_bytes());
    let mut id = [0u8; 16];
    id.copy_from_slice(&h.finalize().as_bytes()[..16]);
    id
}

struct Cursor<'a> {
    bytes: &'a [u8],
    pos: usize,
}

impl<'a> Cursor<'a> {
    fn take_slice(&mut self, n: usize) -> Result<&'a [u8], WorldError> {
        let end = self.pos.checked_add(n).ok_or(WorldError::Truncated)?;
        if end > self.bytes.len() {
            return Err(WorldError::Truncated);
        }
        let s = &self.bytes[self.pos..end];
        self.pos = end;
        Ok(s)
    }
    fn take_array<const N: usize>(&mut self) -> Result<[u8; N], WorldError> {
        Ok(self.take_slice(N)?.try_into().unwrap())
    }
    fn take_u16(&mut self) -> Result<u16, WorldError> {
        Ok(u16::from_le_bytes(self.take_array::<2>()?))
    }
    fn take_u32(&mut self) -> Result<u32, WorldError> {
        Ok(u32::from_le_bytes(self.take_array::<4>()?))
    }
    fn take_u64(&mut self) -> Result<u64, WorldError> {
        Ok(u64::from_le_bytes(self.take_array::<8>()?))
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::world::sim::{kin_mutation, pack_fx};
    use crate::world::{mint_node_id, Fx};

    /// An op's signature is verified strictly (SPEC §7 *Signatures*): a key of small
    /// order, a non-canonical spelling of one, and an R of small order are each refused,
    /// though a lenient check accepts each; a key's defect is `BadActorKey`, R's
    /// `BadSignature`.
    #[test]
    fn the_signature_is_verified_strictly() {
        let a = SigningKey::from_bytes(&[1; 32]);
        let op = CausalOp::sign(&a, 1, 1, vec![], mint_node_id(&a.verifying_key().to_bytes(), 1), kin_mutation::CREATE, pack_fx(&[Fx::ZERO; 7]));
        let set = |o: &mut CausalOp, k, s| (o.actor, o.signature) = (k, s);
        let msg = |o: &CausalOp| [DOMAIN_OP, &o.preimage()].concat();
        crate::sig::assert_refuses_forgeries(&op, set, msg, |o| o.verify().is_ok());
        for f in crate::sig::forgeries(|k| {
            let mut o = op.clone();
            set(&mut o, *k, [0; 64]);
            msg(&o)
        }) {
            let mut o = op.clone();
            set(&mut o, f.key, f.sig);
            match f.refusal {
                crate::sig::SigError::BadKey => assert!(matches!(o.verify(), Err(WorldError::BadActorKey)), "{}", f.name),
                crate::sig::SigError::BadSignature => assert!(matches!(o.verify(), Err(WorldError::BadSignature)), "{}", f.name),
            }
        }
    }
}
