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

use alloc::{vec, vec::Vec};
use mls_rs_codec::{MlsDecode, MlsEncode, MlsSize};
use mls_rs_core::{
    error::IntoAnyError,
    group::GroupContext,
    identity::{IdentityProvider, MemberValidationContext},
};

use super::{
    leaf_node::LeafNode,
    leaf_node_validator::{LeafNodeValidator, ValidationContext},
    node::LeafIndex,
};
use crate::{
    client::MlsError,
    crypto::{CipherSuiteProvider, HpkeCiphertext, HpkePublicKey},
};
use crate::{group::message_processor::ProvisionalState, time::MlsTime};

#[derive(Clone, Debug, PartialEq, Eq, MlsSize, MlsEncode, MlsDecode)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct UpdatePathNode {
    pub public_key: HpkePublicKey,
    pub encrypted_path_secret: Vec<HpkeCiphertext>,
}

#[derive(Clone, Debug, PartialEq, MlsSize, MlsEncode, MlsDecode)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct UpdatePath {
    pub leaf_node: LeafNode,
    pub nodes: Vec<UpdatePathNode>,
}

#[derive(Clone, Debug, PartialEq)]
pub struct ValidatedUpdatePath {
    pub leaf_node: LeafNode,
    pub nodes: Vec<Option<UpdatePathNode>>,
}

#[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
pub(crate) async fn validate_update_path<C: IdentityProvider, CSP: CipherSuiteProvider>(
    identity_provider: &C,
    cipher_suite_provider: &CSP,
    path: UpdatePath,
    state: &ProvisionalState,
    sender: LeafIndex,
    commit_time: Option<MlsTime>,
    current_context: &GroupContext,
) -> Result<ValidatedUpdatePath, MlsError> {
    let member_validation_context = MemberValidationContext::ForCommit {
        current_context,
        new_extensions: &state.group_context.extensions,
    };

    let leaf_validator = LeafNodeValidator::new(
        cipher_suite_provider,
        identity_provider,
        member_validation_context,
    );

    leaf_validator
        .check_if_valid(
            &path.leaf_node,
            ValidationContext::Commit((&state.group_context.group_id, *sender, commit_time)),
        )
        .await?;

    let check_identity_eq = state.applied_proposals.external_initializations.is_empty();

    if check_identity_eq {
        let existing_leaf = state.public_tree.nodes.borrow_as_leaf(sender)?;
        let original_leaf_node = existing_leaf.clone();

        identity_provider
            .valid_successor(
                &original_leaf_node.signing_identity,
                &path.leaf_node.signing_identity,
                &state.group_context.extensions,
            )
            .await
            .map_err(|e| MlsError::IdentityProviderError(e.into_any_error()))?
            .then_some(())
            .ok_or(MlsError::InvalidSuccessor)?;

        (existing_leaf.public_key != path.leaf_node.public_key)
            .then_some(())
            .ok_or(MlsError::SameHpkeKey(*sender))?;
    }

    // Unfilter the update path
    let filtered = state.public_tree.nodes.filtered(sender)?;
    let mut unfiltered_nodes = vec![];
    let mut i = 0;

    for n in path.nodes {
        while *filtered.get(i).ok_or(MlsError::WrongPathLen)? {
            unfiltered_nodes.push(None);
            i += 1;
        }

        unfiltered_nodes.push(Some(n));
        i += 1;
    }

    Ok(ValidatedUpdatePath {
        leaf_node: path.leaf_node,
        nodes: unfiltered_nodes,
    })
}

#[cfg(test)]
mod tests {
    use alloc::vec;
    use assert_matches::assert_matches;

    use crate::client::test_utils::TEST_CIPHER_SUITE;
    use crate::crypto::test_utils::test_cipher_suite_provider;
    use crate::crypto::HpkeCiphertext;
    use crate::group::message_processor::ProvisionalState;
    use crate::group::test_utils::{get_test_group_context, random_bytes, TEST_GROUP};
    use crate::identity::basic::BasicIdentityProvider;
    use crate::tree_kem::leaf_node::test_utils::default_properties;
    use crate::tree_kem::leaf_node::test_utils::get_basic_test_node_sig_key;
    use crate::tree_kem::leaf_node::LeafNodeSource;
    use crate::tree_kem::node::LeafIndex;
    use crate::tree_kem::parent_hash::ParentHash;
    use crate::tree_kem::test_utils::{get_test_leaf_nodes, get_test_tree};
    use crate::tree_kem::validate_update_path;

    use super::{UpdatePath, UpdatePathNode};
    use crate::{cipher_suite::CipherSuite, tree_kem::MlsError};

