//! Core types for evidence-based authority.
//!
//! These types represent the sources of evidence that back patterns
//! and determine their authority weight for graduation decisions.

use serde::{Deserialize, Serialize};

/// Evidence level determines authority weight and graduation threshold.
///
/// Higher evidence levels have more authority and graduate faster.
/// The level is determined by the highest-quality evidence source attached
/// to a pattern.
#[derive(
    Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize,
)]
#[serde(rename_all = "snake_case")]
pub enum EvidenceLevel {
    /// Just code commits, no external context.
    /// Weight: 0.40, Graduation threshold: 10 usages
    #[default]
    Commit = 0,

    /// Research documentation (ADRs, decision logs).
    /// Weight: 0.70, Graduation threshold: 5 usages
    Research = 1,

    /// External standards (RFCs, OWASP).
    /// Weight: 0.85, Graduation threshold: 3 usages
    Standard = 2,

    /// Product specifications with requirement IDs.
    /// Weight: 0.95, Graduation threshold: 1 usage
    ProductSpec = 3,
}

impl EvidenceLevel {
    /// Authority weight for this evidence level.
    ///
    /// Higher weights indicate more authoritative evidence.
    #[must_use]
    pub const fn authority_weight(&self) -> f32 {
        match self {
            EvidenceLevel::Commit => 0.40,
            EvidenceLevel::Research => 0.70,
            EvidenceLevel::Standard => 0.85,
            EvidenceLevel::ProductSpec => 0.95,
        }
    }

    /// Minimum usages required for graduation at this evidence level.
    ///
    /// Higher evidence levels require fewer usages to graduate.
    #[must_use]
    pub const fn graduation_threshold(&self) -> usize {
        match self {
            EvidenceLevel::Commit => 10,
            EvidenceLevel::Research => 5,
            EvidenceLevel::Standard => 3,
            EvidenceLevel::ProductSpec => 1,
        }
    }

    /// Display badge for this evidence level.
    ///
    /// Used in CLI output to show the evidence level.
    #[must_use]
    pub const fn badge(&self) -> &'static str {
        match self {
            EvidenceLevel::Commit => "[COMMIT]",
            EvidenceLevel::Research => "[RESEARCH]",
            EvidenceLevel::Standard => "[STANDARD]",
            EvidenceLevel::ProductSpec => "[SPEC]",
        }
    }

    /// Short name for display.
    #[must_use]
    pub const fn name(&self) -> &'static str {
        match self {
            EvidenceLevel::Commit => "commit",
            EvidenceLevel::Research => "research",
            EvidenceLevel::Standard => "standard",
            EvidenceLevel::ProductSpec => "product_spec",
        }
    }

    /// Threshold multiplier for graduation calculations.
    ///
    /// This is the fraction of the base threshold required at this evidence level.
    #[must_use]
    pub const fn threshold_multiplier(&self) -> f32 {
        match self {
            EvidenceLevel::ProductSpec => 0.1, // 10% of base
            EvidenceLevel::Standard => 0.3,    // 30% of base
            EvidenceLevel::Research => 0.5,    // 50% of base
            EvidenceLevel::Commit => 1.0,      // Full base
        }
    }
}

impl std::fmt::Display for EvidenceLevel {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.name())
    }
}

impl std::str::FromStr for EvidenceLevel {
    type Err = EvidenceLevelParseError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s.to_lowercase().as_str() {
            "commit" => Ok(EvidenceLevel::Commit),
            "research" => Ok(EvidenceLevel::Research),
            "standard" => Ok(EvidenceLevel::Standard),
            "product_spec" | "productspec" | "spec" => Ok(EvidenceLevel::ProductSpec),
            _ => Err(EvidenceLevelParseError(s.to_string())),
        }
    }
}

/// Error returned when parsing an invalid evidence level string.
#[derive(Debug, Clone)]
pub struct EvidenceLevelParseError(pub String);

impl std::fmt::Display for EvidenceLevelParseError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "invalid evidence level '{}': expected commit, research, standard, or product_spec",
            self.0
        )
    }
}

impl std::error::Error for EvidenceLevelParseError {}

