//! Source Registry types for human-readable source metadata.
//!
//! The Source Registry provides a mapping from content-addressed source hashes
//! to human-readable labels and metadata. This enables:
//!
//! - **UI Display**: Show "RFC 7519 - JSON Web Token (IETF)" instead of "abc123..."
//! - **Source Curation**: Curators can deprecate or quarantine unreliable sources
//! - **Tier Overrides**: Deployment-specific tier classification
//! - **Audit Trail**: Full provenance from assertion → source → registry entry
//!
//! # Example
//!
//! ```ignore
//! // Register a source
//! let record = SourceRecord::new(
//!     source_hash,
//!     "RFC 7519 - JSON Web Token".to_string(),
//!     Some("https://tools.ietf.org/html/rfc7519".to_string()),
//!     0, // Regulatory tier
//!     timestamp,
//! );
//!
//! // Later: enrich skeptic response
//! GET /v1/skeptic?subject=jwt-expiry&include_source_metadata=true
//! // Response includes source_metadata with label, tier, url
//! ```

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

/// Status of a source in the registry.
///
/// Sources can be deprecated or quarantined by curators to prevent
/// them from influencing future assertions.
#[derive(Archive, Deserialize, Serialize, Debug, Clone, Copy, PartialEq, Eq, Default)]
#[archive(check_bytes)]
pub enum SourceStatus {
    /// Source is active and can be referenced.
    #[default]
    Active,
    /// Source has been superseded or is no longer authoritative.
    /// Existing assertions remain valid but new ones should use alternatives.
    Deprecated,
    /// Source is under review for quality issues.
    /// Assertions referencing this source may be flagged.
    Quarantined,
}

impl SourceStatus {
    /// Returns the status as a human-readable string.
    pub fn as_str(&self) -> &'static str {
        match self {
            SourceStatus::Active => "active",
            SourceStatus::Deprecated => "deprecated",
            SourceStatus::Quarantined => "quarantined",
        }
    }

    /// Parse a status from a string.
    pub fn parse(s: &str) -> Option<Self> {
        match s.to_lowercase().as_str() {
            "active" => Some(SourceStatus::Active),
            "deprecated" => Some(SourceStatus::Deprecated),
            "quarantined" => Some(SourceStatus::Quarantined),
            _ => None,
        }
    }
}

/// A source registry record containing human-readable metadata.
///
/// This is stored in the Source Registry and can be looked up by source hash
/// to enrich API responses with labels and context.
#[derive(Archive, Deserialize, Serialize, Debug, Clone, PartialEq)]
#[archive(check_bytes)]
pub struct SourceRecord {
    /// Content-addressed hash of the source (BLAKE3, 32 bytes).
    /// This is the primary key and matches the `source_hash` field in assertions.
    pub hash: [u8; 32],

    /// Human-readable label for the source.
    /// Examples: "RFC 7519 - JSON Web Token", "FDA Approval Letter for Wegovy"
    pub label: String,

    /// Optional URL where the source can be accessed.
    /// Examples: "https://tools.ietf.org/html/rfc7519"
    pub url: Option<String>,

    /// Authority tier (0-5), corresponding to SourceClass.
    /// 0 = Regulatory, 1 = Clinical, 2 = Observational,
    /// 3 = Expert, 4 = Community, 5 = Anecdotal
    pub tier: u8,

    /// Current status of the source.
    pub status: SourceStatus,

    /// HLC timestamp when the record was created.
    pub created_at: u64,

    /// HLC timestamp of the last update.
    pub updated_at: u64,

    /// Optional curator notes about the source.
    /// Examples: "Deprecated in favor of RFC 9068", "Under review for accuracy"
    pub notes: Option<String>,
}

impl SourceRecord {
    /// Create a new source record with Active status.
    pub fn new(
        hash: [u8; 32],
        label: String,
        url: Option<String>,
        tier: u8,
        timestamp: u64,
    ) -> Self {
        Self {
            hash,
            label,
            url,
            tier: tier.min(5), // Clamp to valid tier range
            status: SourceStatus::Active,
            created_at: timestamp,
            updated_at: timestamp,
            notes: None,
        }
    }

