// Copyright 2025 Adobe. All rights reserved.
// This file is licensed to you under the Apache License,
// Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
// or the MIT license (http://opensource.org/licenses/MIT),
// at your option.

// Unless required by applicable law or agreed to in writing,
// this software is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or
// implied. See the LICENSE-MIT and LICENSE-APACHE files for the
// specific language governing permissions and limitations under
// each license.

use async_trait::async_trait;
use c2pa_raw_crypto::{RawSigner, SigningAlg};

use crate::{
    crypto::cose::cose_reserve_size, dynamic_assertion::AsyncDynamicAssertion,
    identity::builder::AsyncIdentityAssertionBuilder, AsyncSigner, Result,
};

/// An `AsyncIdentityAssertionSigner` extends the [`AsyncSigner`] interface to
/// add zero or more identity assertions to a C2PA [`Manifest`] that is being
/// produced.
///
/// [`AsyncSigner`]: crate::AsyncSigner
/// [`Manifest`]: crate::Manifest
pub struct AsyncIdentityAssertionSigner {
    #[cfg(not(target_arch = "wasm32"))]
    signer: Box<dyn RawSigner + Sync + Send>,

    #[cfg(target_arch = "wasm32")]
    signer: Box<dyn RawSigner>,

    cert_chain: Vec<Vec<u8>>,

    identity_assertions: std::sync::RwLock<Vec<AsyncIdentityAssertionBuilder>>,
}

impl AsyncIdentityAssertionSigner {
    /// Create an `AsyncIdentityAssertionSigner` wrapping the provided
    /// [`RawSigner`] instance and its signing certificate chain (each
    /// certificate in DER form, end-entity first).
    #[cfg(not(target_arch = "wasm32"))]
    pub fn new(signer: Box<dyn RawSigner + Sync + Send>, cert_chain: Vec<Vec<u8>>) -> Self {
        Self {
            signer,
            cert_chain,
            identity_assertions: std::sync::RwLock::new(vec![]),
        }
    }

    /// Create an `AsyncIdentityAssertionSigner` wrapping the provided
    /// [`RawSigner`] instance and its signing certificate chain (each
    /// certificate in DER form, end-entity first).
    #[cfg(target_arch = "wasm32")]
    pub fn new(signer: Box<dyn RawSigner>, cert_chain: Vec<Vec<u8>>) -> Self {
        Self {
            signer,
            cert_chain,
            identity_assertions: std::sync::RwLock::new(vec![]),
        }
    }

    /// (FOR USE BY INTERNAL TESTS ONLY): Create an AsyncIdentityAssertionSigner
    /// using test credentials for a particular algorithm.
    #[cfg(test)]
    pub(crate) fn from_test_credentials(alg: SigningAlg) -> Self {
        use c2pa_raw_crypto::signer_from_private_key;

        use crate::{
            crypto::cert_chain_pem_to_der,
            identity::tests::fixtures::cert_chain_and_private_key_for_alg,
        };

        let (cert_chain, private_key) = cert_chain_and_private_key_for_alg(alg);

        #[allow(clippy::unwrap_used)]
        Self {
            signer: signer_from_private_key(&private_key, alg).unwrap(),
            cert_chain: cert_chain_pem_to_der(&cert_chain).unwrap(),
            identity_assertions: std::sync::RwLock::new(vec![]),
        }
    }

    /// Add an [`AsyncIdentityAssertionBuilder`] to be used when signing the
    /// next [`Manifest`].
    ///
    /// IMPORTANT: When [`sign()`] is called, the list of
    /// [`AsyncIdentityAssertionBuilder`]s will be cleared.
    ///
    /// [`Manifest`]: crate::Manifest
    /// [`sign()`]: Self::sign
    pub fn add_identity_assertion(&mut self, iab: AsyncIdentityAssertionBuilder) {
        #[allow(clippy::unwrap_used)]
        let mut identity_assertions = self.identity_assertions.write().unwrap();
        // TO DO: Replace with error handling in the very unlikely case of a panic here.
        identity_assertions.push(iab);
    }
}

#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl AsyncSigner for AsyncIdentityAssertionSigner {
    async fn sign(&self, data: Vec<u8>) -> Result<Vec<u8>> {
        // Raw signing is synchronous (CPU-bound).
        self.signer.sign(&data).map_err(|e| e.into())
    }

    fn alg(&self) -> SigningAlg {
        self.signer.alg()
    }

    fn certs(&self) -> Result<Vec<Vec<u8>>> {
        Ok(self.cert_chain.clone())
    }

    fn reserve_size(&self) -> usize {
        cose_reserve_size(
            self.signer.max_signature_size(),
            &self.cert_chain,
            false,
            None,
        )
    }

    fn dynamic_assertions(&self) -> Vec<Box<dyn AsyncDynamicAssertion>> {
        #[allow(clippy::unwrap_used)]
        let mut identity_assertions = self.identity_assertions.write().unwrap();
        // TO DO: Replace with error handling in the very unlikely case of a panic here.

        let ia_clone = identity_assertions.split_off(0);
        let mut dynamic_assertions: Vec<Box<dyn AsyncDynamicAssertion>> = vec![];

        for ia in ia_clone.into_iter() {
            dynamic_assertions.push(Box::new(ia));
        }

        dynamic_assertions
    }
}