/// Source of evidence for a pattern.
///
/// Tagged enum for different evidence source types, enabling
/// source-specific formatting and future linking.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum EvidenceSource {
    /// Reference to an IETF RFC.
    Rfc {
        /// RFC number (e.g., 7519 for JWT).
        number: u32,
        /// Optional section reference (e.g., "4.1").
        section: Option<String>,
    },

    /// Reference to an OWASP category.
    Owasp {
        /// OWASP ID (e.g., "A03" for Injection).
        id: String,
        /// Optional year (e.g., 2021).
        year: Option<u16>,
    },

    /// Reference to an Architecture Decision Record.
    Adr {
        /// ADR identifier (e.g., "042").
        id: String,
        /// Optional file path.
        path: Option<String>,
    },

    /// Reference to a product specification.
    Spec {
        /// Path to the spec file.
        path: String,
        /// Optional requirement ID (e.g., "REQ-API-001").
        requirement_id: Option<String>,
    },

    /// Reference to a decision log entry.
    DecisionLog {
        /// Path to the decision log file.
        path: String,
        /// Optional entry ID within the log.
        entry_id: Option<String>,
    },

    /// Reference to a git commit.
    Commit {
        /// Commit hash (full or short).
        hash: String,
        /// Optional excerpt from commit message.
        message_excerpt: Option<String>,
    },
}

impl EvidenceSource {
    /// Get the evidence level for this source type.
    #[must_use]
    pub const fn level(&self) -> EvidenceLevel {
        match self {
            EvidenceSource::Rfc { .. } | EvidenceSource::Owasp { .. } => EvidenceLevel::Standard,
            EvidenceSource::Adr { .. } | EvidenceSource::DecisionLog { .. } => {
                EvidenceLevel::Research
            }
            EvidenceSource::Spec { .. } => EvidenceLevel::ProductSpec,
            EvidenceSource::Commit { .. } => EvidenceLevel::Commit,
        }
    }

    /// Format for display.
    #[must_use]
    pub fn display(&self) -> String {
        match self {
            EvidenceSource::Rfc { number, section } => {
                if let Some(sec) = section {
                    format!("RFC {number} Section {sec}")
                } else {
                    format!("RFC {number}")
                }
            }
            EvidenceSource::Owasp { id, year } => {
                if let Some(y) = year {
                    format!("OWASP {id}:{y}")
                } else {
                    format!("OWASP {id}")
                }
            }
            EvidenceSource::Adr { id, path } => {
                if let Some(p) = path {
                    format!("ADR-{id} ({p})")
                } else {
                    format!("ADR-{id}")
                }
            }
            EvidenceSource::Spec { path, requirement_id } => {
                if let Some(req) = requirement_id {
                    format!("{path}#{req}")
                } else {
                    path.clone()
                }
            }
            EvidenceSource::DecisionLog { path, entry_id } => {
                if let Some(entry) = entry_id {
                    format!("{path}#{entry}")
                } else {
                    path.clone()
                }
            }
            EvidenceSource::Commit { hash, message_excerpt } => {
                let short_hash = if hash.len() > 7 { &hash[..7] } else { hash };
                if let Some(msg) = message_excerpt {
                    let short_msg =
                        if msg.len() > 30 { format!("{}...", &msg[..30]) } else { msg.clone() };
                    format!("{short_hash}: {short_msg}")
                } else {
                    short_hash.to_string()
                }
            }
        }
    }
}

impl std::fmt::Display for EvidenceSource {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.display())
    }
}

/// Evidence attached to a pattern.
///
/// Aggregates multiple evidence sources and caches the highest level.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct PatternEvidence {
    /// All evidence sources found for this pattern.
    pub sources: Vec<EvidenceSource>,

    /// Cached highest evidence level (computed from sources).
    #[serde(skip_serializing_if = "Option::is_none")]
    pub level: Option<EvidenceLevel>,
}

impl PatternEvidence {
    /// Create new pattern evidence with no sources.
    #[must_use]
    pub fn new() -> Self {
        Self { sources: Vec::new(), level: None }
    }

    /// Create pattern evidence from a list of sources.
    #[must_use]
    pub fn from_sources(sources: Vec<EvidenceSource>) -> Self {
        let level = Self::compute_level(&sources);
        Self { sources, level }
    }

