use anyhow::Result;
use base64::{Engine, prelude::BASE64_STANDARD};
use jiff::Zoned;
use neon_rs::crypto::aes::{aes_decrypt_cbc, aes_encrypt_cbc};
use serde::{Deserialize, Serialize};
use std::fmt;
use std::fmt::Display;

use crate::core::config::config;

pub const EXPIRE_SECONDS: i64 = 86400;

pub enum Role {
    Super,
    Normal,
}

#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct Identity {
    i: u64,
    r: i8,
    t: String,
    x: i64,
}

impl Identity {
    pub fn new(id: u64, role: i8, token: String) -> Self {
        Identity {
            i: id,
            r: role,
            t: token,
            x: Zoned::now().timestamp().as_second() + EXPIRE_SECONDS,
        }
    }

    pub fn from_auth_token(token: String) -> Self {
        if token.is_empty() {
            return Identity::default();
        }
        let cipher = match BASE64_STANDARD.decode(token) {
            Err(e) => {
                tracing::error!(err = ?e, "invalid auth_token");
                return Identity::default();
            }
            Ok(v) => v,
        };
        let secret = match config().get_string("app.secret") {
            Err(e) => {
                tracing::error!(err = ?e, "missing config(app.secret)");
                return Identity::default();
            }
            Ok(v) => v,
        };
        let key = secret.as_bytes();

        let plain = match aes_decrypt_cbc(key, &key[..16], &cipher) {
            Err(e) => {
                tracing::error!(err = ?e, "invalid auth_token");
                return Identity::default();
            }
            Ok(v) => v,
        };

        serde_json::from_slice::<Identity>(&plain).unwrap_or_else(|e| {
            tracing::error!(err = ?e, "invalid auth_token");
            Identity::default()
        })
    }

    pub fn to_auth_token(&self) -> Result<String> {
        let plain = serde_json::to_vec(self)?;

        let secret = config().get_string("app.secret")?;
        let key = secret.as_bytes();

        let cipher = aes_encrypt_cbc(key, &key[..16], &plain, None)?;

        Ok(BASE64_STANDARD.encode(cipher))
    }

    pub fn id(&self) -> u64 {
        self.i
    }

    pub fn match_token(&self, token: String) -> bool {
        self.t == token
    }

    pub fn is_expired(&self) -> bool {
        self.x <= Zoned::now().timestamp().as_second()
    }

    pub fn is_role(&self, role: Role) -> bool {
        match role {
            Role::Normal => self.r == 1,
            Role::Super => self.r == 2,
        }
    }
}

impl Display for Identity {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        if self.i == 0 {
            return write!(f, "<none>");
        }
        if self.r == 0 {
            return write!(f, "id:{}|token:{}", self.i, self.t);
        }
        write!(f, "id:{}|role:{}|token:{}", self.i, self.r, self.t)
    }
}
