use std::ops::Add;
use std::time::{Duration, SystemTime};

use jsonwebtoken::{
    Algorithm, DecodingKey, EncodingKey, Header, TokenData, Validation, decode, encode,
    errors::Error,
};

mod types;
use serde::Serialize;
use time::OffsetDateTime;
pub use types::AuthTokens;

pub fn _peek_token_data<T: serde::de::DeserializeOwned>(
    token: &str,
) -> Result<TokenData<T>, Error> {
    jsonwebtoken::dangerous::insecure_decode::<T>(token)
}

pub fn verify_token<T: serde::de::DeserializeOwned>(
    token: &str,
    key: String,
    audience: Option<String>,
) -> Result<TokenData<T>, Error> {
    let mut validation = Validation::new(Algorithm::HS256);

    match audience {
        Some(aud) => {
            validation.set_audience(&[aud]);
        }
        None => {
            validation.validate_aud = false;
        }
    }

    let secret = DecodingKey::from_secret(key.as_ref());
    decode::<T>(token, &secret, &validation)
}

pub fn generate_expiry_time(seconds: u64) -> usize {
    let duration = Duration::from_secs(seconds);
    let expiry_time = SystemTime::now().add(duration);
    OffsetDateTime::from(expiry_time).unix_timestamp() as usize
}
pub fn generate_tokens<AccessTokenData: Serialize, RefreshTokenData: Serialize>(
    access_token_data: AccessTokenData,
    refresh_token_data: RefreshTokenData,
    access_secret: &String,
    refresh_secret: &String,
) -> Result<types::AuthTokens, Error> {
    let access_token = generate_token(access_token_data, access_secret)?;
    let refresh_token = generate_token(refresh_token_data, refresh_secret)?;

    Ok(AuthTokens {
        access_token,
        refresh_token,
    })
}

pub fn generate_token<TokenData: Serialize>(
    data: TokenData,
    secret: &String,
) -> Result<String, Error> {
    encode_token(&data, secret)
}

fn encode_token<T: Serialize>(data: &T, secret: &String) -> Result<String, Error> {
    encode(
        &Header::default(),
        data,
        &EncodingKey::from_secret(secret.as_ref()),
    )
}
