//! Actor key succession (bl-017).
//!
//! A [`OpPayload::KeySuccession`] record binds an actor's `old_key` to a
//! `new_key` and is signed by the old key — so only the holder of the
//! current key can authorize its successor. Persisted to the oplog (DAG
//! anchored, state-neutral), these records let a verifier chain trust
//! backwards from an actor's current key: a witness signed by a superseded
//! key still verifies, because the chain proves that key was once
//! authoritative. A record signed by anyone other than the declared
//! `old_key` is ignored, so a forged succession cannot extend the chain.

use std::collections::{HashMap, HashSet};

use ed25519_dalek::SigningKey;

use crate::hlc::Hlc;
use crate::op_id::OpId;
use crate::operation::{OpBody, OpPayload, Operation};
use crate::signature::PublicKey;

/// Build a signed key-succession record: `actor_id` rotates from the key
/// behind `old_signing_key` to `new_key`. Signed by the old key, with the
/// body's `actor` set to the old public key so the record self-proves its
/// signer is the predecessor. `parents` + `timestamp` anchor it in the
/// oplog DAG (the caller passes the current heads and a monotonic HLC).
pub fn build_key_succession(
    actor_id: &str,
    old_signing_key: &SigningKey,
    new_key: PublicKey,
    parents: Vec<OpId>,
    timestamp: Hlc,
) -> Operation {
    let old_key = PublicKey::from_signing_key(old_signing_key);
    let body = OpBody {
        actor: old_key,
        parents,
        payload: OpPayload::KeySuccession {
            actor_id: actor_id.to_owned(),
            new_key,
            old_key,
        },
        timestamp,
    };
    Operation::sign(body, old_signing_key)
}

/// Resolve every key valid for `actor_id`, given a set of operations and the
/// actor's current trusted key (e.g. from `actors.ncl`). Walks the
/// succession chain backwards from `current_key`, admitting a predecessor
/// only when its record is signed by the declared `old_key` (== the body
/// actor) and verifies — forged records never extend the chain. The result
/// always contains `current_key`.
pub fn valid_keys_for_actor(
    ops: &[Operation],
    actor_id: &str,
    current_key: PublicKey,
) -> HashSet<PublicKey> {
    // new_key -> old_key for every well-formed, verified succession.
    let mut predecessor: HashMap<PublicKey, PublicKey> = HashMap::new();
    for op in ops {
        let OpPayload::KeySuccession {
            actor_id: aid,
            new_key,
            old_key,
        } = &op.body.payload
        else {
            continue;
        };
        if aid != actor_id {
            continue;
        }
        // Unforgeable: the signer must be the declared predecessor.
        if op.body.actor != *old_key {
            continue;
        }
        if op.verify_signature().is_err() {
            continue;
        }
        predecessor.insert(*new_key, *old_key);
    }

    let mut valid = HashSet::new();
    let mut cursor = current_key;
    valid.insert(cursor);
    while let Some(prev) = predecessor.get(&cursor) {
        if !valid.insert(*prev) {
            break; // cycle guard
        }
        cursor = *prev;
    }
    valid
}

#[cfg(test)]
mod tests {
    use super::*;

    fn key(seed: u8) -> SigningKey {
        SigningKey::from_bytes(&[seed; 32])
    }

    fn pk(seed: u8) -> PublicKey {
        PublicKey::from_signing_key(&key(seed))
    }

    #[test]
    fn prior_key_stays_valid_after_rotation() {
        // agent rotates old(1) -> new(2), record signed by old(1).
        let succ = build_key_succession("agent", &key(1), pk(2), vec![], Hlc::new(1, 0));
        let valid = valid_keys_for_actor(&[succ], "agent", pk(2));
        assert!(valid.contains(&pk(2)), "current key is valid");
        assert!(
            valid.contains(&pk(1)),
            "a witness signed by the superseded key still verifies via the chain"
        );
        assert_eq!(valid.len(), 2);
    }

    #[test]
    fn multi_hop_chain_resolves_all_ancestors() {
        // 1 -> 2 -> 3, each link signed by its predecessor.
        let s1 = build_key_succession("agent", &key(1), pk(2), vec![], Hlc::new(1, 0));
        let s2 = build_key_succession("agent", &key(2), pk(3), vec![], Hlc::new(2, 0));
        let valid = valid_keys_for_actor(&[s1, s2], "agent", pk(3));
        assert!([pk(1), pk(2), pk(3)].iter().all(|k| valid.contains(k)));
        assert_eq!(valid.len(), 3);
    }

    #[test]
    fn forged_succession_is_ignored() {
        // A record claiming old=pk(1) -> new=pk(9) but signed by the forger
        // key(7): the body actor (pk(7)) does not match the declared old
        // key, and even if it did the signature would not verify under
        // pk(1). The chain from current=pk(1) must NOT admit pk(9).
        let forger = key(7);
        let body = OpBody {
            actor: PublicKey::from_signing_key(&forger),
            parents: vec![],
            payload: OpPayload::KeySuccession {
                actor_id: "agent".to_owned(),
                new_key: pk(9),
                old_key: pk(1),
            },
            timestamp: Hlc::new(1, 0),
        };
        let forged = Operation::sign(body, &forger);

        let valid = valid_keys_for_actor(&[forged], "agent", pk(1));
        assert_eq!(
            valid,
            HashSet::from([pk(1)]),
            "forged successor is excluded"
        );

        // And a forged record cannot strand a real key: from current pk(9)
        // (the forger's target) it resolves only itself, never pk(1).
        let valid_from_9 =
            valid_keys_for_actor(&[build_forged_old_signed_by_wrong_key()], "agent", pk(9));
        assert_eq!(valid_from_9, HashSet::from([pk(9)]));
    }

    fn build_forged_old_signed_by_wrong_key() -> Operation {
        // old_key declared as pk(1) but signed by key(2) — actor==old_key
        // holds visually only if we set actor=pk(1); but then the signature
        // (by key 2) fails verify_signature. Construct exactly that.
        let body = OpBody {
            actor: pk(1),
            parents: vec![],
            payload: OpPayload::KeySuccession {
                actor_id: "agent".to_owned(),
                new_key: pk(9),
                old_key: pk(1),
            },
            timestamp: Hlc::new(1, 0),
        };
        Operation::sign(body, &key(2)) // wrong signer
    }

    #[test]
    fn succession_for_other_actor_does_not_leak() {
        let succ = build_key_succession("other", &key(1), pk(2), vec![], Hlc::new(1, 0));
        let valid = valid_keys_for_actor(&[succ], "agent", pk(2));
        assert_eq!(
            valid,
            HashSet::from([pk(2)]),
            "a different actor's succession does not extend this actor's chain"
        );
    }
}
