// GOTCHA:
//     This module is kinda special as it contains a number of various handlers
//     which related to WebUI of Verifying Keys Service (hence VKS),
//     but their URLs DO NOT start with /vks.
//     Otherwise, these handlers would be scattered all over the other modules.

use crate::rate_limiter::RateLimiter;
use crate::web::vks::response::PublishResponse;
use crate::web::vks_web::{UPLOAD_LIMIT, forms, template};
use crate::web::{MyResponse, RequestOrigin, vks, vks_web};
use crate::{mail, tokens};
use anyhow::anyhow;
use gettext_macros::i18n;
use hagrid_database::{Query, Sqlite, StatefulTokens};
use rocket::Data;
use rocket::form::Form;
use rocket::http::ContentType;
use rocket::uri;
use rocket_codegen::{get, post, put};
use rocket_i18n::I18n;
use std::io::Cursor;

#[get("/upload")]
pub fn upload(origin: RequestOrigin, i18n: I18n) -> MyResponse {
    MyResponse::ok_bare("upload/upload", i18n, origin)
}

#[post("/upload/submit", format = "multipart/form-data", data = "<data>")]
pub async fn upload_post_form_data(
    db: &rocket::State<Sqlite>,
    origin: RequestOrigin,
    tokens_stateless: &rocket::State<tokens::Service>,
    rate_limiter: &rocket::State<RateLimiter>,
    i18n: I18n,
    cont_type: &ContentType,
    data: Data<'_>,
) -> MyResponse {
    match vks_web::process_upload(db, tokens_stateless, rate_limiter, &i18n, data, cont_type).await
    {
        Ok(response) => MyResponse::upload_response(response, i18n, origin),
        Err(err) => MyResponse::bad_request("upload/upload", err, i18n, origin),
    }
}

#[get("/search?<q>")]
pub fn search(
    db: &rocket::State<Sqlite>,
    origin: RequestOrigin,
    i18n: I18n,
    q: String,
) -> MyResponse {
    match q.parse::<Query>() {
        Ok(query) => vks_web::key_to_response(db, origin, i18n, q, query),
        Err(e) => MyResponse::bad_request("index", e, i18n, origin),
    }
}

#[put("/", data = "<data>")]
pub async fn quick_upload(
    db: &rocket::State<Sqlite>,
    tokens_stateless: &rocket::State<tokens::Service>,
    rate_limiter: &rocket::State<RateLimiter>,
    i18n: I18n,
    origin: RequestOrigin,
    data: Data<'_>,
) -> MyResponse {
    let buf = match data.open(UPLOAD_LIMIT).into_bytes().await {
        Ok(buf) => buf.into_inner(),
        Err(error) => return MyResponse::bad_request("400-plain", anyhow!(error), i18n, origin),
    };

    MyResponse::upload_response_quick(
        vks::process_key(db, &i18n, tokens_stateless, rate_limiter, Cursor::new(buf)),
        i18n,
        origin,
    )
}

#[get("/upload/<token>", rank = 2)]
pub fn quick_upload_proceed(
    db: &rocket::State<Sqlite>,
    origin: RequestOrigin,
    token_stateful: &rocket::State<StatefulTokens>,
    token_stateless: &rocket::State<tokens::Service>,
    mail_service: &rocket::State<mail::Service>,
    rate_limiter: &rocket::State<RateLimiter>,
    i18n: I18n,
    token: String,
) -> MyResponse {
    let result = vks::request_verify(
        db,
        &origin,
        token_stateful,
        token_stateless,
        mail_service,
        rate_limiter,
        &i18n,
        token,
        vec![],
    );
    MyResponse::upload_response(result, i18n, origin)
}

#[post(
    "/upload/submit",
    format = "application/x-www-form-urlencoded",
    data = "<data>"
)]
pub async fn upload_post_form(
    db: &rocket::State<Sqlite>,
    origin: RequestOrigin,
    tokens_stateless: &rocket::State<tokens::Service>,
    rate_limiter: &rocket::State<RateLimiter>,
    i18n: I18n,
    data: Data<'_>,
) -> MyResponse {
    match vks_web::process_post_form(db, tokens_stateless, rate_limiter, &i18n, data).await {
        Ok(response) => MyResponse::upload_response(response, i18n, origin),
        Err(err) => MyResponse::bad_request("upload/upload", err, i18n, origin),
    }
}

#[post(
    "/upload/request-verify",
    format = "application/x-www-form-urlencoded",
    data = "<request>"
)]
pub fn request_verify_form(
    db: &rocket::State<Sqlite>,
    origin: RequestOrigin,
    token_stateful: &rocket::State<StatefulTokens>,
    token_stateless: &rocket::State<tokens::Service>,
    mail_service: &rocket::State<mail::Service>,
    rate_limiter: &rocket::State<RateLimiter>,
    i18n: I18n,
    request: Form<forms::VerifyRequest>,
) -> MyResponse {
    let forms::VerifyRequest { token, address } = request.into_inner();
    let result = vks::request_verify(
        db,
        &origin,
        token_stateful,
        token_stateless,
        mail_service,
        rate_limiter,
        &i18n,
        token,
        vec![address],
    );
    MyResponse::upload_response(result, i18n, origin)
}

