use std::{future::{ready, Ready, Future}, pin::Pin};
use actix_identity::Identity;
use actix_web::{FromRequest, Error as ActixError, HttpRequest, dev::Payload, error::{ErrorUnauthorized, ErrorInternalServerError}};
use argon2::{password_hash::{rand_core::OsRng, PasswordHasher, PasswordVerifier, SaltString, Error as HashError}, Argon2, PasswordHash};
use diesel::prelude::*;
use serde::{Serialize, Deserialize};
use siren::ServiceError;

use crate::db::schema::users;

#[derive(Debug, Serialize, Deserialize)]
pub struct RegisterUser {
  pub email: String,
  pub password: String,
  pub first_name: String,
  pub last_name: String,
}

impl RegisterUser {
  pub fn convert_to_insert(self) -> Result<InsertUser, ServiceError> {
    let hash = hash(self.password.as_bytes())?;
    Ok(InsertUser {
      email: self.email,
      hash,
      role: "user".to_string(),
      first_name: self.first_name,
      last_name: self.last_name,
    })
  }
}

#[derive(Debug, Serialize, Deserialize)]
pub struct LoginAuth {
  pub email: String,
  pub password: String,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct LoggedUser {
  pub email: String
}

impl FromRequest for LoggedUser {
  type Error = ActixError;
  // type Future = Ready<Result<LoggedUser, ActixError>>;
  // type Future = std::pin::Pin<Box<dyn std::future::Future<Output = Result<LoggedUser, ActixError>>>>;
  type Future = Pin<Box<dyn Future<Output = Result<Self, Self::Error>>>>;

  fn from_request(req: &HttpRequest, pl: &mut Payload) -> Self::Future {
    // if let Ok(identity) = Identity::from_request(req, pl).into_inner() {
    //   if let Ok(user_json) = identity.id() {
    //     if let Ok(user) = serde_json::from_str(&user_json) {
    //       return ready(Ok(user));
    //     }
    //   }
    // }
    // std::future::ready(Err(
    //   ActixError::from(ServiceError {
    //     status: 401,
    //     message: "Unauthorized".to_string(),
    //   })
    // ))
    let identity = Identity::extract(req).into_inner();
    Box::pin(async move {
      process_req_auth_data(identity).await
    })
  }
}

async fn process_req_auth_data(identity: Result<Identity, ActixError>) -> Result<LoggedUser, ActixError> {
  let id = identity
    .map_err(|_| ErrorUnauthorized("You are not logged in; 1"))?
    .id()
    .map_err(|_| ErrorUnauthorized("You are not logged in; 3"))?;
  let logged_user = match serde_json::from_str::<LoggedUser>(&id) {
    Ok(user) => user,
    Err(err) => return Err(ErrorInternalServerError(err))
  };

  let user = QueryUser::get_by_email(&logged_user.email)
      .map_err(|_| ErrorUnauthorized("You are not logged in; 3"))?;

  Ok(LoggedUser { email: user.email })
}

#[derive(Debug, Queryable, QueryableByName, Serialize, Deserialize)]
#[diesel(table_name = users)]
pub struct QueryUser {
  pub email: String,
  pub hash: String,
  pub role: String,
  pub first_name: String,
  pub last_name: String,
}

impl QueryUser {
  pub fn get_by_email(email: &str) -> Result<QueryUser, ServiceError> {
    let mut conn = crate::db::connection()?;
    let user = users::table
      .filter(users::email.eq(email))
      .first(&mut conn)?;
    Ok(user)
  }
}

#[derive(Debug, Insertable, AsChangeset, Serialize, Deserialize)]
#[diesel(table_name = users)]
pub struct InsertUser {
  pub email: String,
  pub hash: String,
  pub role: String,
  pub first_name: String,
  pub last_name: String,
}

impl InsertUser {
  pub fn insert(user: Self) -> Result<QueryUser, ServiceError> {
    let mut conn = crate::db::connection()?;
    let user = diesel::insert_into(users::table)
      .values(user)
      .get_result(&mut conn)?;
    Ok(user)
  }
}

// https://github.com/Sirneij/rust-auth/blob/main/backend/src/routes/users/login.rs
// https://dev.to/sirneij/authentication-system-using-rust-actix-web-and-sveltekit-user-registration-580h
// https://github.com/actix/actix-extras/blob/master/actix-session/examples/basic.rs
// maybe https://github.com/actix/actix-extras/blob/master/actix-identity/examples/identity.rs
// https://www.lpalmieri.com/posts/session-based-authentication-in-rust/#3-3-1-postgres

// pub async fn validator(req: ServiceRequest, credentials: BearerAuth) -> Result<ServiceRequest, (ActixError, ServiceRequest)> {
//   let token = credentials.token();
//   println!("{:?}", req);
//   match validate_token(token) {
//     Ok(res) => {
//       if res {
//         Ok(req)
//       } else {
//         Err((ActixError::from(actix_web::error::ErrorUnauthorized("Invalid token")), req))
//       }
//     },
//     Err(err) => {
//       Err((ActixError::from(actix_web::error::ErrorUnauthorized(err)), req))
//     }
//   }
// }

// fn validate_token(token: &str) -> Result<bool, ServiceError> {
//   println!("Validating token: {}", token);
//   Ok(true)
// }

pub fn hash(password: &[u8]) -> Result<String, HashError> {
  let salt = SaltString::generate(&mut OsRng);
  Ok(Argon2::default().hash_password(password, &salt)?.to_string())
}

pub fn verify(hash: &str, password: &[u8]) -> Result<(), HashError> {
  let parsed_hash = PasswordHash::new(hash)?;
  Ok(Argon2::default().verify_password(password, &parsed_hash)?)
}
