use super::outcome::{FixApplied, FixResultsSummary, PrimitiveFixCount, RuleFixCount};
use crate::core::refactor::FixResult;

pub fn summarize_fix_results(fixes: &[FixApplied]) -> FixResultsSummary {
    use std::collections::{BTreeMap, HashSet};

    let mut files = HashSet::new();
    let mut rule_counts: BTreeMap<String, usize> = BTreeMap::new();
    let mut primitive_counts: BTreeMap<String, usize> = BTreeMap::new();

    for fix in fixes {
        files.insert(fix.file.clone());
        *rule_counts.entry(fix.rule.clone()).or_insert(0) += 1;
        if let Some(primitive) = &fix.primitive {
            *primitive_counts.entry(primitive.clone()).or_insert(0) += 1;
        }
    }

    let rules = rule_counts
        .into_iter()
        .map(|(rule, count)| RuleFixCount { rule, count })
        .collect();

    let primitives = primitive_counts
        .into_iter()
        .map(|(primitive, count)| PrimitiveFixCount { primitive, count })
        .collect();

    FixResultsSummary {
        fixes_applied: fixes.len(),
        files_modified: files.len(),
        rules,
        primitives,
    }
}

pub fn summarize_optional_fix_results(fixes: &[FixApplied]) -> Option<FixResultsSummary> {
    if fixes.is_empty() {
        None
    } else {
        Some(summarize_fix_results(fixes))
    }
}

pub fn summarize_audit_fix_result(fix_result: &FixResult) -> FixResultsSummary {
    use std::collections::{BTreeMap, HashSet};

    let mut files = HashSet::new();
    let mut rule_counts: BTreeMap<String, usize> = BTreeMap::new();
    let mut primitive_counts: BTreeMap<String, usize> = BTreeMap::new();
    let mut total_fixes = 0usize;

    for fix in &fix_result.fixes {
        if !fix.applied {
            continue;
        }
        files.insert(fix.file.clone());
        for insertion in &fix.insertions {
            if insertion.auto_apply {
                let rule = format!("{:?}", insertion.finding).to_lowercase();
                *rule_counts.entry(rule).or_insert(0) += 1;
                if let Some(primitive) = insertion.primitive.as_ref().map(primitive_name) {
                    *primitive_counts.entry(primitive).or_insert(0) += 1;
                }
                total_fixes += 1;
            }
        }
    }

    for new_file in &fix_result.new_files {
        if new_file.written {
            files.insert(new_file.file.clone());
            let rule = format!("{:?}", new_file.finding).to_lowercase();
            *rule_counts.entry(rule).or_insert(0) += 1;
            if let Some(primitive) = new_file.primitive.as_ref().map(primitive_name) {
                *primitive_counts.entry(primitive).or_insert(0) += 1;
            }
            total_fixes += 1;
        }
    }

    for plan in &fix_result.decompose_plans {
        if plan.applied {
            files.insert(plan.file.clone());
            let rule = format!("{:?}", plan.source_finding).to_lowercase();
            *rule_counts.entry(rule).or_insert(0) += 1;
            total_fixes += 1;
        }
    }

    let rules = rule_counts
        .into_iter()
        .map(|(rule, count)| RuleFixCount { rule, count })
        .collect();

    let primitives = primitive_counts
        .into_iter()
        .map(|(primitive, count)| PrimitiveFixCount { primitive, count })
        .collect();

    FixResultsSummary {
        fixes_applied: total_fixes,
        files_modified: files.len(),
        rules,
        primitives,
    }
}

/// Derive the primitive name from serde's `#[serde(tag = "type", rename_all = "snake_case")]`
/// attribute on `RefactorPrimitive`. This eliminates manual match arms — adding a new
/// variant to the enum automatically works for reporting.
pub(crate) fn primitive_name(primitive: &crate::core::refactor::RefactorPrimitive) -> String {
    // RefactorPrimitive uses `#[serde(tag = "type", rename_all = "snake_case")]`.
    // Serializing to JSON gives `{"type": "snake_case_name", ...}`.
    // Extract the "type" field value.
    serde_json::to_value(primitive)
        .ok()
        .and_then(|v| v.get("type").and_then(|t| t.as_str()).map(String::from))
        .unwrap_or_else(|| format!("{:?}", primitive).to_lowercase())
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::core::refactor::RefactorPrimitive;

    #[test]
    fn primitive_name_derives_from_serde() {
        // Verify serde-derived names match the expected snake_case format.
        // Adding a new variant to RefactorPrimitive automatically works —
        // no manual match arm needed.
        assert_eq!(
            primitive_name(&RefactorPrimitive::MoveTestFile),
            "move_test_file"
        );
        assert_eq!(
            primitive_name(&RefactorPrimitive::RemoveOrphanedTest),
            "remove_orphaned_test"
        );
        assert_eq!(
            primitive_name(&RefactorPrimitive::RemoveNearDuplicateImplementation),
            "remove_near_duplicate_implementation"
        );
        assert_eq!(
            primitive_name(&RefactorPrimitive::UpdateStaleDocReference),
            "update_stale_doc_reference"
        );
        assert_eq!(
            primitive_name(&RefactorPrimitive::RemoveBrokenDocReferenceLine),
            "remove_broken_doc_reference_line"
        );
    }
}
