use chrono::{Duration, Utc};
use jwt;

use std::env;
use std::error::Error;

use User;

pub static ENV_JWT_SECRET_KEY: &'static str = "ROOKERIES_JWT_SECRET_KEY";
pub static ENV_JWT_EXPIRATION_DELTA: &'static str = "ROOKERIES_JWT_EXPIRATION_DELTA";
static DEFAULT_EXPIRATION_DELTA: i64 = 300;

#[derive(Debug)]
pub struct JwtToken {
    secret_key: String,
    expiration_delta: i64,
}

#[derive(Serialize, Deserialize)]
pub struct JwtTokenClaims {
    /// Issued at claim.  Becomes the UNIX timestamp of the current time.
    iat: i64,
    /// Expires at timestamp.  Is iat + the configured expiration delta.
    exp: i64,
    /// Not expired before.  Configured to be the same at the issue at timestamp.
    nbf: i64,
    sub: String,
    identity: String,
}

impl JwtToken {
    pub fn configure_from_env() -> Result<JwtToken, Box<Error>> {
        let jwt_secret = env::var(ENV_JWT_SECRET_KEY)?;
        let expiration_delta = match env::var(ENV_JWT_EXPIRATION_DELTA) {
            Ok(t) => t.parse::<i64>()?,
            Err(_) => DEFAULT_EXPIRATION_DELTA,
        };
        Ok(JwtToken {
            secret_key: jwt_secret,
            expiration_delta,
        })
    }

    pub fn encode(&self, claims: &JwtTokenClaims) -> String {
        jwt::encode(&jwt::Header::default(), claims, self.secret_key.as_ref()).unwrap()
    }

    pub fn decode(
        &self,
        token: &str,
    ) -> Result<jwt::TokenData<JwtTokenClaims>, jwt::errors::Error> {
        jwt::decode::<JwtTokenClaims>(
            token,
            self.secret_key.as_ref(),
            &jwt::Validation::new(jwt::Algorithm::HS256),
        )
    }

    pub fn new_claim(&self, user: &User) -> JwtTokenClaims {
        let current_time = Utc::now();
        let iat = current_time.timestamp();

        let expiry = current_time
            .clone()
            .checked_add_signed(Duration::seconds(self.expiration_delta))
            .unwrap()
            .timestamp();

        let not_before = iat.clone();

        JwtTokenClaims {
            iat,
            sub: user.profile.email.clone(),
            exp: expiry,
            nbf: not_before,
            identity: user.username.clone(),
        }
    }
}
