use actix_session::{
    config::PersistentSession, storage::RedisActorSessionStore, SessionMiddleware,
};
use actix_web::{
    cookie::time::Duration,
    middleware,
    web::{self, Data},
    App, HttpRequest, HttpResponse, HttpServer, Responder,
};
use diesel_async::pooled_connection::AsyncDieselConnectionManager;
use diesel_async::AsyncPgConnection;
use diesel_async::{pooled_connection::bb8, RunQueryDsl};
use env_logger;
use keys::{add_key, is_key_exist};
use models::users;
use rand::Rng;
use serde::{Deserialize, Serialize};
use std::io;

use crate::models::users::get_user_with_email;

mod db;
mod keys;
mod middlewares;
mod models;
type Pool = bb8::Pool<AsyncDieselConnectionManager<AsyncPgConnection>>;
const EXPIRES: i64 = 60 * 60 * 24 * 365;
#[actix_web::main]
async fn main() -> io::Result<()> {
    env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
    // std::env::set_var("RUST_LOG", "debug");
    // tracing_subscriber::fmt::init();
    // get env vars
    dotenvy::dotenv().ok();
    log::info!("starting HTTP server at http://localhost:8080");
    let db_url = std::env::var("DATABASE_URL").expect("DATABASE_URL is not set in .env file");
    let redis = redis::Client::open("redis://127.0.0.1:6379").unwrap();
    let private_key = actix_web::cookie::Key::generate();
    let config = AsyncDieselConnectionManager::<diesel_async::AsyncPgConnection>::new(db_url);
    let pool = bb8::Pool::builder().build(config).await.unwrap();
    HttpServer::new(move || {
        App::new()
            .app_data(web::Data::new(redis.clone()))
            .wrap(middleware::Logger::default())
            .app_data(web::Data::new(pool.clone()))
            .wrap(
                SessionMiddleware::builder(
                    RedisActorSessionStore::new("127.0.0.1:6379"),
                    private_key.clone(),
                )
                .cookie_same_site(actix_web::cookie::SameSite::Lax)
                .cookie_name("name".to_string())
                .cookie_secure(false)
                .cookie_http_only(false)
                .cookie_domain(Some("libredu.org".to_string()))
                .session_lifecycle(actix_session::config::SessionLifecycle::PersistentSession(
                    PersistentSession::default().session_ttl(Duration::seconds(EXPIRES)),
                ))
                // .execute()
                .build(),
            )
            .service(
                web::resource("/get_key/{id}")
                    .wrap(middlewares::CheckLogin)
                    .route(web::get().to(get_key)),
            )
            .service(
                web::resource("/confirm_key/{key}")
                    // .wrap(middlewares::CheckLogin)
                    .route(web::get().to(confirm_key)),
            )
            .service(web::resource("/").route(web::post().to(unlock)))
            .service(
                web::resource("/schools")
                    .wrap(middlewares::CheckLogin)
                    .route(web::get().to(schools)),
            )
            .service(web::resource("/login").route(web::post().to(login)))
    })
    //.workers(2)
    .bind(("127.0.0.1", 8082))?
    .run()
    .await
}
async fn get_key(
    con: Data<redis::Client>,
    s: actix_session::Session,
    s_id: web::Path<i32>,
) -> actix_web::Result<impl Responder> {
    loop {
        let key = rand::thread_rng().gen_range(100000..999999);
        let key = format!("{}:{}", s_id, key);
        if !is_key_exist(con.clone(), &key).await {
            add_key(con.clone(), &key).await;
            return Ok(HttpResponse::Ok().body(key.to_string()));
        }
    }
}

async fn confirm_key(
    con: Data<redis::Client>,
    _s: actix_session::Session,
    key: web::Path<String>,
) -> actix_web::Result<impl Responder> {
    if !is_key_exist(con.clone(), &key.into_inner()).await {
        return Ok(HttpResponse::Ok().finish());
    }
    return Ok(HttpResponse::BadRequest().finish());
}
async fn unlock(
    con: Data<redis::Client>,
    password: web::Form<String>,
) -> actix_web::Result<impl Responder> {
    if is_key_exist(con.clone(), &password).await {
        return Ok(HttpResponse::Ok().body(""));
    }
    return Ok(HttpResponse::BadRequest().into());
}
async fn login(
    _form: web::Json<LoginForm>,
    c: web::Data<bb8::Pool<AsyncPgConnection>>, // c: web::Data<Pool>,
    session: actix_session::Session,
) -> actix_web::Result<impl Responder> {
    // use diesel_async::prelude::*;
    let user = get_user_with_email(&_form.email, c).await;
    session.insert("user", user.clone()).unwrap();
    let u = session.get::<crate::users::User>("user");
    println!("{:?}", u);
    return Ok(HttpResponse::Ok().json(user));
}
async fn schools(
    c: web::Data<bb8::Pool<AsyncPgConnection>>, // c: web::Data<Pool>,
    session: actix_session::Session,
) -> actix_web::Result<impl Responder> {
    // use diesel_async::prelude::*;
    use crate::users::*;
    let user = session.get::<User>("user").unwrap().unwrap();
    let schs = get_schools(user.id, c).await;
    println!("schools = {schs:?}");
    return Ok(HttpResponse::Ok().json(schs));
}

#[derive(Clone, Deserialize, Serialize)]
struct LoginForm {
    email: String,
    password: String,
}