//! Internal module. Semver exempt. Do not use.
use pretty_assertions::assert_eq;

#[cfg(feature = "serde")]
use serde::Serialize;

use crate::{Templates, TemplatesBuilder, Value};

#[derive(Debug)]
/// Internal struct. Semver exempt. Do not use.
pub struct SingleSourceTestBuilder {
    path: &'static str,
    source: &'static str,
}

impl SingleSourceTestBuilder {
    #[must_use]
    pub fn new(path: &'static str, source: &'static str) -> SingleSourceTestBuilder {
        SingleSourceTestBuilder { path, source }
    }

    #[must_use]
    pub fn parse(self) -> ParsedTemplateTestBuilder {
        ParsedTemplateTestBuilder {
            builder: TemplatesBuilder::new()
                .with_source_file(self.path, self.source)
                .expect("templates should parse correctly"),
        }
    }
}

#[derive(Debug)]
/// Internal struct. Semver exempt. Do not use.
pub struct ParsedTemplateTestBuilder {
    builder: TemplatesBuilder<'static>,
}

impl ParsedTemplateTestBuilder {
    #[must_use]
    pub fn build_single(self, name: &'static str) -> SingleBuiltTemplateTestBuilder {
        // todo: remove length check and first element access with pattern matching

        assert!(
            self.builder.template_names().len() == 1,
            "Expected a single template but got {tmpls:#?} instead.",
            tmpls = self.builder.template_names().collect::<Vec<_>>()
        );

        let tmpl = self.builder.template_names().next().unwrap();
        assert_eq!(tmpl, name);

        let tmpls = match self.builder.build() {
            Ok(tmpls) => tmpls,
            Err(err) => {
                panic!("{err}")
            }
        };

        SingleBuiltTemplateTestBuilder { name, tmpls }
    }

    pub fn build_multi<I: IntoIterator<Item = &'static str>>(
        self,
        names: I,
    ) -> MultiBuiltTemplateTestBuilder {
        let mut expected_names: Vec<&'static str> = names.into_iter().collect();
        expected_names.sort_unstable();
        let mut actual_names: Vec<&'static str> = self.builder.template_names().collect();
        actual_names.sort_unstable();
        assert_eq!(actual_names, expected_names);

        let tmpls = match self.builder.build() {
            Ok(tmpls) => tmpls,
            Err(err) => {
                panic!("{err}")
            }
        };

        MultiBuiltTemplateTestBuilder { tmpls }
    }
}

/// Internal struct. Semver exempt. Do not use.
pub struct SingleBuiltTemplateTestBuilder {
    name: &'static str,
    tmpls: Templates,
}

impl SingleBuiltTemplateTestBuilder {
    #[must_use]
    pub fn into_inner(self) -> Templates {
        self.tmpls
    }

    pub fn render_and_expect<V: Into<Value>>(&self, arguments: V, expected: &str) {
        assert_eq!(
            self.tmpls
                .render(self.name, arguments)
                .expect("templates should render correctly"),
            expected
        );
    }

    #[cfg(feature = "serde")]
    pub fn render_serde_and_expect<D: Serialize>(&self, data: D, expected: &str) {
        assert_eq!(
            self.tmpls
                .render_serde(self.name, data)
                .expect("templates should render correctly"),
            expected
        );
    }
}

#[derive(Debug)]
/// Internal struct. Semver exempt. Do not use.
pub struct MultiBuiltTemplateTestBuilder {
    tmpls: Templates,
}

impl MultiBuiltTemplateTestBuilder {
    #[must_use]
    pub fn into_inner(self) -> Templates {
        self.tmpls
    }

    pub fn render_and_expect<V: Into<Value>>(&self, name: &str, arguments: V, expected: &str) {
        assert_eq!(
            self.tmpls
                .render(name, arguments)
                .expect("templates should render correctly"),
            expected
        );
    }