    /// Add an evidence source.
    pub fn add_source(&mut self, source: EvidenceSource) {
        self.sources.push(source);
        self.level = Self::compute_level(&self.sources);
    }

    /// Get the effective evidence level.
    ///
    /// Returns the highest level from all sources, or `Commit` if no sources.
    #[must_use]
    pub fn effective_level(&self) -> EvidenceLevel {
        self.level.unwrap_or(EvidenceLevel::Commit)
    }

    /// Check if this evidence has any sources.
    #[must_use]
    pub fn is_empty(&self) -> bool {
        self.sources.is_empty()
    }

    /// Number of evidence sources.
    #[must_use]
    pub fn source_count(&self) -> usize {
        self.sources.len()
    }

    /// Merge another evidence collection into this one.
    pub fn merge(&mut self, other: PatternEvidence) {
        for source in other.sources {
            if !self.sources.contains(&source) {
                self.sources.push(source);
            }
        }
        self.level = Self::compute_level(&self.sources);
    }

    /// Compute the highest evidence level from sources.
    fn compute_level(sources: &[EvidenceSource]) -> Option<EvidenceLevel> {
        sources.iter().map(|s| s.level()).max()
    }

    /// Get authority weight based on evidence level.
    #[must_use]
    pub fn authority_weight(&self) -> f32 {
        self.effective_level().authority_weight()
    }

