use serde::Serialize;

use diffsift_core::{ClassifiedDiff, Priority};

// ── SARIF 2.1.0 types ──────────────────────────────────────────────────────

#[derive(Serialize)]
pub struct SarifLog {
    #[serde(rename = "$schema")]
    pub schema: String,
    pub version: String,
    pub runs: Vec<SarifRun>,
}

#[derive(Serialize)]
pub struct SarifRun {
    pub tool: SarifTool,
    pub results: Vec<SarifResult>,
}

#[derive(Serialize)]
pub struct SarifTool {
    pub driver: SarifDriver,
}

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SarifDriver {
    pub name: String,
    pub version: String,
    pub information_uri: String,
    pub rules: Vec<SarifReportingDescriptor>,
}

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SarifReportingDescriptor {
    pub id: String,
    pub name: String,
    pub short_description: SarifMessage,
    pub default_configuration: SarifConfiguration,
    #[serde(skip_serializing_if = "Vec::is_empty")]
    pub relationships: Vec<SarifRelationship>,
}

#[derive(Serialize)]
pub struct SarifMessage {
    pub text: String,
}

#[derive(Serialize)]
pub struct SarifConfiguration {
    pub level: String,
}

#[derive(Serialize)]
pub struct SarifRelationship {
    pub target: SarifRelationshipTarget,
    pub kinds: Vec<String>,
}

#[derive(Serialize)]
pub struct SarifRelationshipTarget {
    pub id: String,
    pub tool_component: SarifToolComponent,
}

#[derive(Serialize)]
pub struct SarifToolComponent {
    pub name: String,
}

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SarifResult {
    pub rule_id: String,
    pub rule_index: usize,
    pub level: String,
    pub message: SarifMessage,
    pub locations: Vec<SarifLocation>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub fingerprints: Option<SarifFingerprints>,
}

#[derive(Serialize)]
pub struct SarifFingerprints {
    #[serde(rename = "diffsift/v1")]
    pub diffsift_v1: String,
}

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SarifLocation {
    pub physical_location: SarifPhysicalLocation,
}

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SarifPhysicalLocation {
    pub artifact_location: SarifArtifactLocation,
    pub region: SarifRegion,
}

#[derive(Serialize)]
pub struct SarifArtifactLocation {
    pub uri: String,
}

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SarifRegion {
    pub start_line: u32,
    pub end_line: u32,
}

// ── Builder ─────────────────────────────────────────────────────────────────

fn priority_to_level(priority: Priority) -> &'static str {
    match priority {
        Priority::High => "error",
        Priority::Moderate => "warning",
        Priority::Low => "note",
        Priority::Filtered => "none",
    }
}

struct RuleEntry {
    id: String,
    name: String,
    level: String,
    cwe: Option<String>,
}

pub fn build_sarif_output(result: &ClassifiedDiff) -> SarifLog {
    // Collect unique rules across all hunks
    let mut rule_entries: Vec<RuleEntry> = Vec::new();
    let mut rule_index_map: std::collections::HashMap<String, usize> =
        std::collections::HashMap::new();

    let mut sarif_results: Vec<SarifResult> = Vec::new();

    for file in &result.files {
        for hunk in &file.hunks {
            if hunk.patterns.is_empty() {
                continue;
            }

            let pattern = &hunk.patterns[0];
            let rule_idx = if let Some(&idx) = rule_index_map.get(&pattern.pattern_id) {
                idx
            } else {
                let idx = rule_entries.len();
                rule_index_map.insert(pattern.pattern_id.clone(), idx);
                rule_entries.push(RuleEntry {
                    id: pattern.pattern_id.clone(),
                    name: pattern.pattern_name.clone(),
                    level: priority_to_level(hunk.priority).to_string(),
                    cwe: pattern.cwe.clone(),
                });
                idx
            };

            let message = format!(
                "{}: {} ({:?}, confidence {:.0}%)",
                pattern.pattern_id,
                pattern.pattern_name,
                hunk.category,
                pattern.confidence * 100.0
            );

            let fingerprint = hunk.fingerprint.clone();

            sarif_results.push(SarifResult {
                rule_id: pattern.pattern_id.clone(),
                rule_index: rule_idx,
                level: priority_to_level(hunk.priority).to_string(),
                message: SarifMessage { text: message },
                locations: vec![SarifLocation {
                    physical_location: SarifPhysicalLocation {
                        artifact_location: SarifArtifactLocation {
                            uri: file.path.clone(),
                        },
                        region: SarifRegion {
                            start_line: hunk.start_line + 1, // SARIF uses 1-based lines
                            end_line: hunk.end_line + 1,
                        },
                    },
                }],
                fingerprints: fingerprint.map(|fp| SarifFingerprints { diffsift_v1: fp }),
            });
        }
    }

    // Build rule descriptors
    let rules: Vec<SarifReportingDescriptor> = rule_entries
        .into_iter()
        .map(|entry| {
            let relationships = if let Some(cwe) = &entry.cwe {
                vec![SarifRelationship {
                    target: SarifRelationshipTarget {
                        id: cwe.clone(),
                        tool_component: SarifToolComponent {
                            name: "cwe".to_string(),
                        },
                    },
                    kinds: vec!["superset".to_string()],
                }]
            } else {
                Vec::new()
            };

            SarifReportingDescriptor {
                id: entry.id,
                name: entry.name.clone(),
                short_description: SarifMessage { text: entry.name },
                default_configuration: SarifConfiguration { level: entry.level },
                relationships,
            }
        })
        .collect();

    SarifLog {
        schema: "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/main/sarif-2.1/schema/sarif-schema-2.1.0.json".to_string(),
        version: "2.1.0".to_string(),
        runs: vec![SarifRun {
            tool: SarifTool {
                driver: SarifDriver {
                    name: "DiffSift".to_string(),
                    version: env!("CARGO_PKG_VERSION").to_string(),
                    information_uri: "https://gitlab.com/neilfitzgerald1972/diffsift".to_string(),
                    rules,
                },
            },
            results: sarif_results,
        }],
    }
}
