// SPDX-FileCopyrightText: Peter Pentchev <roam@ringlet.net>
// SPDX-License-Identifier: BSD-2-Clause
//! Load the test definitions.

use std::fs;

use eyre::{WrapErr as _, eyre};
use log::debug;
use media_type_version::{Config as MTVConfig, Error as MTVError};
use serde_derive::Deserialize;

use crate::defs::{Config, Error, TestData};

/// The prefix that we expect to find in the test definitions' `mediaType` field.
const MT_PREFIX: &str = "vnd.ringlet.sysutils.remrun.tests/config";

/// The suffix that we expect to find in the test definitions' `mediaType` field.
const MT_SUFFIX: &str = "+json";

/// The path where we expect the test definitions file to be in the `remrun` source tree.
const PATH_TESTS: &str = "tests/test-data/tests.json";

/// Only get the `mediaType` field from the test definitions file.
#[derive(Debug, Deserialize)]
struct TopMediaTypeOnly {
    /// The test definitions file's format version.
    #[serde(rename = "mediaType")]
    media_type: String,
}

/// Load the test definitions from the JSON file.
///
/// # Errors
///
/// [`Error::FsError`] if the test definitions file could not be read.
/// [`Error::Internal`] if the test definitios file could not be parsed.
#[inline]
pub fn load_defs(cfg: &Config) -> Result<TestData, Error> {
    let test_defs = cfg.root_dir().join(PATH_TESTS);
    debug!("about to read {test_defs}");
    let contents = fs::read_to_string(&test_defs)
        .with_context(|| format!("Could not read the test definitions from {test_defs}"))
        .map_err(Error::FsError)?;
    let fver = {
        let mtv_only: TopMediaTypeOnly = serde_json::from_str(&contents)
            .with_context(|| format!("Could not get the mediaType field from {test_defs}"))
            .map_err(Error::Internal)?;
        let mtv_cfg = MTVConfig::builder()
            .prefix(MT_PREFIX)
            .suffix(MT_SUFFIX)
            .build()
            .context("Could not build the media type version parser config")
            .map_err(Error::Internal)?;
        media_type_version::extract(&mtv_cfg, &mtv_only.media_type)
            .map_err(MTVError::into_owned_error)
            .with_context(|| format!("Could not parse the mediaType field from {test_defs}"))
            .map_err(Error::Internal)?
    };
    if (fver.major(), fver.minor()) != (0, 1) {
        return Err(Error::Internal(eyre!(
            "Unexpected test definitions format version {major}.{minor}",
            major = fver.major(),
            minor = fver.minor()
        )));
    }

    serde_json::from_str::<TestData>(&contents)
        .with_context(|| format!("Could not load the test definitions from {test_defs}"))
        .map_err(Error::Internal)
}

#[cfg(test)]
#[expect(clippy::panic_in_result_fn, reason = "this is a test suite")]
mod tests {
    use eyre::Result;

    use crate::defs::{self, TestCase, TestData};

    fn collect_matching<'data>(tdata: &'data TestData, needle: &str) -> Vec<&'data str> {
        tdata
            .tests()
            .iter()
            .filter(|tcase| tcase.output_template().contains(needle))
            .map(TestCase::title)
            .collect()
    }

    #[test]
    fn load() -> Result<()> {
        let cfg = defs::get_test_config()?;
        let tdata = super::load_defs(&cfg)?;
        assert_eq!(
            collect_matching(&tdata, "@REMOTE_TEMP_DIR@"),
            vec!["Do not run \"whoami\" on the remote host"]
        );
        assert_eq!(
            collect_matching(&tdata, "^0$"),
            vec!["Pass command-line arguments to the executed program 2"]
        );
        Ok(())
    }
}
