use crate::{Error, Path, Result};
use klirr_foundation::RonError;
use serde::de::DeserializeOwned;

/// Returns the type name of the generic type `T` as a `String`.
///
/// # Examples
/// ```
/// extern crate klirr_core_invoice;
/// use klirr_core_invoice::*;
/// let type_name = type_name::<CompanyInformation>();
/// assert!(type_name.ends_with("CompanyInformation"));
/// ```
pub fn type_name<T>() -> String {
    klirr_foundation::type_name::<T>()
}

/// Tries to load the contents of a file at the given path and deserialize it from RON format into the specified type.
///
/// # Throws
/// - `Error::FileNotFound` if the file does not exist or cannot be read
/// - `Error::Deserialize` if the contents cannot be deserialized into the specified type
pub fn deserialize_contents_of_ron<T: DeserializeOwned>(path: impl AsRef<Path>) -> Result<T> {
    klirr_foundation::deserialize_contents_of_ron(path).map_err(map_ron_error)
}

pub fn deserialize_ron_str<T: DeserializeOwned>(ron_str: &str) -> Result<T> {
    klirr_foundation::deserialize_ron_str(ron_str).map_err(map_ron_error)
}

fn map_ron_error(error: RonError) -> Error {
    match error {
        RonError::FileNotFound { path, underlying } => Error::FileNotFound { path, underlying },
        RonError::Deserialize { type_name, error } => Error::Deserialize { type_name, error },
        RonError::FailedToSerialize {
            type_name,
            underlying,
        } => Error::FailedToRonSerializeData {
            type_name,
            underlying,
        },
        RonError::FailedToWriteDataToDisk { underlying } => {
            Error::FailedToWriteDataToDisk { underlying }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::{CompanyInformation, Rate, ServiceFees};
    use rust_decimal::dec;
    use std::fs;
    use test_log::test;

    #[test]
    fn test_type_name() {
        let type_name = type_name::<CompanyInformation>();
        assert!(type_name.ends_with("CompanyInformation"));
    }

    #[test]
    fn deserialize_contents_of_ron_fail_file_not_found() {
        let result: Result<CompanyInformation> =
            deserialize_contents_of_ron(Path::new("non_existent_file.ron"));
        assert!(result.is_err(), "Expected error, got: {:?}", result);
        if let Err(Error::FileNotFound {
            path: _,
            underlying: _,
        }) = result
        {
            // Expected error
        } else {
            panic!("Expected FileNotFound error, got: {:?}", result);
        }
    }

    #[test]
    fn deserialize_contents_of_ron_fail() {
        let tempfile = tempfile::NamedTempFile::new().unwrap();
        let path = tempfile.path();
        let result: Result<CompanyInformation> = deserialize_contents_of_ron(path);
        assert!(result.is_err(), "Expected error, got: {:?}", result);
        if let Err(Error::Deserialize { type_name, error }) = result {
            assert!(
                type_name.contains("CompanyInformation"),
                "Expected type name to contain 'CompanyInformation', got: {}",
                type_name
            );
            assert!(
                error.contains("Expected opening"),
                "Expected deserialization error, got: {}",
                error
            );
        } else {
            panic!("Expected Deserialize error, got: {:?}", result);
        }
    }

    #[test]
    fn deserialize_ron_success() {
        let tempfile = tempfile::NamedTempFile::new().unwrap();
        let path = tempfile.path();
        fs::write(
            path,
            r#"
            ServiceFees(
              name: "Agreed Consulting Service",
              rate: Daily(UnitPrice(777.0)),
              cadence: Monthly,
            )
            "#,
        )
        .unwrap();

        let result: Result<ServiceFees> = deserialize_contents_of_ron(path);
        assert!(result.is_ok(), "Expected success, got: {:?}", result);
        let consulting_service = result.unwrap();
        assert_eq!(consulting_service.name(), "Agreed Consulting Service");
        assert_eq!(*consulting_service.rate(), Rate::daily(dec!(777.0)));
    }
}