    /// Create a new source record with all fields specified.
    #[allow(clippy::too_many_arguments)]
    pub fn with_notes(
        hash: [u8; 32],
        label: String,
        url: Option<String>,
        tier: u8,
        status: SourceStatus,
        created_at: u64,
        updated_at: u64,
        notes: Option<String>,
    ) -> Self {
        Self { hash, label, url, tier: tier.min(5), status, created_at, updated_at, notes }
    }

    /// Returns the tier label based on the tier number.
    pub fn tier_label(&self) -> &'static str {
        match self.tier {
            0 => "Regulatory",
            1 => "Clinical",
            2 => "Observational",
            3 => "Expert",
            4 => "Community",
            _ => "Anecdotal",
        }
    }

    /// Check if the source is active.
    pub fn is_active(&self) -> bool {
        self.status == SourceStatus::Active
    }

    /// Check if the source is quarantined.
    pub fn is_quarantined(&self) -> bool {
        self.status == SourceStatus::Quarantined
    }
}

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

    #[test]
    fn test_source_record_new() {
        let hash = [1u8; 32];
        let record = SourceRecord::new(
            hash,
            "Test Source".to_string(),
            Some("https://example.com".to_string()),
            0,
            1000,
        );

        assert_eq!(record.hash, hash);
        assert_eq!(record.label, "Test Source");
        assert_eq!(record.url, Some("https://example.com".to_string()));
        assert_eq!(record.tier, 0);
        assert_eq!(record.status, SourceStatus::Active);
        assert_eq!(record.created_at, 1000);
        assert_eq!(record.updated_at, 1000);
        assert!(record.notes.is_none());
    }

    #[test]
    fn test_tier_clamping() {
        let hash = [1u8; 32];
        let record = SourceRecord::new(hash, "Test".to_string(), None, 100, 1000);
        assert_eq!(record.tier, 5); // Clamped to max
    }

    #[test]
    fn test_tier_labels() {
        let hash = [1u8; 32];
        for tier in 0..=6 {
            let record = SourceRecord::new(hash, "Test".to_string(), None, tier, 1000);
            let label = record.tier_label();
            match tier {
                0 => assert_eq!(label, "Regulatory"),
                1 => assert_eq!(label, "Clinical"),
                2 => assert_eq!(label, "Observational"),
                3 => assert_eq!(label, "Expert"),
                4 => assert_eq!(label, "Community"),
                _ => assert_eq!(label, "Anecdotal"),
            }
        }
    }

    #[test]
    fn test_source_status_as_str() {
        assert_eq!(SourceStatus::Active.as_str(), "active");
        assert_eq!(SourceStatus::Deprecated.as_str(), "deprecated");
        assert_eq!(SourceStatus::Quarantined.as_str(), "quarantined");
    }

    #[test]
    fn test_source_status_from_str() {
        assert_eq!(SourceStatus::parse("active"), Some(SourceStatus::Active));
        assert_eq!(SourceStatus::parse("ACTIVE"), Some(SourceStatus::Active));
        assert_eq!(SourceStatus::parse("deprecated"), Some(SourceStatus::Deprecated));
        assert_eq!(SourceStatus::parse("quarantined"), Some(SourceStatus::Quarantined));
        assert_eq!(SourceStatus::parse("invalid"), None);
    }

    #[test]
    fn test_is_active_and_quarantined() {
        let hash = [1u8; 32];
        let mut record = SourceRecord::new(hash, "Test".to_string(), None, 0, 1000);

        assert!(record.is_active());
        assert!(!record.is_quarantined());

        record.status = SourceStatus::Quarantined;
        assert!(!record.is_active());
        assert!(record.is_quarantined());
    }

    #[test]
    fn test_rkyv_roundtrip() {
        let hash = [42u8; 32];
        let record = SourceRecord::with_notes(
            hash,
            "RFC 7519".to_string(),
            Some("https://tools.ietf.org/html/rfc7519".to_string()),
            0,
            SourceStatus::Active,
            1000,
            2000,
            Some("JWT specification".to_string()),
        );

        // Serialize
        let bytes = crate::serde::serialize(&record).expect("Failed to serialize SourceRecord");

        // Deserialize
        let recovered: SourceRecord =
            crate::serde::deserialize(&bytes).expect("Failed to deserialize SourceRecord");

        assert_eq!(record, recovered);
    }
}
