//! Supersession data structures for error correction.

use rkyv::{Archive, Deserialize, Serialize};

use super::hlc::HlcTimestamp;
use super::Hash;

/// Defines the nature of a paradigm shift or error correction.
///
/// Used for both Epoch supersession and individual Assertion supersession.
/// The type determines how the old knowledge should be treated:
///
/// # Use Cases
///
/// | Type | Old Knowledge | New Knowledge | Example |
/// |------|---------------|---------------|---------|
/// | Invalidate | Wrong, hide | Correct | Agent deployed bad config |
/// | Temporal | Was true, historical | Now true | "President is Obama" → "President is Biden" |
/// | Refinement | Simplification | More precise | Newtonian → Relativity |
/// | RequiresReview | Uncertain | Pending | Flagged for human review |
/// | Additive | Still valid | Adds context | Original + clarification |
#[derive(Archive, Deserialize, Serialize, Debug, Clone, Copy, PartialEq, Eq)]
#[archive(check_bytes)]
pub enum SupersessionType {
    /// The old assertion was factually incorrect and should be hidden/ignored.
    /// Example: Agent deployed wrong config, need to invalidate that decision.
    Invalidate,
    /// The old assertion was correct at the time but is now outdated.
    /// Example: "President is Obama" (2015) → "President is Biden" (2024).
    Temporal,
    /// The old assertion was a simplification; the new one is more precise.
    /// Example: Newtonian Physics → Relativity.
    Refinement,
    /// The old assertion needs human review before trusting.
    /// Example: Flagged by automated check, pending supervisor decision.
    RequiresReview,
    /// The new assertion adds context without invalidating the old one.
    /// Example: Original claim + clarifying note.
    Additive,
}

/// A record of one assertion superseding another.
///
/// Supersession enables error correction without violating append-only semantics.
/// Instead of mutating an assertion, we create a supersession record that points
/// from the old (target) to the new (replacement) assertion.
///
/// # Storage Layout
///
/// | Key Pattern | Value | Purpose |
/// |-------------|-------|---------|
/// | `SUP:{target_hash}` | Serialized Supersession | Quick lookup: "Is this superseded?" |
/// | `SUP:IDX:{agent_id}:{timestamp}` | target_hash | Audit: "What did this agent supersede?" |
///
/// # Use Case: 3am Incident Investigation
///
/// 1. SRE finds agent deployed bad config
/// 2. Trace shows agent queried assertion X with hash `abc123`
/// 3. Supervisor creates supersession: `abc123` → `new_hash` (Invalidate)
/// 4. Future queries skip `abc123` automatically
/// 5. Audit trail preserved: "Who fixed it? When? Why?"
///
/// # Example
///
/// ```ignore
/// let supersession = Supersession {
///     target_hash: bad_assertion_hash,
///     supersession_type: SupersessionType::Invalidate,
///     reason: "Proposal treated as approved. See incident INC-2024-001".to_string(),
///     new_hash: Some(corrected_assertion_hash),
///     timestamp: now(),
///     hlc_timestamp: Some(hlc_clock.now()), // For distributed causal ordering
///     agent_id: supervisor_public_key,
///     signature: supervisor_signature,
/// };
/// ```
#[derive(Archive, Deserialize, Serialize, Debug, Clone, PartialEq)]
#[archive(check_bytes)]
pub struct Supersession {
    /// Hash of the assertion being superseded (the "old" or "bad" one).
    pub target_hash: Hash,
    /// How the target is being superseded.
    pub supersession_type: SupersessionType,
    /// Human-readable explanation (for audit trail).
    /// Example: "Proposal treated as approved. See incident INC-2024-001"
    pub reason: String,
    /// Hash of the replacement assertion (if any).
    /// None for RequiresReview (flagging, not replacing) or pure invalidation.
    pub new_hash: Option<Hash>,
    /// Unix timestamp when the supersession was created.
    ///
    /// Kept for backward compatibility. New supersessions should also set
    /// `hlc_timestamp` for distributed causal ordering.
    pub timestamp: u64,
    /// Hybrid Logical Clock timestamp for distributed causal ordering.
    ///
    /// Provides causal ordering guarantees across distributed nodes. When
    /// comparing supersessions from different nodes, HLC comparison is
    /// preferred over `timestamp` when available.
    ///
    /// # Migration
    ///
    /// - New supersessions: Set both `timestamp` and `hlc_timestamp`
    /// - Existing supersessions: Have `hlc_timestamp: None`
    /// - Comparison: Use HLC when available, fall back to `timestamp`
    pub hlc_timestamp: Option<HlcTimestamp>,
    /// Ed25519 public key of the agent creating the supersession.
    pub agent_id: [u8; 32],
    /// Ed25519 signature over the supersession content.
    /// Signs: BLAKE3(target_hash || type || reason || new_hash || timestamp)
    pub signature: [u8; 64],
}

