use std::{pin::Pin, time::SystemTime};

use actix_web::{error::ErrorUnauthorized, http::header, web, Error, FromRequest, HttpRequest};
use futures::Future;
use jsonwebtoken::{DecodingKey, EncodingKey, Header};
use sea_orm::EntityTrait;
use serde::{Deserialize, Serialize};
use uuid::Uuid;

use crate::AppState;

#[derive(Deserialize, Serialize)]
struct Claims {
    sub: Uuid,
    name: String,
    exp: u64,
}

pub fn create_jwt(
    user: entity::user::Model,
    key: &EncodingKey,
) -> Result<String, jsonwebtoken::errors::Error> {
    let time = SystemTime::now()
        .duration_since(SystemTime::UNIX_EPOCH)
        .expect("SystemTime before UNIX EPOCH")
        .as_secs()
        + 86400;
    let claims = Claims {
        sub: user.id,
        name: user.name,
        exp: time,
    };
    jsonwebtoken::encode(&Header::default(), &claims, key)
}

pub struct AuthedUser(entity::user::Model);

impl AuthedUser {
    fn parse_token(req: &HttpRequest) -> Result<Claims, Error> {
        let header = req
            .headers()
            .get(header::AUTHORIZATION)
            .ok_or(ErrorUnauthorized("Unauthorized"))?;

        if header.len() < 8 {
            return Err(ErrorUnauthorized("Unauthorized"));
        }

        let mut parts = header
            .to_str()
            .map_err(|_| ErrorUnauthorized("Unauthorized"))?
            .splitn(2, ' ');

        match parts.next() {
            Some("Bearer") => {}
            _ => return Err(ErrorUnauthorized("Unauthorized")),
        }

        let token = parts.next().ok_or(ErrorUnauthorized("Unauthorized"))?;

        let key = &req.app_data::<web::Data<AppState>>().unwrap().secret;

        let validation = jsonwebtoken::Validation::new(jsonwebtoken::Algorithm::HS256);
        let claims = jsonwebtoken::decode::<Claims>(
            token,
            &DecodingKey::from_secret(key.as_bytes()),
            &validation,
        )
        .map_err(|e| ErrorUnauthorized(e))?;
        Ok(claims.claims)
    }
}

impl FromRequest for AuthedUser {
    type Error = actix_web::Error;

    type Future = Pin<Box<dyn Future<Output = Result<Self, Self::Error>>>>;

    fn from_request(
        req: &actix_web::HttpRequest,
        _payload: &mut actix_web::dev::Payload,
    ) -> Self::Future {
        let res = AuthedUser::parse_token(req);
        let state = req.app_data::<web::Data<AppState>>().unwrap().db.clone();
        Box::pin(async move {
            let claims = res?;
            let id = claims.sub;

            let user = entity::prelude::User::find_by_id(id)
                .one(&state)
                .await
                .map_err(|_| ErrorUnauthorized("Unauthorized"))?
                .ok_or(ErrorUnauthorized("Unauthorized"))?;

            Ok(AuthedUser(user))
        })
    }
}