    use alloc::vec::Vec;

    #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
    async fn test_update_path(cipher_suite: CipherSuite, cred: &str) -> UpdatePath {
        let (mut leaf_node, _, signer) = get_basic_test_node_sig_key(cipher_suite, cred).await;

        leaf_node.leaf_node_source = LeafNodeSource::Commit(ParentHash::from(hex!("beef")));

        leaf_node
            .commit(
                &test_cipher_suite_provider(cipher_suite),
                TEST_GROUP,
                0,
                Some(default_properties()),
                None,
                &signer,
            )
            .await
            .unwrap();

        let node = UpdatePathNode {
            public_key: random_bytes(32).into(),
            encrypted_path_secret: vec![HpkeCiphertext {
                kem_output: random_bytes(32),
                ciphertext: random_bytes(32),
            }],
        };

        UpdatePath {
            leaf_node,
            nodes: vec![node.clone(), node],
        }
    }

    #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
    async fn test_provisional_state(cipher_suite: CipherSuite) -> ProvisionalState {
        let mut tree = get_test_tree(cipher_suite).await.public;
        let leaf_nodes = get_test_leaf_nodes(cipher_suite).await;

        tree.add_leaves(
            leaf_nodes,
            &BasicIdentityProvider,
            &test_cipher_suite_provider(cipher_suite),
        )
        .await
        .unwrap();

        ProvisionalState {
            public_tree: tree,
            applied_proposals: Default::default(),
            group_context: get_test_group_context(1, cipher_suite).await,
            indexes_of_added_kpkgs: vec![],
            external_init_index: None,
            unused_proposals: vec![],
        }
    }

    #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
    async fn test_valid_leaf_node() {
        let cipher_suite_provider = test_cipher_suite_provider(TEST_CIPHER_SUITE);
        let update_path = test_update_path(TEST_CIPHER_SUITE, "creator").await;
        let state = test_provisional_state(TEST_CIPHER_SUITE).await;

        let validated = validate_update_path(
            &BasicIdentityProvider,
            &cipher_suite_provider,
            update_path.clone(),
            &state,
            LeafIndex::unchecked(0),
            None,
            &state.group_context,
        )
        .await
        .unwrap();

        let expected = update_path.nodes.into_iter().map(Some).collect::<Vec<_>>();

        assert_eq!(validated.nodes, expected);
        assert_eq!(validated.leaf_node, update_path.leaf_node);
    }

    #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
    async fn test_invalid_key_package() {
        let cipher_suite_provider = test_cipher_suite_provider(TEST_CIPHER_SUITE);
        let mut update_path = test_update_path(TEST_CIPHER_SUITE, "creator").await;
        update_path.leaf_node.signature = random_bytes(32);
        let state = test_provisional_state(TEST_CIPHER_SUITE).await;

        let validated = validate_update_path(
            &BasicIdentityProvider,
            &cipher_suite_provider,
            update_path,
            &state,
            LeafIndex::unchecked(0),
            None,
            &state.group_context,
        )
        .await;

        assert_matches!(validated, Err(MlsError::InvalidSignature));
    }

    #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
    async fn validating_path_fails_with_different_identity() {
        let cipher_suite_provider = test_cipher_suite_provider(TEST_CIPHER_SUITE);
        let cipher_suite = TEST_CIPHER_SUITE;
        let update_path = test_update_path(cipher_suite, "foobar").await;
        let state = test_provisional_state(TEST_CIPHER_SUITE).await;

        let validated = validate_update_path(
            &BasicIdentityProvider,
            &cipher_suite_provider,
            update_path,
            &state,
            LeafIndex::unchecked(0),
            None,
            &state.group_context,
        )
        .await;

        assert_matches!(validated, Err(MlsError::InvalidSuccessor));
    }

    #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
    async fn validating_path_fails_with_same_hpke_key() {
        let cipher_suite_provider = test_cipher_suite_provider(TEST_CIPHER_SUITE);
        let update_path = test_update_path(TEST_CIPHER_SUITE, "creator").await;
        let mut state = test_provisional_state(TEST_CIPHER_SUITE).await;

        state
            .public_tree
            .nodes
            .borrow_as_leaf_mut(LeafIndex::unchecked(0))
            .unwrap()
            .public_key = update_path.leaf_node.public_key.clone();

        let validated = validate_update_path(
            &BasicIdentityProvider,
            &cipher_suite_provider,
            update_path,
            &state,
            LeafIndex::unchecked(0),
            None,
            &state.group_context,
        )
        .await;

        assert_matches!(validated, Err(MlsError::SameHpkeKey(_)));
    }
}
