use actix_web::http::header;
use actix_web::{web, HttpRequest, HttpResponse};

use crate::pages::templates::RESET_GAME_PWD;
use crate::pages::{insert_security_headers, request_to_jar, CommonAuthInfo};
use crate::{DataBaseRo, DataBaseRw, WebData};

use uuid::Uuid;

#[derive(serde_derive::Serialize)]
struct PageData<'a> {
    common_auth_info: CommonAuthInfo<'a>,
    token: Uuid,
    csrf: Uuid,
    last_error: Option<&'a str>,
    is_game_pwd: bool,
}

#[derive(serde_derive::Deserialize)]
pub struct FormData {
    login: Option<String>,
    password: String,
    password_copy: String,
    token: Option<Uuid>,
    csrf: Uuid,
}

#[actix_web::get("/reset-game-pwd-{token}.html")]
pub async fn get_reset_game_pwd(
    request: HttpRequest,
    token: web::Path<String>,
    data: web::Data<WebData<'_>>,
    data_ro: web::Data<DataBaseRo>,
) -> HttpResponse {
    let jar = request_to_jar(request);

    let user = jar
        .private(&data.cookies_key)
        .get("auth")
        .map(|x| x.value().to_string());

    let token = match Uuid::parse_str(&token.into_inner()) {
        Ok(t) => t,
        Err(e) => {
            log::warn!("Incorrect token: {}", e);
            return HttpResponse::NotFound().body("Not found");
        }
    };
    let dbclient = match data_ro.0.get().await {
        Ok(c) => c,
        Err(e) => {
            log::error!("Pool RO error {}", e);
            return HttpResponse::ServiceUnavailable().body(actix_web::body::None::new());
        }
    };
    let stmt = match dbclient.prepare("select player_name, last_error, is_game_pwd from auth.reset_tokens where token = $1 and NOW() < create_ts + interval '1 day' limit 1;").await {
        Ok(stmt) => stmt,
        Err(e) => {
            log::error!("Pool RO statement error {}", e);
            return HttpResponse::ServiceUnavailable().body(actix_web::body::None::new());
        }
    };
    let rows = match dbclient.query_opt(&stmt, &[&token]).await {
        Ok(rows) => rows,
        Err(e) => {
            log::error!("Pool RO query error {}", e);
            return HttpResponse::ServiceUnavailable().body(actix_web::body::None::new());
        }
    };
    let row = match rows {
        Some(row) => row,
        None => {
            return HttpResponse::NotFound().body("Not found");
        }
    };

    let player_name = row.get::<_, &str>(0);
    let last_error = row.get::<_, Option<&str>>(1);
    let is_game_pwd = row.get::<_, bool>(2);
    let csrf = Uuid::new_v4();

    {
        let mut cache = data.cache_reset_game_pwd.lock().await;
        cache.insert(
            csrf,
            (token, player_name.to_string(), is_game_pwd),
            std::time::Duration::from_secs(data.cache_duration_sec),
        );
    }

    let body = match data.handlebars.render(
        RESET_GAME_PWD,
        &PageData {
            common_auth_info: CommonAuthInfo {
                user: user.map(Into::into),
            },
            token,
            csrf,
            last_error,
            is_game_pwd,
        },
    ) {
        Ok(b) => b,
        Err(e) => {
            log::error!("Render reset-game-pwd error: {}", e);
            return HttpResponse::ServiceUnavailable().body(actix_web::body::None::new());
        }
    };
    insert_security_headers(HttpResponse::Ok()).body(body)
}