    #[cfg(feature = "serde")]
    pub fn render_serde_and_expect<D: Serialize>(&self, name: &str, data: D, expected: &str) {
        assert_eq!(
            self.tmpls
                .render_serde(name, data)
                .expect("templates should render correctly"),
            expected
        );
    }
}

#[allow(clippy::crate_in_macro_def, non_snake_case)]
#[macro_export]
#[doc(hidden)]
/// Internal macro. Semver exempt. Do not use.
macro_rules! __internal__single_source_single_template_tests {
    ($test_name:ident, $expected:literal, $path:literal, $proc:expr, $name:literal, $data:expr, {$($serde_types:item)*}, $serde_data:expr) => {
        mod $test_name {
            use cookie_cutter::*;

            fn common() -> $crate::__internal__tests_util::SingleBuiltTemplateTestBuilder {
                $crate::__internal__tests_util::SingleSourceTestBuilder::new($path, include_str!($path))
                    .parse()
                    .build_single($name)
            }

            #[test]
            fn manual() {
                common().render_and_expect($data, $expected);
            }

            #[cfg(feature = "serde")]
            use serde::Serialize;

            #[cfg(feature = "serde")]
            $($serde_types)*

            #[cfg(feature = "serde")]
            #[test]
            fn serde() {
                common().render_serde_and_expect(
                    $serde_data,
                    $expected,
                )
            }

            #[cfg(feature = "proc_macros")]
            #[test]
            fn proc_macro() {
                assert_eq!(
                    $proc
                        .render($name, $data)
                        .unwrap(),
                    $expected
                )
            }
        }
    };
}

#[macro_export]
#[allow(non_snake_case)]
#[doc(hidden)]
/// Internal macro. Semver exempt. Do not use.
macro_rules! __internal__single_source_single_template_no_params_tests {
    ($test_name:ident, $expected:literal, $path:literal, $proc:expr, $name:literal) => {
        $crate::__internal__single_source_single_template_tests!(
            $test_name,
            $expected,
            $path,
            $proc,
            $name,
            $crate::Value::Struct(::std::collections::BTreeMap::new()),
            {
                #[derive(Serialize)]
                struct Data {}
            },
            Data {}
        );
    };
}

#[macro_export]
#[allow(non_snake_case)]
#[doc(hidden)]
/// Internal macro. Semver exempt. Do not use.
macro_rules! __internal__single_source_multi_template_tests {
    ($test_name:ident, $expected:literal, $path:literal, $proc:expr, $names:expr, $name:literal, $data:expr, {$($serde_types:item)*}, $serde_data:expr) => {
        mod $test_name {
            use cookie_cutter::*;

            fn common() -> $crate::__internal__tests_util::MultiBuiltTemplateTestBuilder {
                $crate::__internal__tests_util::SingleSourceTestBuilder::new($path, include_str!($path))
                    .parse()
                    .build_multi($names)
            }

            #[test]
            fn manual() {
                common().render_and_expect($name, $data, $expected);
            }

            #[cfg(feature = "serde")]
            use serde::Serialize;

            #[cfg(feature = "serde")]
            $($serde_types)*

            #[cfg(feature = "serde")]
            #[test]
            fn serde() {
                common().render_serde_and_expect($name, $serde_data, $expected);
            }

            #[cfg(feature = "proc_macros")]
            #[test]
            fn proc_macro() {
                assert_eq!(
                    $proc
                        .render($name, $data)
                        .unwrap(),
                    $expected
                )
            }
        }
    };
}

#[macro_export]
#[allow(non_snake_case)]
#[doc(hidden)]
/// Internal macro. Semver exempt. Do not use.
macro_rules! __internal__single_source_multi_template_no_params_tests {
    ($test_name:ident, $expected:literal, $path:literal, $proc:expr, $names:expr, $name:literal) => {
        $crate::__internal__single_source_multi_template_tests!(
            $test_name,
            $expected,
            $path,
            $proc,
            $names,
            $name,
            $crate::Value::Struct(::std::collections::BTreeMap::new()),
            {
                #[derive(Serialize)]
                struct Data {}
            },
            Data {}
        );
    };
}
