// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
use std::future::Future;

use rquickjs::{Array, Class, Ctx, FromJs, Result, Value};

use super::{
    algorithm_not_supported_error,
    crypto_key::{CryptoKey, KeyKind},
    derive_algorithm::DeriveAlgorithm,
    derive_bits::{derive_bits, DeriveBitsLength},
    key_algorithm::{KeyAlgorithm, KeyAlgorithmMode, KeyAlgorithmWithUsages},
    util::ResultDomExt,
};

pub fn subtle_derive_key<'js>(
    ctx: Ctx<'js>,
    algorithm: Value<'js>,
    base_key: Class<'js, CryptoKey<'js>>,
    derived_key_algorithm: Value<'js>,
    extractable: bool,
    key_usages: Array<'js>,
) -> impl Future<Output = Result<Class<'js, CryptoKey<'js>>>> + 'js {
    let prepared = prepare_derive_key(&ctx, algorithm, derived_key_algorithm, key_usages);

    async move {
        let (algorithm, key_algorithm) = prepared?;

        derive_key(&ctx, &algorithm, &base_key, extractable, key_algorithm)
    }
}

fn prepare_derive_key<'js>(
    ctx: &Ctx<'js>,
    algorithm: Value<'js>,
    derived_key_algorithm: Value<'js>,
    key_usages: Array<'js>,
) -> Result<(DeriveAlgorithm, KeyAlgorithmWithUsages)> {
    let algorithm = DeriveAlgorithm::from_js(ctx, algorithm)?;

    let key_algorithm = KeyAlgorithm::from_js(
        ctx,
        KeyAlgorithmMode::Derive,
        derived_key_algorithm,
        key_usages,
    )?;

    Ok((algorithm, key_algorithm))
}

fn derive_key<'js>(
    ctx: &Ctx<'js>,
    algorithm: &DeriveAlgorithm,
    base_key: &Class<'js, CryptoKey<'js>>,
    extractable: bool,
    key_algorithm: KeyAlgorithmWithUsages,
) -> Result<Class<'js, CryptoKey<'js>>> {
    let length = match &key_algorithm.algorithm {
        KeyAlgorithm::Aes { length } => *length,
        KeyAlgorithm::Hmac { length, .. } => *length,
        KeyAlgorithm::Derive { .. } => 0,
        _ => {
            return algorithm_not_supported_error(ctx);
        },
    };

    let base_key = base_key.borrow();

    base_key.check_validity("deriveKey").or_throw_dom(ctx)?;

    let bytes = derive_bits(
        ctx,
        algorithm,
        &base_key,
        DeriveBitsLength::Specified(length as u32),
    )?;

    let key = CryptoKey::new(
        KeyKind::Secret,
        key_algorithm.name,
        extractable,
        key_algorithm.algorithm,
        key_algorithm.public_usages,
        bytes,
    );

    Class::instance(ctx.clone(), key)
}
