#![allow(clippy::missing_panics_doc, reason = "tests are allowed to panic")]
#![allow(clippy::panic, reason = "tests are allowed panic")]

use std::{collections::BTreeMap, path::Path};

use super::Report;
use crate::{
    json, tariff,
    test::{self, Expectation},
    warning, Version,
};

/// Each `test*.json` file in the `test_data/v*/lint` directories results in a test run of
/// the associated test function.
#[derive(serde::Deserialize)]
struct TestRun<'buf> {
    /// The input tariff for this test run.
    #[serde(borrow)]
    tariff: &'buf serde_json::value::RawValue,

    /// The expectation for the priced CDR generated from the tariff.
    expect: Expect,
}

/// Expectations for the result of calling `tariff::lint`.
#[derive(serde::Deserialize)]
struct Expect {
    #[serde(default)]
    unexpected_fields: Expectation<Vec<json::test::PathGlob>>,

    #[serde(default)]
    warnings: Expectation<BTreeMap<String, Vec<String>>>,
}

#[track_caller]
pub fn run_lint(test_json: &str, test_file_path: &Path, expected_version: Version) {
    let mut test_json = test_json.to_owned();
    let test_run = {
        json_strip_comments::strip(&mut test_json).unwrap_or_else(|err| {
            panic!(
                "Unable to strip comments from {}:\n{:#?}",
                test_file_path.display(),
                err
            );
        });
        serde_json::from_str::<TestRun<'_>>(&test_json).unwrap_or_else(|err| {
            panic!("Unable to parse {}:\n{:#?}", test_file_path.display(), err);
        })
    };

    let TestRun { tariff, expect } = test_run;
    let expect_file_name = test_file_path.display().to_string();
    let Expect {
        unexpected_fields: expect_unexpected_fields,
        warnings: expect_warnings,
    } = expect;

    let tariff = {
        let tariff_json = tariff.get();
        let doc = json::parse_object(tariff_json).unwrap_or_else(|err| {
            panic!("Unable to parse the tariff:\n{err:#?}");
        });
        let (tariff, warnings) = tariff::build(doc, expected_version).into_parts();
        let mut unexpected_fields = warnings.unexpected_fields();

        test::expect_unexpected_fields(
            &expect_file_name,
            &mut unexpected_fields,
            expect_unexpected_fields,
        );

        tariff
    };

    let Report { warnings } = super::lint(&tariff);

    // If there are warnings reported and there is no `expect` file
    // then panic printing the fields of the expect JSON object that would silence these warnings.
    // These can be copied into the `test*.json` file.
    warning::test::assert_warnings(&expect_file_name, &warnings, expect_warnings);
}
