#![allow(unused)]
use actix_files::{Files, NamedFile};
use actix_form_data::{Error, Field};
use actix_identity::Identity;
use actix_identity::{CookieIdentityPolicy, IdentityService};
use actix_multipart::Multipart;
use actix_web::{
    cookie::Cookie, get, http, middleware, post, web, web::Data, web::Form, web::Path as Pathweb,
    App, HttpRequest, HttpResponse, HttpServer, Responder,
};

use lettre::transport::smtp::commands::Mail;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fmt::format;
use std::{fs::*, string};
use std::path::*;
use std::sync::mpsc::Sender;
use tera::{Context, Tera};

use super::models::User;
use diesel::prelude::*;

use walkdir::{DirEntry, WalkDir};
use serde_json::{json, Serializer, Value};

use super::response;
use super::util::*;

use futures_util::TryStreamExt as _;
use std::io::Write;
use tempfile::tempfile;
use uuid::Uuid;

// use oauth2::basic::BasicClient;
// use oauth2::reqwest::async_http_client;
// use oauth2::{
//     AuthUrl, AuthorizationCode, ClientId, ClientSecret, CsrfToken, PkceCodeChallenge, RedirectUrl,
//     Scope, TokenResponse, TokenUrl,
// };
//
// use jsonwebtoken::errors::ErrorKind;
// use jsonwebtoken::{decode, encode, Algorithm, DecodingKey, EncodingKey, Header, Validation};

use jsonwebtoken_google::*;

// #[get("/user_admin")]
// pub async fn user_admin(req: HttpRequest, tmpl: Data<Tera>, id: Identity) -> impl Responder {
//     let result_user = get_user_session(&id);
//     if result_user.is_err() {
//         return response::redirect("/login");
//     }
//     let user_session = result_user.unwrap();
//     if user_session.username != "admin" {
//         return response::redirect("/");
//     }
//     let result = User::find_all();
//     if result.is_err() {
//         return response::redirect("/login");
//     }
//     let users = result.unwrap();
//     let uri = format!(
//         "{}://{}",
//         req.connection_info().scheme(),
//         req.connection_info().host()
//     );
//     let mut context = Context::new();
//     context.insert("user", &user_session);
//     context.insert("users", &users);
//     context.insert("uri", uri.as_str());
//     response::template(tmpl, "user/user_admin.html.twig", &context)
// }
//
// #[get("/add_user")]
// pub async fn add_user(id: Identity) -> impl Responder {
//     let result_user = get_user_session(&id);
//     if result_user.is_err() {
//         return response::redirect("/login");
//     }
//     let user_session = result_user.unwrap();
//     let result = User::create(generate_key());
//     if result.is_err() {
//         return response::redirect("/user_admin");
//     }
//     response::redirect("/user_admin")
// }pl, "user/login.html.twig", &Context::new())

#[derive(Debug, Deserialize)]
pub struct Params {
    message_login: String,
}


#[get("/login")]
pub async fn login(req: HttpRequest, tmpl: Data<Tera>) -> impl Responder {
    let mut context = Context::new();
    let params_result = web::Query::<Params>::from_query(req.query_string());
    if !params_result.is_err() {
        let params = params_result.unwrap();
        context.insert("message_login", &params.message_login);
    }
    response::template(tmpl, "user/login.html.twig", &context)
}
#[derive(Deserialize)]
pub struct LoginForm {
    username: String,
    password: String,
}
#[post("/login")]
pub async fn post_login(
    req: HttpRequest,
    tmpl: Data<Tera>,
    id: Identity,
    form_login: web::Form<LoginForm>,
) -> impl Responder {
    let mut context_err = Context::new();
    context_err.insert("message_login", "Wrong login or password !");
    let mut result = User::find_by_username(&form_login.username);
    if result.is_err() {
        result = User::find_by_email(&form_login.username);
        if result.is_err() {
            return response::template(tmpl, "user/login.html.twig", &context_err);
        }
    }
    let user = result.unwrap();
    let password = sha256::digest(format!("{}{}", user.salt, form_login.password));
    if user.password != password {
        return response::template(tmpl, "user/login.html.twig", &context_err);
    }
    id.remember(user.id.to_string());
    return response::redirect("/");
}

#[derive(Debug, Serialize, Deserialize)]
pub struct TokenClaims {
    pub email: String,
    pub aud: String,
    pub iss: String,
    pub exp: u64,
    pub picture: String,
    pub sub: String,
}

