//! Merkle-style ledger helpers for SRBN traces.
//!
//! This crate provides a small SHA-256 hash chain for committing trace payloads.
//! It does not provide durable storage; applications should persist entries in
//! their own database, object store, or audit log.

use sha2::{Digest, Sha256};
use srbn::{RunResult, run_result_to_json};

/// The all-zero genesis parent hash.
pub const GENESIS_HASH: &str = "0000000000000000000000000000000000000000000000000000000000000000";

/// One committed payload in a Merkle-style hash chain.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LedgerEntry {
    pub index: usize,
    pub parent_hash: String,
    pub payload_hash: String,
    pub hash: String,
    pub payload: String,
}

impl LedgerEntry {
    /// Build and hash a ledger entry.
    pub fn new(index: usize, parent_hash: impl Into<String>, payload: impl Into<String>) -> Self {
        let parent_hash = parent_hash.into();
        let payload = payload.into();
        let payload_hash = sha256_hex(payload.as_bytes());
        let hash = entry_hash(index, &parent_hash, &payload_hash);
        Self {
            index,
            parent_hash,
            payload_hash,
            hash,
            payload,
        }
    }

    /// Recompute this entry's hash and payload hash.
    pub fn verify(&self) -> bool {
        self.payload_hash == sha256_hex(self.payload.as_bytes())
            && self.hash == entry_hash(self.index, &self.parent_hash, &self.payload_hash)
    }
}

/// In-memory Merkle-style ledger.
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct MerkleLedger {
    entries: Vec<LedgerEntry>,
}

impl MerkleLedger {
    pub fn new() -> Self {
        Self {
            entries: Vec::new(),
        }
    }

    pub fn entries(&self) -> &[LedgerEntry] {
        &self.entries
    }

    pub fn head(&self) -> &str {
        self.entries
            .last()
            .map(|entry| entry.hash.as_str())
            .unwrap_or(GENESIS_HASH)
    }

    pub fn commit(&mut self, payload: impl Into<String>) -> &LedgerEntry {
        let entry = LedgerEntry::new(self.entries.len(), self.head(), payload);
        self.entries.push(entry);
        self.entries.last().expect("entry was just pushed")
    }

    pub fn commit_run_result(&mut self, result: &RunResult) -> &LedgerEntry {
        self.commit(run_result_to_json(result))
    }

    pub fn verify(&self) -> bool {
        let mut expected_parent = GENESIS_HASH.to_string();
        for (index, entry) in self.entries.iter().enumerate() {
            if entry.index != index || entry.parent_hash != expected_parent || !entry.verify() {
                return false;
            }
            expected_parent = entry.hash.clone();
        }
        true
    }
}

/// Compute a SHA-256 digest as lowercase hex.
pub fn sha256_hex(bytes: &[u8]) -> String {
    let digest = Sha256::digest(bytes);
    hex::encode(digest)
}

fn entry_hash(index: usize, parent_hash: &str, payload_hash: &str) -> String {
    let entry = format!(
        "srbn-ledger-entry-v1\nindex:{index}\nparent:{parent_hash}\npayload:{payload_hash}\n"
    );
    sha256_hex(entry.as_bytes())
}

#[cfg(test)]
mod tests {
    use srbn::{Policy, boxed_check, contains, run};

    use super::*;

    #[test]
    fn commits_and_verifies_payload_chain() {
        let mut ledger = MerkleLedger::new();
        ledger.commit("first");
        ledger.commit("second");
        assert!(ledger.verify());
        assert_ne!(ledger.head(), GENESIS_HASH);
        assert_eq!(ledger.entries()[1].parent_hash, ledger.entries()[0].hash);
    }

    #[test]
    fn detects_tampering() {
        let mut ledger = MerkleLedger::new();
        ledger.commit("first");
        ledger.entries[0].payload = "changed".to_string();
        assert!(!ledger.verify());
    }

    #[test]
    fn commits_run_result() {
        let checks = vec![boxed_check(contains("ok"))];
        let result = run(
            "task",
            |_prompt: &str, _feedback: Option<&str>| Ok("ok".to_string()),
            &checks,
            Policy::default(),
        )
        .unwrap();
        let mut ledger = MerkleLedger::new();
        let entry = ledger.commit_run_result(&result);
        assert!(entry.payload.contains("\"status\":\"stable\""));
        assert!(ledger.verify());
    }
}
