//! Vulnerability data structures.

use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::fmt;

/// Reference to a vulnerability affecting a component
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VulnerabilityRef {
    /// Vulnerability identifier (CVE, GHSA, etc.)
    pub id: String,
    /// Source database
    pub source: VulnerabilitySource,
    /// Severity level
    pub severity: Option<Severity>,
    /// CVSS scores
    pub cvss: Vec<CvssScore>,
    /// Affected version ranges
    pub affected_versions: Vec<String>,
    /// Remediation information
    pub remediation: Option<Remediation>,
    /// Description
    pub description: Option<String>,
    /// CWE identifiers
    pub cwes: Vec<String>,
    /// Published date
    pub published: Option<DateTime<Utc>>,
    /// Last modified date
    pub modified: Option<DateTime<Utc>>,
    /// Whether this CVE is in CISA's Known Exploited Vulnerabilities catalog
    pub is_kev: bool,
    /// KEV-specific metadata if applicable
    pub kev_info: Option<KevInfo>,
    /// Per-vulnerability VEX status (from external VEX documents or embedded analysis)
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub vex_status: Option<VexStatus>,
}

/// CISA Known Exploited Vulnerabilities (KEV) catalog information
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct KevInfo {
    /// Date added to KEV catalog
    pub date_added: DateTime<Utc>,
    /// Due date for remediation (per CISA directive)
    pub due_date: DateTime<Utc>,
    /// Whether known to be used in ransomware campaigns
    pub known_ransomware_use: bool,
    /// Required action description
    pub required_action: String,
    /// Vendor/project name
    pub vendor_project: Option<String>,
    /// Product name
    pub product: Option<String>,
}

impl KevInfo {
    /// Create new KEV info
    #[must_use]
    pub const fn new(
        date_added: DateTime<Utc>,
        due_date: DateTime<Utc>,
        required_action: String,
    ) -> Self {
        Self {
            date_added,
            due_date,
            known_ransomware_use: false,
            required_action,
            vendor_project: None,
            product: None,
        }
    }

    /// Check if remediation is overdue
    #[must_use]
    pub fn is_overdue(&self) -> bool {
        Utc::now() > self.due_date
    }

    /// Days until due date (negative if overdue)
    #[must_use]
    pub fn days_until_due(&self) -> i64 {
        (self.due_date - Utc::now()).num_days()
    }
}

impl VulnerabilityRef {
    /// Create a new vulnerability reference
    #[must_use]
    pub const fn new(id: String, source: VulnerabilitySource) -> Self {
        Self {
            id,
            source,
            severity: None,
            cvss: Vec::new(),
            affected_versions: Vec::new(),
            remediation: None,
            description: None,
            cwes: Vec::new(),
            published: None,
            modified: None,
            is_kev: false,
            kev_info: None,
            vex_status: None,
        }
    }

    /// Check if this vulnerability is actively exploited (KEV)
    #[must_use]
    pub const fn is_actively_exploited(&self) -> bool {
        self.is_kev
    }

    /// Check if this is a ransomware-related KEV entry
    #[must_use]
    pub fn is_ransomware_related(&self) -> bool {
        self.kev_info
            .as_ref()
            .is_some_and(|k| k.known_ransomware_use)
    }

    /// Set per-vulnerability VEX status
    #[must_use]
    pub fn with_vex_status(mut self, vex: VexStatus) -> Self {
        self.vex_status = Some(vex);
        self
    }

    /// Get the highest CVSS score
    #[must_use]
    pub fn max_cvss_score(&self) -> Option<f32> {
        self.cvss
            .iter()
            .map(|c| c.base_score)
            .max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
    }
}

impl PartialEq for VulnerabilityRef {
    fn eq(&self, other: &Self) -> bool {
        self.id == other.id && self.source == other.source
    }
}

impl Eq for VulnerabilityRef {}

impl std::hash::Hash for VulnerabilityRef {
    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
        self.id.hash(state);
        self.source.hash(state);
    }
}

/// Vulnerability database source
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum VulnerabilitySource {
    Nvd,
    Ghsa,
    Osv,
    Snyk,
    Sonatype,
    VulnDb,
    Cve,
    Other(String),
}

impl fmt::Display for VulnerabilitySource {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Nvd => write!(f, "NVD"),
            Self::Ghsa => write!(f, "GHSA"),
            Self::Osv => write!(f, "OSV"),
            Self::Snyk => write!(f, "Snyk"),
            Self::Sonatype => write!(f, "Sonatype"),
            Self::VulnDb => write!(f, "VulnDB"),
            Self::Cve => write!(f, "CVE"),
            Self::Other(s) => write!(f, "{s}"),
        }
    }
}

/// Severity level
#[derive(Debug, Clone, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[non_exhaustive]
pub enum Severity {
    Critical,
    High,
    Medium,
    Low,
    Info,
    None,
    #[default]
    Unknown,
}

