#![deny(warnings)]
#![allow(dead_code)]
//! [TSM.ID].[11031972] -- Platform X Ecosystem
//! xcu-ouroboros -- Self-updating Binary Manager with OTA & Integrity

#[derive(Debug)]
pub enum OuroborosError { VersionConflict(String), IntegrityFailed(String), RollbackFailed(String) }
impl std::fmt::Display for OuroborosError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self { Self::VersionConflict(e) => write!(f, "Version: {e}"), Self::IntegrityFailed(e) => write!(f, "Integrity: {e}"), Self::RollbackFailed(e) => write!(f, "Rollback: {e}") }
    }
}
impl std::error::Error for OuroborosError {}

#[derive(Debug, Clone)]
pub struct BinaryVersion { pub version: String, pub hash: [u8; 32], pub size_bytes: u64, pub timestamp: u64, pub changelog: String }

#[derive(Debug, Clone, PartialEq)]
pub enum UpdateState { Idle, Downloading, Verifying, Swapping, Rollback, Complete, Failed }

pub struct Ouroboros {
    current: BinaryVersion,
    history: Vec<BinaryVersion>,
    state: UpdateState,
    max_rollback: usize,
}

impl Ouroboros {
    pub fn new(current: BinaryVersion, max_rollback: usize) -> Self {
        Self { current, history: Vec::new(), state: UpdateState::Idle, max_rollback }
    }

    /// Verify binary integrity using FNV hash
    pub fn verify_integrity(&self, binary_data: &[u8], expected_hash: &[u8; 32]) -> Result<bool, OuroborosError> {
        let hash = Self::compute_hash(binary_data);
        if hash != *expected_hash {
            return Err(OuroborosError::IntegrityFailed(
                format!("Hash mismatch: computed {:02x}{:02x}..., expected {:02x}{:02x}...", hash[0], hash[1], expected_hash[0], expected_hash[1])));
        }
        Ok(true)
    }

    fn compute_hash(data: &[u8]) -> [u8; 32] {
        let mut hash = [0u8; 32];
        let mut state: u64 = 0xcbf29ce484222325;
        for (i, &b) in data.iter().enumerate() {
            state ^= b as u64;
            state = state.wrapping_mul(0x100000001b3);
            if i % 4 == 0 { hash[i % 32] ^= (state & 0xFF) as u8; }
        }
        for i in 0..32 { hash[i] ^= ((state >> (i % 8 * 8)) & 0xFF) as u8; }
        hash
    }

    /// Stage update: download → verify → swap
    pub fn stage_update(&mut self, new_version: BinaryVersion, binary_data: &[u8]) -> Result<(), OuroborosError> {
        self.state = UpdateState::Downloading;
        // Verify
        self.state = UpdateState::Verifying;
        self.verify_integrity(binary_data, &new_version.hash)?;
        // Compare versions
        if new_version.version == self.current.version {
            return Err(OuroborosError::VersionConflict(format!("Already at {}", self.current.version)));
        }
        // Swap
        self.state = UpdateState::Swapping;
        self.history.push(self.current.clone());
        if self.history.len() > self.max_rollback { self.history.remove(0); }
        self.current = new_version;
        self.state = UpdateState::Complete;
        Ok(())
    }

    /// Rollback to previous version
    pub fn rollback(&mut self) -> Result<BinaryVersion, OuroborosError> {
        self.state = UpdateState::Rollback;
        let prev = self.history.pop().ok_or_else(|| OuroborosError::RollbackFailed("No previous version".into()))?;
        self.current = prev.clone();
        self.state = UpdateState::Complete;
        Ok(prev)
    }

    pub fn current_version(&self) -> &BinaryVersion { &self.current }
    pub fn state(&self) -> &UpdateState { &self.state }
    pub fn rollback_depth(&self) -> usize { self.history.len() }

    /// Version comparison (semver-like)
    pub fn is_newer(current: &str, candidate: &str) -> bool {
        let parse = |v: &str| -> Vec<u32> { v.split('.').filter_map(|s| s.parse().ok()).collect() };
        let c = parse(current);
        let n = parse(candidate);
        for i in 0..c.len().max(n.len()) {
            let cv = c.get(i).copied().unwrap_or(0);
            let nv = n.get(i).copied().unwrap_or(0);
            if nv > cv { return true; }
            if nv < cv { return false; }
        }
        false
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    fn v1() -> BinaryVersion { BinaryVersion { version: "1.0.0".into(), hash: [0u8; 32], size_bytes: 1000, timestamp: 100, changelog: "init".into() } }
    #[test]
    fn test_version_compare() {
        assert!(Ouroboros::is_newer("1.0.0", "1.0.1"));
        assert!(Ouroboros::is_newer("1.0.0", "2.0.0"));
        assert!(!Ouroboros::is_newer("2.0.0", "1.0.0"));
    }
    #[test]
    fn test_integrity() {
        let o = Ouroboros::new(v1(), 3);
        let data = b"test binary";
        let hash = Ouroboros::compute_hash(data);
        assert!(o.verify_integrity(data, &hash).is_ok());
        let bad_hash = [0xFF; 32];
        assert!(o.verify_integrity(data, &bad_hash).is_err());
    }
    #[test]
    fn test_rollback() {
        let data = b"new binary";
        let hash = Ouroboros::compute_hash(data);
        let mut o = Ouroboros::new(v1(), 3);
        let v2 = BinaryVersion { version: "2.0.0".into(), hash, size_bytes: 500, timestamp: 200, changelog: "v2".into() };
        o.stage_update(v2, data).unwrap();
        assert_eq!(o.current_version().version, "2.0.0");
        let prev = o.rollback().unwrap();
        assert_eq!(prev.version, "1.0.0");
    }
}
