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

use uuid::Uuid;

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

#[derive(serde_derive::Serialize)]
struct PageData {
    csrf: Uuid,
}

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

#[derive(serde_derive::Deserialize)]
pub struct FormDataMastodon {
    login: String,
    csrf: Uuid,
}

pub async fn get_log_in(request: HttpRequest, data: web::Data<WebData<'_>>) -> HttpResponse {
    let csrf = Uuid::new_v4();

    {
        let mut cache = data.cache_login.lock().await;
        cache.insert(
            csrf,
            (),
            std::time::Duration::from_secs(data.cache_duration_sec),
        );
    }

    let jar = request_to_jar(request);
    if !jar
        .private(&data.cookies_key)
        .get("i_accept_cookie")
        .map_or(false, |x| x.value() == "yes")
    {
        return HttpResponse::Found()
            .append_header((header::LOCATION, "cookies-policy.html"))
            .finish();
    }

    let body = match data.handlebars.render(LOG_IN, &PageData { csrf }) {
        Ok(b) => b,
        Err(e) => {
            log::error!("Render login error: {}", e);
            return HttpResponse::ServiceUnavailable().body(actix_web::body::None::new());
        }
    };
    insert_security_headers(HttpResponse::Ok()).body(body)
}

pub async fn post_log_in(
    request: HttpRequest,
    form: web::Form<FormData>,
    data: web::Data<WebData<'_>>,
    data_ro: web::Data<DataBaseRo>,
) -> HttpResponse {
    let cached_data = {
        let mut cache = data.cache_login.lock().await;
        cache.remove(&form.csrf)
    };
    if cached_data.is_none() {
        log::warn!("Unknown data for CSRF: {}", form.csrf);
        return HttpResponse::BadRequest().body("Incorrect");
    }

    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 web_password = crypt($1, web_password) from auth.users where player_name = $2 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, &[&form.password, &form.login])
        .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");
        }
    };

    if !row.get::<_, Option<bool>>(0).unwrap_or(false) {
        return HttpResponse::NotFound().body("Not found");
    }

    let mut jar = request_to_jar(request);
    if !jar
        .private(&data.cookies_key)
        .get("i_accept_cookie")
        .map_or(false, |x| x.value() == "yes")
    {
        return HttpResponse::Found()
            .append_header((header::LOCATION, "cookies-policy.html"))
            .finish();
    }

    let mut builder = cookie::Cookie::build("auth", form.login.to_lowercase())
        .path("/")
        .secure(true)
        .http_only(true)
        .same_site(cookie::SameSite::Strict)
        .max_age(cookie::time::Duration::weeks(4));
    if data.set_cookie_domain {
        builder = builder.domain(data.base_domain.to_string());
    }
    jar.private_mut(&data.cookies_key).add(builder.finish());

    let mut response = HttpResponse::Found()
        .append_header((header::LOCATION, "my.html"))
        .finish();
    if let Some(c) = jar.get("auth") {
        if let Err(e) = response.add_cookie(c) {
            log::error!("Cann't set cookie {}", e);
        }
    }
    response
}

pub async fn post_log_in_mastodon(
    form: web::Form<FormDataMastodon>,
    data: web::Data<WebData<'_>>,
    data_ro: web::Data<DataBaseRo>,
    data_rw: web::Data<DataBaseRw>,
) -> HttpResponse {
    let cached_data = {
        let mut cache = data.cache_login.lock().await;
        cache.remove(&form.csrf)
    };
    if cached_data.is_none() {
        log::warn!("Unknown data for CSRF: {}", form.csrf);
        return HttpResponse::BadRequest().body("Incorrect");
    }

    let mut parts = form.login.splitn(4, '@');
    let (user, domain) = match (parts.next(), parts.next(), parts.next(), parts.next()) {
        (Some(u), Some(d), None, None) | (Some(""), Some(u), Some(d), None) => {
            (u.to_ascii_lowercase(), d.to_ascii_lowercase())
        }
        other => {
            log::error!("Unknown Mastodon address {:?}", other);
            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 from auth.contacts where protocol = 'mastodon' and is_active = true and delete_ts is null and address = $1 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 address = format!("{}@{}", user, domain);
    let rows = match dbclient.query_opt(&stmt, &[&address]).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);

    log::info!("Found player {} to login with mastodon", player_name);

    let dbclient_rw = 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 stmt = match dbclient_rw
        .prepare(
            "INSERT INTO auth.mastodon_apps (domain, refresh_ts) VALUES ($1, $2)
            ON CONFLICT (domain) DO UPDATE SET refresh_ts = $2
            RETURNING client_id, client_secret;",
        )
        .await
    {
        Ok(stmt) => stmt,
        Err(e) => {
            log::error!("Pool RW statement error {}", e);
            return HttpResponse::ServiceUnavailable().body(actix_web::body::None::new());
        }
    };
    let ts = chrono::Utc::now().naive_utc();
    let rows = match dbclient_rw.query_opt(&stmt, &[&domain, &ts]).await {
        Ok(rows) => rows,
        Err(e) => {
            log::error!("Pool RW query error {}", e);
            return HttpResponse::ServiceUnavailable().body(actix_web::body::None::new());
        }
    };
    let row = match rows {
        Some(row) => row,
        None => {
            log::error!("Pool RW query error");
            return HttpResponse::ServiceUnavailable().body(actix_web::body::None::new());
        }
    };

    let _opt_client_id = row.get::<_, Option<&str>>(0);
    let _opt_client_secret = row.get::<_, Option<&str>>(1);

    // Try to register on Mastodon domain

    HttpResponse::Found()
        .append_header((header::LOCATION, "index.html"))
        .finish()
}