// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// Copyright by contributors to this project.
// SPDX-License-Identifier: (Apache-2.0 OR MIT)

/// Basic credential identity provider.
pub mod basic;

/// X.509 certificate identity provider.
#[cfg(feature = "x509")]
pub mod x509 {
    pub use mls_rs_identity_x509::*;
}

pub use mls_rs_core::identity::{
    Credential, CredentialType, CustomCredential, MlsCredential, SigningIdentity,
};

#[cfg(test)]
pub(crate) mod test_utils {
    #[cfg(feature = "std")]
    use alloc::boxed::Box;
    use alloc::vec;
    use alloc::vec::Vec;
    use mls_rs_core::{
        crypto::{CipherSuite, CipherSuiteProvider, SignatureSecretKey},
        error::IntoAnyError,
        extension::ExtensionList,
        identity::{
            Credential, CredentialType, IdentityProvider, MemberValidationContext, SigningIdentity,
        },
        time::MlsTime,
    };

    use crate::crypto::test_utils::test_cipher_suite_provider;

    use super::basic::{BasicCredential, BasicIdentityProvider};

    #[derive(Debug)]
    #[cfg_attr(feature = "std", derive(thiserror::Error))]
    #[cfg_attr(
        feature = "std",
        error("expected basic or custom credential type 42 found: {0:?}")
    )]
    pub struct BasicWithCustomProviderError(CredentialType);

    impl IntoAnyError for BasicWithCustomProviderError {
        #[cfg(feature = "std")]
        fn into_dyn_error(self) -> Result<Box<dyn std::error::Error + Send + Sync>, Self> {
            Ok(self.into())
        }
    }

    #[derive(Debug, Clone)]
    pub struct BasicWithCustomProvider {
        pub(crate) basic: BasicIdentityProvider,
        pub(crate) allow_any_custom: bool,
        supported_cred_types: Vec<CredentialType>,
    }

    impl BasicWithCustomProvider {
        pub const CUSTOM_CREDENTIAL_TYPE: u16 = 42;

        pub fn new(basic: BasicIdentityProvider) -> BasicWithCustomProvider {
            BasicWithCustomProvider {
                basic,
                allow_any_custom: false,
                supported_cred_types: vec![
                    CredentialType::BASIC,
                    Self::CUSTOM_CREDENTIAL_TYPE.into(),
                ],
            }
        }

        pub fn with_credential_type(mut self, cred_type: CredentialType) -> Self {
            self.supported_cred_types.push(cred_type);
            self
        }

        #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
        async fn resolve_custom_identity(
            &self,
            signing_id: &SigningIdentity,
        ) -> Result<Vec<u8>, BasicWithCustomProviderError> {
            self.basic
                .identity(signing_id, &Default::default())
                .await
                .or_else(|_| {
                    signing_id
                        .credential
                        .as_custom()
                        .map(|c| {
                            if c.credential_type
                                == CredentialType::from(Self::CUSTOM_CREDENTIAL_TYPE)
                                || self.allow_any_custom
                            {
                                Ok(c.data.to_vec())
                            } else {
                                Err(BasicWithCustomProviderError(c.credential_type))
                            }
                        })
                        .transpose()?
                        .ok_or_else(|| {
                            BasicWithCustomProviderError(signing_id.credential.credential_type())
                        })
                })
        }
    }

    impl Default for BasicWithCustomProvider {
        fn default() -> Self {
            Self::new(BasicIdentityProvider::new())
        }
    }

    #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
    #[cfg_attr(mls_build_async, maybe_async::must_be_async)]
    impl IdentityProvider for BasicWithCustomProvider {
        type Error = BasicWithCustomProviderError;

        async fn validate_member(
            &self,
            _signing_identity: &SigningIdentity,
            _timestamp: Option<MlsTime>,
            _context: MemberValidationContext<'_>,
        ) -> Result<(), Self::Error> {
            //TODO: Is it actually beneficial to check the key, or does that already happen elsewhere before
            //this point?
            Ok(())
        }

        async fn validate_external_sender(
            &self,
            _signing_identity: &SigningIdentity,
            _timestamp: Option<MlsTime>,
            _extensions: Option<&ExtensionList>,
        ) -> Result<(), Self::Error> {
            //TODO: Is it actually beneficial to check the key, or does that already happen elsewhere before
            //this point?
            Ok(())
        }

        async fn identity(
            &self,
            signing_id: &SigningIdentity,
            _extensions: &ExtensionList,
        ) -> Result<Vec<u8>, Self::Error> {
            self.resolve_custom_identity(signing_id).await
        }

        async fn valid_successor(
            &self,
            predecessor: &SigningIdentity,
            successor: &SigningIdentity,
            _extensions: &ExtensionList,
        ) -> Result<bool, Self::Error> {
            let predecessor = self.resolve_custom_identity(predecessor).await?;
            let successor = self.resolve_custom_identity(successor).await?;

            Ok(predecessor == successor)
        }

        fn supported_types(&self) -> Vec<CredentialType> {
            self.supported_cred_types.clone()
        }
    }

    #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
    pub async fn get_test_signing_identity(
        cipher_suite: CipherSuite,
        identity: &[u8],
    ) -> (SigningIdentity, SignatureSecretKey) {
        let provider = test_cipher_suite_provider(cipher_suite);
        let (secret_key, public_key) = provider.signature_key_generate().await.unwrap();

        let basic = get_test_basic_credential(identity.to_vec());

        (SigningIdentity::new(basic, public_key), secret_key)
    }

    pub fn get_test_basic_credential(identity: Vec<u8>) -> Credential {
        BasicCredential::new(identity).into_credential()
    }
}