#[derive(Deserialize)]
pub struct LoginGoogleForm {
    credential: String,
    g_csrf_token: String,
}
#[post("/auth")]
pub async fn post_auth(
    req: HttpRequest,
    tmpl: Data<Tera>,
    id: Identity,
    form_login: web::Form<LoginGoogleForm>,
) -> impl Responder {
    let parser = Parser::new(env!("GOOGLE_CLIENT_ID"));
    let claims = parser
        .parse::<TokenClaims>(&form_login.credential)
        .await
        .unwrap();

    let user_result = User::find_by_google(&claims.sub);
    if user_result.is_err() {
        let key = generate_key();
        let result_user = User::create(claims.email.clone(), claims.email.clone(), "".to_string());
        if result_user.is_err() {
            return response::redirect("/login?message_login=L'email du compte google est utilisé mais pas synchronisé");
        }
        let mut user = result_user.unwrap();
        user.username = key.clone();
        user.password = "".to_string();
        user.salt = "".to_string();
        user.google = claims.sub.clone();
        user.update().unwrap();
        return response::redirect(format!("/register_google/{}", key).as_str());
    }
    let user = user_result.unwrap();
    if user.password == "" {
        return response::redirect(format!("/register_google/{}", user.username).as_str());
    }
    id.remember(user.id.to_string());

    response::redirect("/")
}
#[post("/auth/{user_id}")]
pub async fn post_auth_user(
    req: HttpRequest,
    tmpl: Data<Tera>,
    id: Identity,
    form_login: web::Form<LoginGoogleForm>,
    user_id: Pathweb<i32>
) -> impl Responder {
    let user = get_user_session(&id).unwrap();
    if user.id != user_id.clone() {
        return response::redirect("/");
    }
    let parser = Parser::new(env!("GOOGLE_CLIENT_ID"));
    let claims = parser
        .parse::<TokenClaims>(&form_login.credential)
        .await
        .unwrap();

    let mut user = User::find(user_id.clone()).unwrap();
    user.google = claims.sub.clone();
    if user.email_valid != "" {
        user.email = claims.email.clone();
        user.email_valid = "".to_string();
    }
    let user = user.update().unwrap();

    response::redirect(format!("/profile/{}", user_id.clone()).as_str())
}