    /// Get graduation threshold based on evidence level.
    #[must_use]
    pub fn graduation_threshold(&self) -> usize {
        self.effective_level().graduation_threshold()
    }
}

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

    #[test]
    fn test_evidence_level_ordering() {
        assert!(EvidenceLevel::ProductSpec > EvidenceLevel::Standard);
        assert!(EvidenceLevel::Standard > EvidenceLevel::Research);
        assert!(EvidenceLevel::Research > EvidenceLevel::Commit);
    }

    #[test]
    fn test_authority_weights() {
        assert!((EvidenceLevel::ProductSpec.authority_weight() - 0.95).abs() < 0.001);
        assert!((EvidenceLevel::Standard.authority_weight() - 0.85).abs() < 0.001);
        assert!((EvidenceLevel::Research.authority_weight() - 0.70).abs() < 0.001);
        assert!((EvidenceLevel::Commit.authority_weight() - 0.40).abs() < 0.001);
    }

    #[test]
    fn test_graduation_thresholds() {
        assert_eq!(EvidenceLevel::ProductSpec.graduation_threshold(), 1);
        assert_eq!(EvidenceLevel::Standard.graduation_threshold(), 3);
        assert_eq!(EvidenceLevel::Research.graduation_threshold(), 5);
        assert_eq!(EvidenceLevel::Commit.graduation_threshold(), 10);
    }

    #[test]
    fn test_badges() {
        assert_eq!(EvidenceLevel::ProductSpec.badge(), "[SPEC]");
        assert_eq!(EvidenceLevel::Standard.badge(), "[STANDARD]");
        assert_eq!(EvidenceLevel::Research.badge(), "[RESEARCH]");
        assert_eq!(EvidenceLevel::Commit.badge(), "[COMMIT]");
    }

    #[test]
    fn test_evidence_source_levels() {
        assert_eq!(
            EvidenceSource::Rfc { number: 7519, section: None }.level(),
            EvidenceLevel::Standard
        );
        assert_eq!(
            EvidenceSource::Owasp { id: "A03".into(), year: Some(2021) }.level(),
            EvidenceLevel::Standard
        );
        assert_eq!(
            EvidenceSource::Adr { id: "042".into(), path: None }.level(),
            EvidenceLevel::Research
        );
        assert_eq!(
            EvidenceSource::Spec {
                path: "specs/api.md".into(),
                requirement_id: Some("REQ-001".into())
            }
            .level(),
            EvidenceLevel::ProductSpec
        );
        assert_eq!(
            EvidenceSource::Commit { hash: "abc123".into(), message_excerpt: None }.level(),
            EvidenceLevel::Commit
        );
    }

    #[test]
    fn test_evidence_source_display() {
        assert_eq!(
            EvidenceSource::Rfc { number: 7519, section: Some("4.1".into()) }.display(),
            "RFC 7519 Section 4.1"
        );
        assert_eq!(
            EvidenceSource::Owasp { id: "A03".into(), year: Some(2021) }.display(),
            "OWASP A03:2021"
        );
        assert_eq!(EvidenceSource::Adr { id: "042".into(), path: None }.display(), "ADR-042");
        assert_eq!(
            EvidenceSource::Spec {
                path: "specs/api.md".into(),
                requirement_id: Some("REQ-001".into())
            }
            .display(),
            "specs/api.md#REQ-001"
        );
    }

    #[test]
    fn test_pattern_evidence_aggregation() {
        let mut evidence = PatternEvidence::new();
        assert_eq!(evidence.effective_level(), EvidenceLevel::Commit);

        evidence.add_source(EvidenceSource::Commit { hash: "abc".into(), message_excerpt: None });
        assert_eq!(evidence.effective_level(), EvidenceLevel::Commit);

        evidence.add_source(EvidenceSource::Adr { id: "042".into(), path: None });
        assert_eq!(evidence.effective_level(), EvidenceLevel::Research);

        evidence.add_source(EvidenceSource::Rfc { number: 7519, section: None });
        assert_eq!(evidence.effective_level(), EvidenceLevel::Standard);

        evidence.add_source(EvidenceSource::Spec { path: "spec.md".into(), requirement_id: None });
        assert_eq!(evidence.effective_level(), EvidenceLevel::ProductSpec);
    }

    #[test]
    fn test_pattern_evidence_from_sources() {
        let sources = vec![
            EvidenceSource::Commit { hash: "abc".into(), message_excerpt: None },
            EvidenceSource::Rfc { number: 7519, section: None },
        ];

        let evidence = PatternEvidence::from_sources(sources);
        assert_eq!(evidence.effective_level(), EvidenceLevel::Standard);
        assert_eq!(evidence.source_count(), 2);
    }

    #[test]
    fn test_pattern_evidence_merge() {
        let mut e1 = PatternEvidence::from_sources(vec![EvidenceSource::Commit {
            hash: "abc".into(),
            message_excerpt: None,
        }]);

        let e2 = PatternEvidence::from_sources(vec![EvidenceSource::Rfc {
            number: 7519,
            section: None,
        }]);

        e1.merge(e2);
        assert_eq!(e1.effective_level(), EvidenceLevel::Standard);
        assert_eq!(e1.source_count(), 2);
    }

    #[test]
    fn test_evidence_level_from_str() {
        assert_eq!("commit".parse::<EvidenceLevel>().ok(), Some(EvidenceLevel::Commit));
        assert_eq!("STANDARD".parse::<EvidenceLevel>().ok(), Some(EvidenceLevel::Standard));
        assert_eq!("product_spec".parse::<EvidenceLevel>().ok(), Some(EvidenceLevel::ProductSpec));
        assert_eq!("spec".parse::<EvidenceLevel>().ok(), Some(EvidenceLevel::ProductSpec));
        assert!("unknown".parse::<EvidenceLevel>().is_err());
    }

    #[test]
    fn test_threshold_multipliers() {
        assert!((EvidenceLevel::ProductSpec.threshold_multiplier() - 0.1).abs() < 0.001);
        assert!((EvidenceLevel::Standard.threshold_multiplier() - 0.3).abs() < 0.001);
        assert!((EvidenceLevel::Research.threshold_multiplier() - 0.5).abs() < 0.001);
        assert!((EvidenceLevel::Commit.threshold_multiplier() - 1.0).abs() < 0.001);
    }

    #[test]
    fn test_serialization_roundtrip() {
        let evidence = PatternEvidence::from_sources(vec![
            EvidenceSource::Rfc { number: 7519, section: Some("4.1".into()) },
            EvidenceSource::Owasp { id: "A03".into(), year: Some(2021) },
        ]);

        let json = serde_json::to_string(&evidence).expect("serialize");
        let parsed: PatternEvidence = serde_json::from_str(&json).expect("deserialize");

        assert_eq!(parsed.source_count(), 2);
        assert_eq!(parsed.effective_level(), EvidenceLevel::Standard);
    }
}
