use std::marker::PhantomData;

use aead::consts::U256;
use aes::cipher::{
    typenum::{IsLess, Le, NonZero},
    BlockSizeUser,
};
use hmac::{digest::OutputSizeUser, Hmac, Mac};
use rustls::crypto;
use sha2::{
    digest::{
        block_buffer::Eager,
        core_api::{BufferKindUser, CoreProxy, FixedOutputCore, UpdateCore},
        HashMarker,
    },
    Digest, Sha256, Sha384,
};

pub(crate) struct HmacSha<D>(PhantomData<D>)
where
    D: CoreProxy + Digest,
    D::Core: HashMarker
        + UpdateCore
        + FixedOutputCore
        + BufferKindUser<BufferKind = Eager>
        + Default
        + Clone,
    <D::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
    Le<<D::Core as BlockSizeUser>::BlockSize, U256>: NonZero;

pub(crate) struct HmacShaKey<D>(Hmac<D>)
where
    D: CoreProxy,
    D::Core: HashMarker
        + UpdateCore
        + FixedOutputCore
        + BufferKindUser<BufferKind = Eager>
        + Default
        + Clone,
    <D::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
    Le<<D::Core as BlockSizeUser>::BlockSize, U256>: NonZero;

pub(crate) static HMAC_SHA256: HmacSha<Sha256> = HmacSha(PhantomData);
pub(crate) static HMAC_SHA384: HmacSha<Sha384> = HmacSha(PhantomData);

impl<D> crypto::hmac::Hmac for HmacSha<D>
where
    D: CoreProxy + Digest + Send + Sync + 'static,
    D::Core: HashMarker
        + Send
        + Sync
        + UpdateCore
        + FixedOutputCore
        + BufferKindUser<BufferKind = Eager>
        + Default
        + Clone,
    <D::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
    Le<<D::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
{
    fn with_key(&self, key: &[u8]) -> Box<dyn crypto::hmac::Key> {
        Box::new(HmacShaKey::<D>(Hmac::new_from_slice(key).unwrap()))
    }

    fn hash_output_len(&self) -> usize {
        <D as Digest>::output_size()
    }
}

impl<D> crypto::hmac::Key for HmacShaKey<D>
where
    D: CoreProxy,
    D::Core: HashMarker
        + Send
        + Sync
        + UpdateCore
        + FixedOutputCore
        + BufferKindUser<BufferKind = Eager>
        + Default
        + Clone,
    <D::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
    Le<<D::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
{
    fn sign_concat(&self, first: &[u8], middle: &[&[u8]], last: &[u8]) -> crypto::hmac::Tag {
        let mut ctx = self.0.clone();
        ctx.update(first);
        for m in middle {
            ctx.update(m);
        }
        ctx.update(last);
        crypto::hmac::Tag::new(&ctx.finalize().into_bytes()[..])
    }

    fn tag_len(&self) -> usize {
        D::Core::output_size()
    }
}
