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

use super::case_result::{CaseResult, CaseStatus};
use super::snapshot::TestSnapshots;
use crate::ast_grep::lang::Lang;

use ast_grep_config::RuleConfig;
use serde::{Deserialize, Serialize};

/// Corresponds to one rule-test.yml for testing.
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TestCase {
    pub id: String,
    #[serde(default)]
    pub valid: Vec<String>,
    #[serde(default)]
    pub invalid: Vec<String>,
}

impl TestCase {
    pub fn verify_rule(&self, rule_config: &RuleConfig<Lang>) -> CaseResult<'_> {
        debug_assert_eq!(self.id, rule_config.id);
        verify_test_case(self, rule_config)
    }

    pub fn verify_with_snapshot(
        &'_ self,
        rule_config: &RuleConfig<Lang>,
        snapshots: Option<&TestSnapshots>,
    ) -> CaseResult<'_> {
        debug_assert_eq!(self.id, rule_config.id);
        verify_test_case_with_snapshots(self, rule_config, snapshots)
    }
}

fn verify_test_case<'a>(test_case: &'a TestCase, rule_config: &RuleConfig<Lang>) -> CaseResult<'a> {
    let valid_cases = test_case
        .valid
        .iter()
        .map(|valid| CaseStatus::verify_valid(rule_config, valid));
    let invalid_cases = test_case
        .invalid
        .iter()
        .map(|invalid| CaseStatus::verify_invalid(rule_config, invalid));
    CaseResult {
        id: &test_case.id,
        cases: valid_cases.chain(invalid_cases).collect(),
    }
}

fn verify_test_case_with_snapshots<'a>(
    test_case: &'a TestCase,
    rule_config: &RuleConfig<Lang>,
    snapshots: Option<&TestSnapshots>,
) -> CaseResult<'a> {
    let valid_cases = test_case
        .valid
        .iter()
        .map(|valid| CaseStatus::verify_valid(rule_config, valid));
    let invalid_cases = test_case.invalid.iter().map(|invalid| {
        let snap = snapshots.and_then(|s| s.snapshots.get(invalid));
        CaseStatus::verify_snapshot(rule_config, invalid, snap)
    });
    CaseResult {
        id: &test_case.id,
        cases: valid_cases.chain(invalid_cases).collect(),
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::ast_grep::verify::snapshot::TestSnapshot;
    use crate::ast_grep::verify::test::{TEST_RULE, get_rule_config};

    fn mock_test_case(valid: &[&str], invalid: &[&str]) -> TestCase {
        TestCase {
            id: TEST_RULE.to_string(),
            valid: valid.iter().map(|s| s.to_string()).collect(),
            invalid: invalid.iter().map(|s| s.to_string()).collect(),
        }
    }

    fn mock_snapshots(case: &str, snap: TestSnapshot) -> TestSnapshots {
        TestSnapshots {
            id: TEST_RULE.to_string(),
            snapshots: vec![(case.to_string(), snap)].into_iter().collect(),
        }
    }

    fn mock_snapshot(rule_config: &RuleConfig<Lang>, case: &str) -> TestSnapshot {
        TestSnapshot::generate(rule_config, case)
            .expect("should ok")
            .expect("should generate")
    }

    #[test]
    fn test_verify_rule() {
        let rule_config = get_rule_config("pattern: let x = $A");
        let test_case = mock_test_case(&["var x = 123"], &["let x = 123"]);
        let result = test_case.verify_rule(&rule_config);
        assert_eq!(result.id, test_case.id);
        assert!(matches!(result.cases[0], CaseStatus::Validated));
        assert!(matches!(result.cases[1], CaseStatus::Reported));
    }

    #[test]
    fn test_invalid() {
        let rule_config = get_rule_config("pattern: let x = $A");
        let test_case = mock_test_case(&["let x = 123"], &["var x = 123"]);
        let result = test_case.verify_rule(&rule_config);
        assert_eq!(result.id, test_case.id);
        assert!(matches!(result.cases[0], CaseStatus::Noisy("let x = 123")));
        assert!(matches!(
            result.cases[1],
            CaseStatus::Missing("var x = 123")
        ));
    }

    #[test]
    fn test_verify_snapshot_with_existing() {
        let rule_config = get_rule_config("pattern: let x = $A");
        let test_case = mock_test_case(&[], &["let x = 123"]);
        let snap = mock_snapshot(&rule_config, "let x = 123");
        let snaps = mock_snapshots("let x = 123", snap.clone());
        let result = test_case.verify_with_snapshot(&rule_config, Some(&snaps));
        assert_eq!(result.cases[0], CaseStatus::Reported);
    }

    #[test]
    fn test_verify_snapshot_with_mismatch() {
        let rule_config = get_rule_config("pattern: let x = $A");
        let test_case = mock_test_case(&["var x = 123"], &["let x = 123"]);
        let snap = mock_snapshot(&rule_config, "let x = 456");
        let snaps = mock_snapshots("let x = 123", snap.clone());
        let result = test_case.verify_with_snapshot(&rule_config, Some(&snaps));
        assert_eq!(result.cases[0], CaseStatus::Validated);
        assert_eq!(
            result.cases[1],
            CaseStatus::Wrong {
                source: "let x = 123",
                actual: mock_snapshot(&rule_config, "let x = 123"),
                expected: Some(mock_snapshot(&rule_config, "let x = 456")),
            }
        );
    }

    #[test]
    fn test_verify_snapshot_without_existing() {
        let rule_config = get_rule_config("pattern: let x = $A");
        let test_case = mock_test_case(&["var x = 123"], &["let x = 123"]);
        let result = test_case.verify_with_snapshot(&rule_config, None);
        assert_eq!(result.cases[0], CaseStatus::Validated);
        assert_eq!(
            result.cases[1],
            CaseStatus::Wrong {
                source: "let x = 123",
                actual: mock_snapshot(&rule_config, "let x = 123"),
                expected: None,
            }
        );
    }

    #[test]
    fn test_verify_snapshot_without_existing_2() {
        let rule_config = get_rule_config("pattern: let x = $A");
        let test_case = mock_test_case(&["var x = 123"], &["let x = 123"]);
        let snap = mock_snapshot(&rule_config, "let x = 456");
        let snaps = mock_snapshots("let x = 456", snap.clone());
        let result = test_case.verify_with_snapshot(&rule_config, Some(&snaps));
        assert_eq!(result.cases[0], CaseStatus::Validated);
        assert_eq!(
            result.cases[1],
            CaseStatus::Wrong {
                source: "let x = 123",
                actual: mock_snapshot(&rule_config, "let x = 123"),
                expected: None,
            }
        );
    }

    #[test]
    #[should_panic]
    #[cfg(debug_assertions)]
    fn test_unmatching_id() {
        let rule_config = get_rule_config("pattern: let x = $A");
        let test_case = TestCase {
            id: "non-matching".into(),
            valid: vec![],
            invalid: vec![],
        };
        test_case.verify_rule(&rule_config);
    }
}