#[get("/register")]
pub async fn register(tmpl: Data<Tera>) -> impl Responder {
    let mut context = Context::new();
    response::template(tmpl, "user/register.html.twig", &context)
}
#[get("/register_google/{key}")]
pub async fn register_google(tmpl: Data<Tera>, key: Pathweb<String>) -> impl Responder {
    let key_str = key.as_str();
    let result = User::find_by_key(&key_str);
    if result.is_err() {
        return response::redirect("/login");
    }
    let user = result.unwrap();
    let mut context = Context::new();
    context.insert("user_id", &key_str);
    context.insert("registrable_user", &user);
    response::template(tmpl, "user/register_google.html.twig", &context)
}
#[post("/register")]
pub async fn post_register(
    req: HttpRequest,
    tmpl: Data<Tera>,
    id: Identity,
    mut payload: Multipart,
) -> impl Responder {
    let form_data = get_form_data(payload).await;
    let uuid_image = Uuid::new_v4().to_string();
    let final_path_local = Path::new("./static/sourcefiles").join(&uuid_image);
    let final_path = Path::new("/static/sourcefiles").join(&uuid_image);

    if form_data["password"].value != form_data["repassword"].value {
        let mut context = Context::new();
        context.insert("message_register", "Les mots de passe sont différents");
        return response::template(tmpl, "user/register.html.twig", &context);
    }
    let mut user_result = User::create(form_data["username"].value.clone(), form_data["email"].value.clone(), form_data["password"].value.clone());
    if user_result.is_err() {
        print!("user error");
        let mut context = Context::new();
        context.insert("message_register", &user_result.err());
        return response::template(tmpl, "user/register.html.twig", &context);
    }
    let mut user = user_result.unwrap();
    if Path::new(&form_data["image"].path.clone()).exists() {
        form_data["image"].clone().save_file(&final_path_local);
        user.image = "/static/img/default_user.svg".to_string();
        if final_path_local.exists() {
            user.image = final_path
                .to_str()
                .expect("final_path convertion str err")
                .to_string();
        }
    }

    let mut context = Context::new();
    context.insert("user", &user);
    context.insert("uri", &format!("{}://{}", req.connection_info().scheme(), req.connection_info().host()).to_string());
    envoye_un_mail(&tmpl, "mail/welcome.html.twig", &context, "Bienvenue sur DrWhy", form_data["email"].value.clone().as_str());
    let result = user.update();
    if !result.is_err() {
        let mut user = result.unwrap();
        id.remember(user.id.to_string());
        return response::redirect("/");
    }
    let mut context = Context::new();
    context.insert("message_register", "Erreur incconue");
    response::template(tmpl, "user/register.html.twig", &context)
}
#[post("/register_google/{key}")]
pub async fn post_register_google(
    tmpl: Data<Tera>,
    id: Identity,
    key: Pathweb<String>,
    mut payload: Multipart,
) -> impl Responder {
    let form_data = get_form_data(payload).await;
    let key_str = key.as_str();
    let result = User::find_by_key(key_str);
    if !result.is_err() {
        let mut user = result.unwrap();
        let uuid_image = Uuid::new_v4().to_string();
        let final_path_local = Path::new("./static/sourcefiles").join(&uuid_image);
        let final_path = Path::new("/static/sourcefiles").join(&uuid_image);

        if form_data["password"].value != form_data["repassword"].value {
            let mut context = Context::new();
            context.insert("user_id", &key_str);
            context.insert("message_register", "Les mots de passe sont différents");
            context.insert("registrable_user", &user);
            return response::template(tmpl, "user/register_google.html.twig", &context);
        }
        user.username = form_data["username"].value.clone();
        user.generate_salt();
        user.password = sha256::digest(format!("{}{}", &user.salt, &form_data["password"].value));
        let result = user.update();
        if !result.is_err() {
            let mut user = result.unwrap();
            id.remember(user.id.to_string());
            return response::redirect("/");
        }
    }
    let mut context = Context::new();
    context.insert("user_id", &key_str);
    context.insert("message", "Erreur inconnue");
    response::template(tmpl, "user/register_google.html.twig", &context)
}

#[get("/logout")]
pub async fn logout(req: HttpRequest, id: Identity) -> impl Responder {
    let redir = req
        .headers()
        .get(http::header::REFERER)
        .unwrap()
        .to_str()
        .unwrap();
    id.forget(); // <- remove identity
    response::redirect(redir)
}


#[get("/user/validate/{uuid}")]
pub async fn user_validate(req: HttpRequest, id: Identity, uuid: Pathweb<String>) -> impl Responder {
    let mut user = User::find_by_email_valid(uuid.into_inner().as_str()).unwrap();
    user.email_valid = "".to_string();
    user.update();
    response::redirect("/")
}


#[get("/forgotpassword")]
pub async fn forgotpassword(tmpl: Data<Tera>) -> impl Responder {
    response::template(tmpl, "user/forgotpassword.html.twig", &Context::new())
}

#[post("/forgotpassword")]
pub async fn post_forgotpassword(
    req: HttpRequest,
    tmpl: Data<Tera>,
    mut payload: Multipart,
) -> impl Responder {
    let form_data = get_form_data(payload).await;
    let mut context_mail = Context::new();
    let email_to = form_data["email"].value.clone();
    let user_result = User::find_by_email(&email_to);
    if user_result.is_err() {
        let mut context_page = Context::new();
        context_page.insert("message_mail_nok", "L'adresse Email n'existe pas");
        return response::template(tmpl, "user/forgotpassword.html.twig", &context_page);
    }
    let mut user = user_result.unwrap();
    user.password = Uuid::new_v4().to_string();
    user.salt = String::new();
    context_mail.insert("user", &user);
    user.update().unwrap();
    context_mail.insert("uri", &format!("{}://{}", req.connection_info().scheme(), req.connection_info().host()).to_string());
    envoye_un_mail(&tmpl, "mail/forgot_password.html.twig", &context_mail, "Mot de passe oublié", &email_to);
    let mut context_page = Context::new();
    context_page.insert("message_password", "Un mail vous a été envoyé");
    response::template(tmpl, "user/forgotpassword.html.twig", &context_page)
}