impl Severity {
    /// Create severity from CVSS score
    #[must_use]
    pub fn from_cvss(score: f32) -> Self {
        match score {
            s if s >= 9.0 => Self::Critical,
            s if s >= 7.0 => Self::High,
            s if s >= 4.0 => Self::Medium,
            s if s >= 0.1 => Self::Low,
            0.0 => Self::None,
            _ => Self::Unknown,
        }
    }

    /// Get numeric priority (lower is more severe)
    #[must_use]
    pub const fn priority(&self) -> u8 {
        match self {
            Self::Critical => 0,
            Self::High => 1,
            Self::Medium => 2,
            Self::Low => 3,
            Self::Info => 4,
            Self::None => 5,
            Self::Unknown => 6,
        }
    }
}

impl fmt::Display for Severity {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Critical => write!(f, "Critical"),
            Self::High => write!(f, "High"),
            Self::Medium => write!(f, "Medium"),
            Self::Low => write!(f, "Low"),
            Self::Info => write!(f, "Info"),
            Self::None => write!(f, "None"),
            Self::Unknown => write!(f, "Unknown"),
        }
    }
}

/// CVSS score information
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CvssScore {
    /// CVSS version
    pub version: CvssVersion,
    /// Base score (0.0 - 10.0)
    pub base_score: f32,
    /// Attack vector
    pub vector: Option<String>,
    /// Exploitability score
    pub exploitability_score: Option<f32>,
    /// Impact score
    pub impact_score: Option<f32>,
}

impl CvssScore {
    /// Create a new CVSS score
    #[must_use]
    pub const fn new(version: CvssVersion, base_score: f32) -> Self {
        Self {
            version,
            base_score,
            vector: None,
            exploitability_score: None,
            impact_score: None,
        }
    }
}

/// CVSS version
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum CvssVersion {
    V2,
    V3,
    V31,
    V4,
}

impl fmt::Display for CvssVersion {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::V2 => write!(f, "2.0"),
            Self::V3 => write!(f, "3.0"),
            Self::V31 => write!(f, "3.1"),
            Self::V4 => write!(f, "4.0"),
        }
    }
}

/// Remediation information
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Remediation {
    /// Remediation type
    pub remediation_type: RemediationType,
    /// Description
    pub description: Option<String>,
    /// Fixed version
    pub fixed_version: Option<String>,
}

/// Remediation type
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum RemediationType {
    Patch,
    Upgrade,
    Workaround,
    Mitigation,
    None,
}

impl fmt::Display for RemediationType {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Patch => write!(f, "Patch"),
            Self::Upgrade => write!(f, "Upgrade"),
            Self::Workaround => write!(f, "Workaround"),
            Self::Mitigation => write!(f, "Mitigation"),
            Self::None => write!(f, "None"),
        }
    }
}

/// VEX (Vulnerability Exploitability eXchange) status
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VexStatus {
    /// VEX state
    pub status: VexState,
    /// Justification for the status
    pub justification: Option<VexJustification>,
    /// Action statement
    pub action_statement: Option<String>,
    /// Impact statement
    pub impact_statement: Option<String>,
    /// Response actions
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub responses: Vec<VexResponse>,
    /// Details
    pub detail: Option<String>,
}

impl VexStatus {
    /// Create a new VEX status
    #[must_use]
    pub const fn new(status: VexState) -> Self {
        Self {
            status,
            justification: None,
            action_statement: None,
            impact_statement: None,
            responses: Vec::new(),
            detail: None,
        }
    }
}

/// VEX state
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum VexState {
    Affected,
    NotAffected,
    Fixed,
    UnderInvestigation,
}

impl fmt::Display for VexState {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Affected => write!(f, "Affected"),
            Self::NotAffected => write!(f, "Not Affected"),
            Self::Fixed => write!(f, "Fixed"),
            Self::UnderInvestigation => write!(f, "Under Investigation"),
        }
    }
}

/// VEX justification for `not_affected` status
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum VexJustification {
    ComponentNotPresent,
    VulnerableCodeNotPresent,
    VulnerableCodeNotInExecutePath,
    VulnerableCodeCannotBeControlledByAdversary,
    InlineMitigationsAlreadyExist,
}

impl fmt::Display for VexJustification {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::ComponentNotPresent => write!(f, "Component not present"),
            Self::VulnerableCodeNotPresent => write!(f, "Vulnerable code not present"),
            Self::VulnerableCodeNotInExecutePath => {
                write!(f, "Vulnerable code not in execute path")
            }
            Self::VulnerableCodeCannotBeControlledByAdversary => {
                write!(f, "Vulnerable code cannot be controlled by adversary")
            }
            Self::InlineMitigationsAlreadyExist => {
                write!(f, "Inline mitigations already exist")
            }
        }
    }
}

/// VEX response type
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum VexResponse {
    CanNotFix,
    WillNotFix,
    Update,
    Rollback,
    Workaround,
}

impl fmt::Display for VexResponse {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::CanNotFix => write!(f, "Can Not Fix"),
            Self::WillNotFix => write!(f, "Will Not Fix"),
            Self::Update => write!(f, "Update"),
            Self::Rollback => write!(f, "Rollback"),
            Self::Workaround => write!(f, "Workaround"),
        }
    }
}