#[post(
    "/upload/request-verify",
    format = "multipart/form-data",
    data = "<request>"
)]
pub fn request_verify_form_data(
    db: &rocket::State<Sqlite>,
    origin: RequestOrigin,
    token_stateful: &rocket::State<StatefulTokens>,
    token_stateless: &rocket::State<tokens::Service>,
    mail_service: &rocket::State<mail::Service>,
    rate_limiter: &rocket::State<RateLimiter>,
    i18n: I18n,
    request: Form<forms::VerifyRequest>,
) -> MyResponse {
    let forms::VerifyRequest { token, address } = request.into_inner();
    let result = vks::request_verify(
        db,
        &origin,
        token_stateful,
        token_stateless,
        mail_service,
        rate_limiter,
        &i18n,
        token,
        vec![address],
    );
    MyResponse::upload_response(result, i18n, origin)
}

#[post("/verify/<token>")]
pub fn verify_confirm(
    db: &rocket::State<Sqlite>,
    origin: RequestOrigin,
    token_service: &rocket::State<StatefulTokens>,
    rate_limiter: &rocket::State<RateLimiter>,
    i18n: I18n,
    token: String,
) -> MyResponse {
    let rate_limit_id = format!("verify-token-{}", &token);
    match vks::verify_confirm(db, &i18n, token_service, token) {
        PublishResponse::Ok { fingerprint, email } => {
            rate_limiter.action_perform(rate_limit_id);
            let userid_link = uri!(search(q = &email)).to_string();
            let context = template::Verify {
                userid: email,
                key_fpr: fingerprint,
                userid_link,
            };

            MyResponse::ok("upload/publish-result", context, i18n, origin)
        }
        PublishResponse::Error(error) => {
            let error_msg = if rate_limiter.action_check(rate_limit_id) {
                anyhow!(error)
            } else {
                anyhow!(i18n!(
                    i18n.catalog,
                    "This address has already been verified."
                ))
            };
            MyResponse::bad_request("400", error_msg, i18n, origin)
        }
    }
}

#[get("/verify/<token>")]
pub fn verify_confirm_form(origin: RequestOrigin, i18n: I18n, token: String) -> MyResponse {
    MyResponse::ok(
        "upload/verification-form",
        template::VerifyForm { token },
        i18n,
        origin,
    )
}

#[cfg(test)]
mod tests {
    use crate::routes::tests::common::*;
    use ::rocket::http::{ContentType, Status};
    use ::rocket::local::blocking::Client;
    use rstest::rstest;
    use tempfile::TempDir;

    mod get_upload {
        use super::*;

        const URI: &str = "/upload";

        #[rstest]
        fn upload_form_is_visible(#[from(client)] (_tmpdir, client): (TempDir, Client)) {
            assert::response(
                client.get(URI).dispatch(),
                Status::Ok,
                ContentType::HTML,
                "upload",
            );
        }

        #[rstest]
        fn maintenance(
            #[from(client)] (tmpdir, client): (TempDir, Client),
            maintenance_text: &str,
        ) {
            test::maintenance(
                || client.get(URI).dispatch(),
                tmpdir,
                ContentType::HTML,
                maintenance_text,
            );
        }
    }

    mod get_search {
        use super::*;

        #[rstest]
        fn not_supported_search_query(
            #[from(client)] (_tmpdir, client): (TempDir, Client),
            #[values("/search?q=0x1234abcd", "/search?q=1234abcd")] uri: &str,
        ) {
            assert::response(
                client.get(uri).dispatch(),
                Status::BadRequest,
                ContentType::HTML,
                "not supported",
            );
        }
    }

    mod get_verify_token {
        use super::*;

        fn uri(token: &str) -> String {
            const URI: &str = "/verify";

            [URI, token].join("/")
        }

        #[rstest]
        fn maintenance(
            #[from(client)] (tmpdir, client): (TempDir, Client),
            maintenance_text: &str,
            token: &str,
        ) {
            test::maintenance(
                || client.get(uri(token)).dispatch(),
                tmpdir,
                ContentType::HTML,
                maintenance_text,
            );
        }
    }

    mod put_root {
        use super::*;

        const URI: &str = "/";

        #[rstest]
        fn maintenance(
            #[from(client)] (tmpdir, client): (TempDir, Client),
            maintenance_text: &str,
            #[from(serialized_cert)] (_cert_name, serialized_cert): (&str, Vec<u8>),
        ) {
            let request_closure = || client.put(URI).body(&serialized_cert).dispatch();

            test::maintenance(
                request_closure,
                tmpdir,
                ContentType::Plain,
                maintenance_text,
            );
        }
    }
}