#[get("/change_password/{user_changepass}")]
pub async fn change_password(tmpl: Data<Tera>, user_changepass: Pathweb<String>) -> impl Responder {
    let result_user = User::find_by_changepass(&user_changepass);
    if result_user.is_err() {
        return response::just_404(tmpl);
    }
    let user_session = result_user.unwrap();
    if user_session.password != user_changepass.into_inner() {}
    let mut context = Context::new();
    context.insert("user", &user_session);
    response::template(tmpl, "user/change_password.html.twig", &context)
}

#[post("/change_password/{user_changepass}")]
pub async fn post_change_password(
    tmpl: Data<Tera>,
    mut payload: Multipart,
    user_changepass: Pathweb<String>
) -> impl Responder {
    let mut user = User::find_by_changepass(&user_changepass).unwrap();
    let form_data = get_form_data(payload).await;
    if form_data["password"].value != form_data["repassword"].value {
        let mut context_error = Context::new();
        user.password = user_changepass.into_inner();
        context_error.insert("user", &user);
        context_error.insert("message_error", "Les mots de passe sont différents");
        return response::template(tmpl, "user/change_password.html.twig", &context_error);
    }
    user.generate_salt();
    user.password = sha256::digest(format!(
        "{}{}",
        &user.salt, &form_data["password"].value
    ));
    user = user.update().unwrap();
    response::redirect("/login?message_login=Mot de passe modifié")
}

#[get("/profile/{user_id}")]
async fn profile(tmpl: Data<Tera>, user_id: Pathweb<i32>, id: Identity) -> impl Responder {
    let result_user = get_user_session(&id);
    if result_user.is_err() {
        return response::redirect("/login");
    }
    let user_session = result_user.unwrap();
    if user_session.id != user_id.into_inner() {}
    let mut context = Context::new();
    context.insert("user", &user_session);
    response::template(tmpl, "user/profile.html.twig", &context)
}

#[post("/profile/{user_id}")]
async fn profile_post(
    req: HttpRequest,
    tmpl: Data<Tera>,
    user_id: Pathweb<i32>,
    id: Identity,
    mut payload: Multipart,
) -> impl Responder {
    let result_user = get_user_session(&id);
    if result_user.is_err() {
        return response::redirect("/login");
    }
    let mut user_session = result_user.unwrap();
    let form_data = get_form_data(payload).await;
    let user_id = user_id.into_inner();
    if user_session.id == user_id {
        let uuid_image = Uuid::new_v4().to_string();
        let final_path_local = Path::new("./static/sourcefiles").join(&uuid_image);
        let final_path = Path::new("/static/sourcefiles").join(&uuid_image);
        form_data["image"].clone().save_file(&final_path_local);
        if final_path_local.exists() {
            user_session.image = final_path
                .to_str()
                .expect("final_path convertion str err")
                .to_string();
        }

        if form_data["password"].value != "" {
            if form_data["password"].value != form_data["repassword"].value {
                let mut context = Context::new();
                context.insert("user", &user_session);
                context.insert("message_register", "Les mots de passe sont différents");
                return response::template(tmpl, "user/profile.html.twig", &context);
            }
            user_session.generate_salt();
            user_session.password = sha256::digest(format!(
                "{}{}",
                &user_session.salt, &form_data["password"].value
            ));
        }
        user_session.username = form_data["username"].value.clone();
        user_session.email = form_data["email"].value.clone();
        user_session = user_session.update().unwrap();
    }
    let mut context = Context::new();
    context.insert("message_profile","Votre profil a été mis à jour");
    context.insert("user", &user_session);
    response::template(tmpl, "user/profile.html.twig", &context)
}

#[get("/delete/{user_id}")]
async fn delete(
    req: HttpRequest,
    tmpl: Data<Tera>,
    id: Identity,
    user_id: Pathweb<i32>,
    mut payload: Multipart,
) -> impl Responder {
    let redir = req
        .headers()
        .get(http::header::REFERER)
        .unwrap()
        .to_str()
        .unwrap();
    let result_user_test = get_user_session(&id);
    if result_user_test.is_err() {
        return response::redirect(redir);
    }
    
    let result_user = User::find(user_id.into_inner());
    if result_user.is_err() {
        return response::redirect(redir);
    }
    let user = result_user.unwrap();
    user.delete().unwrap();
    return response::redirect("/user_deleted");
}

#[get("/user_deleted")]
pub async fn user_deleted(tmpl: Data<Tera>) -> impl Responder {
    let mut context = Context::new();
    response::template(tmpl, "user/user_deleted.html.twig", &context)
}