impl Supersession {
    /// Compares two supersessions by their temporal ordering.
    ///
    /// Uses HLC timestamp when available for causal ordering, otherwise
    /// falls back to Unix timestamp comparison.
    ///
    /// # Returns
    ///
    /// - `std::cmp::Ordering::Less` if `self` is before `other`
    /// - `std::cmp::Ordering::Greater` if `self` is after `other`
    /// - `std::cmp::Ordering::Equal` if they have the same timestamp
    pub fn temporal_cmp(&self, other: &Self) -> std::cmp::Ordering {
        match (&self.hlc_timestamp, &other.hlc_timestamp) {
            // Both have HLC: use causal ordering
            (Some(a), Some(b)) => a.cmp(b),
            // Only self has HLC: prefer HLC physical time vs other's timestamp
            (Some(a), None) => {
                let self_ms = a.millis();
                let other_ms = other.timestamp * 1000; // Convert seconds to millis if needed
                self_ms.cmp(&other_ms)
            }
            // Only other has HLC: prefer other's HLC physical time
            (None, Some(b)) => {
                let self_ms = self.timestamp * 1000;
                let other_ms = b.millis();
                self_ms.cmp(&other_ms)
            }
            // Neither has HLC: fall back to Unix timestamp
            (None, None) => self.timestamp.cmp(&other.timestamp),
        }
    }

    /// Returns true if this supersession has causal ordering information.
    ///
    /// Supersessions with HLC timestamps can be reliably ordered across
    /// distributed nodes, even in the presence of clock skew.
    pub fn has_hlc(&self) -> bool {
        self.hlc_timestamp.is_some()
    }
}

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

    fn create_supersession(timestamp: u64, hlc: Option<HlcTimestamp>) -> Supersession {
        Supersession {
            target_hash: [0u8; 32],
            supersession_type: SupersessionType::Temporal,
            reason: "test".to_string(),
            new_hash: None,
            timestamp,
            hlc_timestamp: hlc,
            agent_id: [0u8; 32],
            signature: [0u8; 64],
        }
    }

    #[test]
    fn test_temporal_cmp_both_hlc() {
        // When both have HLC, use HLC comparison
        let hlc1 = HlcTimestamp::new(1000, [1u8; 16]);
        let hlc2 = HlcTimestamp::new(2000, [1u8; 16]);

        let s1 = create_supersession(100, Some(hlc1));
        let s2 = create_supersession(200, Some(hlc2));

        // HLC timestamps should determine order (s1 < s2)
        assert_eq!(s1.temporal_cmp(&s2), Ordering::Less);
        assert_eq!(s2.temporal_cmp(&s1), Ordering::Greater);
    }

    #[test]
    fn test_temporal_cmp_neither_hlc() {
        // When neither has HLC, use Unix timestamp
        let s1 = create_supersession(100, None);
        let s2 = create_supersession(200, None);

        assert_eq!(s1.temporal_cmp(&s2), Ordering::Less);
        assert_eq!(s2.temporal_cmp(&s1), Ordering::Greater);
    }

    #[test]
    fn test_temporal_cmp_only_first_has_hlc() {
        // When only first has HLC, compare HLC millis to other's timestamp*1000
        let hlc = HlcTimestamp::new(500_u64 << 32, [1u8; 16]); // ~500ms since NTP epoch

        let s1 = create_supersession(100, Some(hlc));
        let s2 = create_supersession(200, None);

        // s1's HLC millis vs s2's timestamp*1000 (200000ms)
        // This depends on the actual HLC time value
        let result = s1.temporal_cmp(&s2);
        // Just verify it produces some ordering
        assert!(
            result == Ordering::Less || result == Ordering::Greater || result == Ordering::Equal
        );
    }

    #[test]
    fn test_temporal_cmp_equal() {
        let s1 = create_supersession(100, None);
        let s2 = create_supersession(100, None);

        assert_eq!(s1.temporal_cmp(&s2), Ordering::Equal);
    }

    #[test]
    fn test_has_hlc() {
        let s_without = create_supersession(100, None);
        let s_with = create_supersession(100, Some(HlcTimestamp::new(1000, [1u8; 16])));

        assert!(!s_without.has_hlc());
        assert!(s_with.has_hlc());
    }
}
