#![allow(warnings, clippy::all, unexpected_cfgs)]
// Vendored from ast-grep 0.41.0 (MIT)
// Modified: SgLang → Lang, ast_grep_language::LanguageExt → ast_grep_core::tree_sitter::LanguageExt

use super::{Lang, TestSnapshots, snapshot::TestSnapshot};
use ast_grep_config::RuleConfig;
use ast_grep_core::tree_sitter::LanguageExt;

/// [CaseStatus] categorize whether and how ast-grep
/// reports error for either valid or invalid code.
#[derive(PartialEq, Eq, Debug)]
pub enum CaseStatus<'a> {
    /// Reported no issue for valid code
    Validated,
    /// Reported correct issue for invalid code
    Reported,
    /// User accepted new snapshot updates
    Updated {
        source: &'a str,
        updated: TestSnapshot,
    },
    /// Reported issues for invalid code but it is wrong
    Wrong {
        source: &'a str,
        actual: TestSnapshot,
        expected: Option<TestSnapshot>,
    },
    /// Reported no issue for invalid code
    Missing(&'a str),
    /// Reported some issue for valid code
    Noisy(&'a str),
    /// Error occurred when applying fix
    Error,
}

impl<'a> CaseStatus<'a> {
    pub fn verify_valid(rule_config: &RuleConfig<Lang>, case: &'a str) -> Self {
        let rule = &rule_config.matcher;
        let sg = rule_config.language.ast_grep(case);
        if sg.root().find(rule).is_some() {
            CaseStatus::Noisy(case)
        } else {
            CaseStatus::Validated
        }
    }

    pub fn verify_invalid(rule_config: &RuleConfig<Lang>, case: &'a str) -> Self {
        let sg = rule_config.language.ast_grep(case);
        let rule = &rule_config.matcher;
        if sg.root().find(rule).is_some() {
            CaseStatus::Reported
        } else {
            CaseStatus::Missing(case)
        }
    }

    pub fn verify_snapshot(
        rule_config: &RuleConfig<Lang>,
        case: &'a str,
        snapshot: Option<&TestSnapshot>,
    ) -> Self {
        let actual = match TestSnapshot::generate(rule_config, case) {
            Ok(Some(snap)) => snap,
            Ok(None) => return CaseStatus::Missing(case),
            Err(_) => return CaseStatus::Error,
        };
        match snapshot {
            Some(e) if e == &actual => CaseStatus::Reported,
            nullable => CaseStatus::Wrong {
                source: case,
                actual,
                expected: nullable.cloned(),
            },
        }
    }

    pub fn accept(&mut self) -> bool {
        let CaseStatus::Wrong { source, actual, .. } = self else {
            return false;
        };
        let updated = std::mem::replace(
            actual,
            TestSnapshot {
                fixed: None,
                labels: vec![],
            },
        );
        *self = CaseStatus::Updated { source, updated };
        true
    }

    pub fn is_pass(&self) -> bool {
        matches!(
            self,
            CaseStatus::Validated | CaseStatus::Reported | CaseStatus::Updated { .. }
        )
    }
}

/// The result for one rule-test.yml
#[derive(PartialEq, Eq, Default, Debug)]
pub struct CaseResult<'a> {
    pub id: &'a str,
    pub cases: Vec<CaseStatus<'a>>,
}

impl CaseResult<'_> {
    pub fn passed(&self) -> bool {
        self.cases.iter().all(CaseStatus::is_pass)
    }

    pub fn is_snapshot_mismatch_only_failure(&self) -> bool {
        let mut has_failures = false;
        for case in &self.cases {
            if !case.is_pass() {
                has_failures = true;
                if !matches!(case, CaseStatus::Wrong { .. }) {
                    return false;
                }
            }
        }
        has_failures
    }

    pub fn changed_snapshots(&self) -> TestSnapshots {
        let snapshots = self
            .cases
            .iter()
            .filter_map(|c| match c {
                CaseStatus::Updated { source, updated } => {
                    Some((source.to_string(), updated.clone()))
                }
                _ => None,
            })
            .collect();
        TestSnapshots {
            id: self.id.to_string(),
            snapshots,
        }
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use crate::ast_grep::verify::test::get_rule_config;

    #[test]
    fn test_snapshot() {
        let rule = get_rule_config("pattern: let a = 1");
        let ret = CaseStatus::verify_snapshot(&rule, "function () { let a = 1 }", None);
        assert!(matches!(&ret, CaseStatus::Wrong { expected: None, .. }));
        let CaseStatus::Wrong { actual, source, .. } = ret else {
            panic!("wrong");
        };
        assert_eq!(source, "function () { let a = 1 }");
        let primary = &actual.labels[0];
        assert_eq!(primary.source, "let a = 1");
        let ret = CaseStatus::verify_snapshot(&rule, "function () { let a = 1 }", Some(&actual));
        assert!(matches!(ret, CaseStatus::Reported));
    }

    #[test]
    fn test_is_snapshot_mismatch_only_failure() {
        let snapshot_only_result = CaseResult {
            id: "test",
            cases: vec![
                CaseStatus::Wrong {
                    source: "test",
                    actual: TestSnapshot {
                        fixed: None,
                        labels: vec![],
                    },
                    expected: None,
                },
                CaseStatus::Wrong {
                    source: "test2",
                    actual: TestSnapshot {
                        fixed: None,
                        labels: vec![],
                    },
                    expected: None,
                },
            ],
        };
        assert!(snapshot_only_result.is_snapshot_mismatch_only_failure());

        let mixed_result = CaseResult {
            id: "test",
            cases: vec![
                CaseStatus::Wrong {
                    source: "test",
                    actual: TestSnapshot {
                        fixed: None,
                        labels: vec![],
                    },
                    expected: None,
                },
                CaseStatus::Missing("test2"),
            ],
        };
        assert!(!mixed_result.is_snapshot_mismatch_only_failure());

        let passing_result = CaseResult {
            id: "test",
            cases: vec![CaseStatus::Validated, CaseStatus::Reported],
        };
        assert!(!passing_result.is_snapshot_mismatch_only_failure());

        let non_snapshot_result = CaseResult {
            id: "test",
            cases: vec![CaseStatus::Missing("test"), CaseStatus::Noisy("test2")],
        };
        assert!(!non_snapshot_result.is_snapshot_mismatch_only_failure());
    }
}