#[actix_web::post("/reset-game-pwd.do")]
pub async fn post_reset_game_pwd(
    request: HttpRequest,
    form: web::Form<FormData>,
    data: web::Data<WebData<'_>>,
    data_rw: web::Data<DataBaseRw>,
) -> HttpResponse {
    let cached_data = {
        let mut cache = data.cache_reset_game_pwd.lock().await;
        cache.remove(&form.csrf)
    };
    let (cached_token, cached_login, is_game_pwd) = match cached_data {
        Some(d) => d,
        None => {
            log::warn!("Unknown data for CSRF: {}", form.csrf);
            return HttpResponse::BadRequest().body("Incorrect");
        }
    };
    if cached_token.is_nil() {
        if form.token.is_some() {
            log::warn!("Expected nil token for CSRF: {}", form.csrf);
            return HttpResponse::BadRequest().body("Incorrect");
        }
    } else if form.token != Some(cached_token) {
        log::warn!("Mismatch token for CSRF: {}", form.csrf);
        return HttpResponse::BadRequest().body("Incorrect");
    }

    let jar = request_to_jar(request);

    let form_login = jar
        .private(&data.cookies_key)
        .get("auth")
        .map(|x| std::borrow::Cow::Owned(x.value().to_string()))
        .or(form.login.as_ref().map(std::borrow::Cow::Borrowed))
        .map(|x| x.to_ascii_lowercase());

    let dbclient = match data_rw.0.get().await {
        Ok(c) => c,
        Err(e) => {
            log::error!("Pool RW error {}", e);
            return HttpResponse::ServiceUnavailable().body(actix_web::body::None::new());
        }
    };

    let pass_match = form.password == form.password_copy;
    let login_match = form_login == Some(cached_login.to_ascii_lowercase());
    if pass_match && login_match {
        if !cached_token.is_nil() {
            let stmt = match dbclient
                .prepare("delete from auth.reset_tokens where token = $1 and player_name = $2;")
                .await
            {
                Ok(stmt) => stmt,
                Err(e) => {
                    log::error!("Pool RW statement delete error {}", e);
                    return HttpResponse::ServiceUnavailable().body(actix_web::body::None::new());
                }
            };
            let deleted = match dbclient
                .execute(&stmt, &[&cached_token, &cached_login])
                .await
            {
                Ok(c) => c,
                Err(e) => {
                    log::error!("Pool RW execute delete error {}", e);
                    return HttpResponse::ServiceUnavailable().body(actix_web::body::None::new());
                }
            };
            if deleted == 0 {
                log::error!("Not delete error: {}", cached_token);
                return HttpResponse::BadRequest().body("Incorrect");
            }
        }
        let stmt = match dbclient.prepare(if is_game_pwd { "update auth.users set game_password = crypt($1, gen_salt('bf', 8)) where player_name = $2;" } else { "update auth.users set web_password = crypt($1, gen_salt('bf', 8)) where player_name = $2;" }).await {
            Ok(stmt) => stmt,
            Err(e) => {
                log::error!("Pool RW statement upd pwd error {}", e);
                return HttpResponse::ServiceUnavailable().body(actix_web::body::None::new());
            }
        };
        let changed = match dbclient
            .execute(&stmt, &[&form.password, &cached_login])
            .await
        {
            Ok(c) => c,
            Err(e) => {
                log::error!("Pool RW execute upd pwd error {}", e);
                return HttpResponse::ServiceUnavailable().body(actix_web::body::None::new());
            }
        };
        if changed > 0 {
            HttpResponse::Found()
                .append_header((header::LOCATION, "index.html"))
                .finish()
        } else {
            log::error!("Not update pwd error: {}", cached_token);
            HttpResponse::BadRequest().body("Incorrect")
        }
    } else {
        let stmt = match dbclient.prepare("update auth.reset_tokens set last_error = $1 where token = $2 and player_name = $3;").await {
            Ok(stmt) => stmt,
            Err(e) => {
                log::error!("Pool RW statement error {}", e);
                return HttpResponse::ServiceUnavailable().body(actix_web::body::None::new());
            }
        };
        let changed = match dbclient
            .execute(
                &stmt,
                &[
                    &if login_match {
                        "Passwords mismatch"
                    } else {
                        "Login mismatch"
                    },
                    &cached_token,
                    &cached_login,
                ],
            )
            .await
        {
            Ok(c) => c,
            Err(e) => {
                log::error!("Pool RW execute error {}", e);
                return HttpResponse::ServiceUnavailable().body(actix_web::body::None::new());
            }
        };
        if changed > 0 {
            HttpResponse::Found()
                .append_header((
                    header::LOCATION,
                    format!("reset-game-pwd-{}.html", cached_token),
                ))
                .finish()
        } else {
            log::error!("Not set error: {}", cached_token);
            HttpResponse::BadRequest().body("Incorrect")
        }
    }
}