Documentation
use actix_http::header::HeaderMap;
use actix_web::HttpRequest;
use anyhow;
use jsonwebtoken::{decode, encode, Algorithm, DecodingKey, EncodingKey, Header, Validation};
use serde::{Deserialize, Serialize};

#[async_trait::async_trait]
pub trait TokenPermission {
    async fn check_and_verify(&self, req: (HeaderMap, String)) -> anyhow::Result<AccessToken>;
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct AccessToken {
    pub user_id: u64,
    pub user_name: String,
    pub user_account: String,
    pub app_id: String,
    pub exp: u128,
}

impl AccessToken {
    pub fn encode_token(
        user_id: u64,
        user_account: &String,
        user_name: &String,
        app_id: &String,
        timeout_hour: u16,
        secret: &str,
    ) -> anyhow::Result<String> {
        let header = Header::new(Algorithm::HS512);
        let my_claims = AccessToken {
            user_id: user_id.clone(),
            user_account: user_account.clone(),
            user_name: user_name.clone(),
            app_id: app_id.clone(),
            exp: (std::time::SystemTime::now()
                .duration_since(std::time::UNIX_EPOCH)
                .unwrap()
                .as_millis()
                + 1000 * 60 * 60 * (timeout_hour as u128)),
        };
        if let Ok(token) = encode(
            &header,
            &my_claims,
            &EncodingKey::from_secret(secret.as_ref()),
        ) {
            return anyhow::Ok(token);
        } else {
            return Err(anyhow::anyhow!("encode_token with error"));
        }
    }

    pub fn decode_token(token: &String, secret: &str) -> anyhow::Result<Self> {
        if let Ok(decode_data) = decode::<Self>(
            &token,
            &DecodingKey::from_secret(secret.as_ref()),
            &Validation::new(Algorithm::HS512),
        ) {
            return anyhow::Ok(decode_data.claims);
        } else {
            return Err(anyhow::anyhow!("decode_token with error"));
        }
    }

    pub fn is_expired(&self) -> bool {
        let cur_time = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .unwrap()
            .as_millis();
        if self.exp < cur_time {
            true
        } else {
            false
        }
    }
}

pub const ACCESS_TOKEN_TIME: u16 = 2 * 24;
pub const REFRESH_TOKEN_TIME: u16 = 7 * 24;

pub fn create_access_token(
    user_id: u64,
    user_account: &String,
    user_name: &String,
    app_id: &String,
    secret: &str,
) -> anyhow::Result<String> {
    AccessToken::encode_token(
        user_id,
        user_account,
        user_name,
        app_id,
        ACCESS_TOKEN_TIME,
        secret,
    )
}

pub fn create_refresh_token(
    user_id: u64,
    user_account: &String,
    user_name: &String,
    app_id: &String,
    secret: &str,
) -> anyhow::Result<String> {
    AccessToken::encode_token(
        user_id,
        user_account,
        user_name,
        app_id,
        REFRESH_TOKEN_TIME,
        secret,
    )
}

pub fn get_token_and_path(req: &HttpRequest) -> (HeaderMap, String) {
    let headers = req.headers();
    let reqpath = format!("{} {}", req.method(), req.path());
    (headers.clone(), reqpath)
}