use std::future::{Ready, ready};

use actix_web::{FromRequest, http, HttpRequest, dev::Payload, web::Data};
use chrono::Utc;
use jsonwebtoken::{encode, Header, EncodingKey, decode, DecodingKey, Validation};
use serde::{Serialize, Deserialize};
use uuid::Uuid;

use crate::{schema::user::{User, get_user}, DbPool};

use super::error::ApiError;

#[derive(Debug, Serialize, Deserialize)]
pub struct Claims {
    exp: i64,
    uid: Uuid
}

fn get_claims(req: &HttpRequest) -> Result<Claims, ApiError>  {
    let token = req.headers()
        .get(http::header::AUTHORIZATION)
        .map(|h| h.to_str().unwrap().split_at(7).1.to_string());

    let token = token.ok_or(ApiError{
        status: 400,
        message: "JWT-token was not specified in the Authorization header as Bearer: token".to_string()
    })?;

    let secret = "secret";
    Ok(decode::<Claims>(token.as_str(), &DecodingKey::from_secret(secret.as_bytes()), &Validation::default())?.claims)
}

pub fn get_token(user: &User) -> Result<String, ApiError> {
    let claims = Claims {
        exp: Utc::now().timestamp() + chrono::Duration::days(7).num_seconds(),
        uid: user.id,
    };

    let secret = "secret";
    Ok(encode(
        &Header::default(),
        &claims,
        &EncodingKey::from_secret(secret.as_bytes()))?)
}

impl FromRequest for User {
    type Error = ApiError;
    type Future = Ready<Result<Self, Self::Error>>;

    fn from_request(req: &HttpRequest, _payload: &mut Payload) -> Self::Future {
        ready(
            (|| -> Result<User, ApiError>{
                let pool = req.app_data::<Data<DbPool>>().expect("No database configured");
                let mut conn = pool.get().expect("couldn't get db connection from pool");
                let uid = get_claims(req)?.uid;
                
                Ok(get_user(&mut conn, uid)?)
            })()
        )
    }
}