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

use super::scan::ScanType;

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
#[serde(rename_all = "lowercase")]
pub enum Severity {
    Info,
    Low,
    Medium,
    High,
    Critical,
}

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

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum FindingStatus {
    Open,
    Triaged,
    FalsePositive,
    Resolved,
    Ignored,
}

impl std::fmt::Display for FindingStatus {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Open => write!(f, "open"),
            Self::Triaged => write!(f, "triaged"),
            Self::FalsePositive => write!(f, "false_positive"),
            Self::Resolved => write!(f, "resolved"),
            Self::Ignored => write!(f, "ignored"),
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Finding {
    #[serde(rename = "_id", skip_serializing_if = "Option::is_none")]
    pub id: Option<bson::oid::ObjectId>,
    pub repo_id: String,
    pub fingerprint: String,
    pub scanner: String,
    pub scan_type: ScanType,
    pub rule_id: Option<String>,
    pub title: String,
    pub description: String,
    pub severity: Severity,
    pub confidence: Option<f64>,
    pub cwe: Option<String>,
    pub cve: Option<String>,
    pub cvss_score: Option<f64>,
    pub file_path: Option<String>,
    pub line_number: Option<u32>,
    pub code_snippet: Option<String>,
    pub remediation: Option<String>,
    pub suggested_fix: Option<String>,
    pub status: FindingStatus,
    pub tracker_issue_url: Option<String>,
    pub scan_run_id: Option<String>,
    /// LLM triage action and reasoning
    pub triage_action: Option<String>,
    pub triage_rationale: Option<String>,
    /// Developer feedback on finding quality
    pub developer_feedback: Option<String>,
    /// Compliance control ids this finding is evidence for (stamped by control
    /// triage against the `control-map` LUT). Empty when unmapped.
    #[serde(default)]
    pub control_refs: Vec<String>,
    #[serde(with = "super::serde_helpers::bson_datetime")]
    pub created_at: DateTime<Utc>,
    #[serde(with = "super::serde_helpers::bson_datetime")]
    pub updated_at: DateTime<Utc>,
}

impl Finding {
    pub fn new(
        repo_id: String,
        fingerprint: String,
        scanner: String,
        scan_type: ScanType,
        title: String,
        description: String,
        severity: Severity,
    ) -> Self {
        let now = Utc::now();
        Self {
            id: None,
            repo_id,
            fingerprint,
            scanner,
            scan_type,
            rule_id: None,
            title,
            description,
            severity,
            confidence: None,
            cwe: None,
            cve: None,
            cvss_score: None,
            file_path: None,
            line_number: None,
            code_snippet: None,
            remediation: None,
            suggested_fix: None,
            status: FindingStatus::Open,
            tracker_issue_url: None,
            scan_run_id: None,
            triage_action: None,
            triage_rationale: None,
            developer_feedback: None,
            control_refs: Vec::new(),
            created_at: now,
            updated_at: now,
        }
    }
}
