use crate::rate_limiter::RateLimiter;
use crate::web::hkp::Hkp;
use crate::web::vks::response::UploadResponse;
use crate::web::{MyResponse, RequestOrigin, hkp, vks_web};
use crate::{mail, tokens, web};
use hagrid_database::{Query, Sqlite};
use rocket::Data;
use rocket::http::ContentType;
use rocket_codegen::{get, post};
use rocket_i18n::I18n;
use std::str::FromStr;

#[post("/pks/add", format = "multipart/form-data", data = "<data>")]
pub async fn pks_add_form_data(
    db: &rocket::State<Sqlite>,
    tokens_stateless: &rocket::State<tokens::Service>,
    rate_limiter: &rocket::State<RateLimiter>,
    i18n: I18n,
    cont_type: &ContentType,
    data: Data<'_>,
) -> MyResponse {
    match vks_web::process_post_form_data(db, tokens_stateless, rate_limiter, i18n, cont_type, data)
        .await
    {
        Ok(_) => MyResponse::plain("Ok".into()),
        Err(err) => MyResponse::ise(err),
    }
}

#[post(
    "/pks/add",
    format = "application/x-www-form-urlencoded",
    data = "<data>"
)]
pub async fn pks_add_form(
    origin: RequestOrigin,
    db: &rocket::State<Sqlite>,
    tokens_stateless: &rocket::State<tokens::Service>,
    rate_limiter: &rocket::State<RateLimiter>,
    mail_service: &rocket::State<mail::Service>,
    i18n: I18n,
    data: Data<'_>,
) -> MyResponse {
    match vks_web::process_post_form(db, tokens_stateless, rate_limiter, &i18n, data).await {
        Ok(UploadResponse::Ok {
            is_new_key,
            key_fpr,
            primary_uid,
            token,
            status,
            ..
        }) => {
            let msg = hkp::pks_add_ok(
                &origin,
                mail_service,
                rate_limiter,
                token,
                status,
                is_new_key,
                key_fpr,
                primary_uid,
            );
            MyResponse::plain(msg)
        }
        Ok(_) => {
            let msg = format!(
                "Upload successful. Please note that identity information will only be published after verification. See {baseuri}/about/usage#gnupg-upload",
                baseuri = origin.get_base_uri()
            );
            MyResponse::plain(msg)
        }
        Err(err) => MyResponse::ise(err),
    }
}

#[get("/pks/lookup?<op>&<search>")]
pub fn pks_lookup(
    db: &rocket::State<Sqlite>,
    i18n: I18n,
    op: Option<String>,
    search: Option<String>,
) -> MyResponse {
    let search = search.unwrap_or_default();
    let key = match Hkp::from_str(&search) {
        Ok(key) => key,
        Err(_) => return MyResponse::bad_request_plain("Invalid search query!"),
    };
    let query = match key {
        Hkp::Fingerprint { fpr } => Query::ByFingerprint(fpr),
        Hkp::KeyID { keyid } => Query::ByKeyID(keyid),
        Hkp::Email { email } => Query::ByEmail(email),
        Hkp::ShortKeyID { query: _, .. } => {
            return MyResponse::bad_request_plain(
                "Search by short key ids is not supported, sorry!",
            );
        }
    };

    if let Some(op) = op {
        match op.as_str() {
            "index" => hkp::key_to_hkp_index(db, i18n, query),
            "get" => web::key_to_response_plain(db, i18n, query),
            "vindex" => MyResponse::not_implemented_plain("vindex not implemented"),
            s if s.starts_with("x-") => {
                MyResponse::not_implemented_plain("x-* operations not implemented")
            }
            &_ => MyResponse::bad_request_plain("Invalid op parameter!"),
        }
    } else {
        MyResponse::bad_request_plain("op parameter required!")
    }
}

#[get("/pks/internal/index/<query_string>")]
pub fn pks_internal_index(
    db: &rocket::State<Sqlite>,
    i18n: I18n,
    query_string: String,
) -> MyResponse {
    match query_string.parse() {
        Ok(query) => hkp::key_to_hkp_index(db, i18n, query),
        Err(_) => MyResponse::bad_request_plain("Invalid search query!"),
    }
}

#[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_pks_lookup {
        use super::*;

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

    mod post_pks_add_multipart_form_data {
        use super::*;

        const URI: &str = "/pks/add";

        #[rstest]
        fn maintenance(
            #[from(client)] (tmpdir, client): (TempDir, Client),
            maintenance_text: &str,
            #[from(key_multipart_form_data)] (content_type, key_body): (ContentType, Vec<u8>),
        ) {
            let request_closure = || {
                client
                    .post(URI)
                    .header(content_type.to_owned())
                    .body(&key_body)
                    .dispatch()
            };

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