use crate::token::{DecodeError, Decoder, Encoder, Signature, VerifyError};
use crate::{UserAgent, UserID};

pub type TokenID = crate::Credential<16>;
mod exp_date;
mod user_agent;
pub use exp_date::ExpirationDate;

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Payload {
    pub id: TokenID,
    pub user_agent: UserAgent,
    pub user_id: UserID,
    pub expires_at: ExpirationDate,
}

impl Decoder for Payload {
    const SIZE: usize = TokenID::SIZE + UserAgent::SIZE + UserID::SIZE + ExpirationDate::SIZE;

    fn decode(buf: &mut impl bytes::Buf) -> Result<Self, DecodeError>
    where
        Self: Sized,
    {
        if buf.remaining() < Self::SIZE {
            return Err(DecodeError::InvalidLength {
                expected: Self::SIZE,
                received: buf.remaining(),
            });
        }
        let token_id = TokenID::decode(buf).map_err(DecodeError::InvalidTokenID)?;
        let user_id = UserID::decode(buf).map_err(DecodeError::InvalidUserID)?;
        let user_agent = UserAgent::decode(buf)?;
        let exp_date = ExpirationDate::decode(buf)?;

        Ok(Self {
            id: token_id,
            expires_at: exp_date,
            user_agent,
            user_id,
        })
    }
}

impl Encoder for Payload {
    fn encode(&self, buf: &mut impl bytes::BufMut) {
        self.id.encode(buf);
        self.user_id.encode(buf);
        self.user_agent.encode(buf);
        self.expires_at.encode(buf);
    }
}

use bytes::BytesMut;
use hmac::{Hmac, Mac, NewMac};
use sha2::Sha256;
type HmacSha256 = Hmac<Sha256>;

impl Payload {
    pub fn new(user_agent: UserAgent, user_id: UserID, expires_at: ExpirationDate) -> Self {
        Self {
            id: rand::random(),
            user_agent,
            user_id,
            expires_at,
        }
    }

    pub fn sign(&self, key: impl AsRef<[u8]>) -> Signature {
        let mut mac = HmacSha256::new_from_slice(key.as_ref()).unwrap();
        let mut bytes = BytesMut::with_capacity(Payload::SIZE);
        self.encode(&mut bytes);
        mac.update(&bytes);
        let result = mac.finalize();
        Signature::new(result)
    }

    #[inline]
    pub fn verify_user_agent(&self, user_agent: Option<&UserAgent>) -> Result<(), VerifyError> {
        match user_agent {
            Some(user_agent) if self.user_agent != *user_agent => {
                Err(VerifyError::InvalidUserAgent {
                    expected: *user_agent,
                    received: self.user_agent,
                })
            }
            Some(_) => Ok(()),
            None => Ok(()),
        }
    }

    #[inline]
    pub fn verify_expires_at(&self) -> Result<(), VerifyError> {
        if self.expires_at.has_expired() {
            Err(VerifyError::Expired {
                date: self.expires_at.clone(),
            })
        } else {
            Ok(())
        }
    }

    pub fn verify(&self, user_agent: Option<&UserAgent>) -> Result<(), VerifyError> {
        self.verify_user_agent(user_agent)?;
        self.verify_expires_at()?;
        Ok(())
    }
}
