use hyperx::header::{Charset, ContentDisposition, DispositionParam, DispositionType};
use rocket::http::{Header, Status};
use rocket::outcome::Outcome;
use rocket::response::{Responder, Response};
use rocket::{async_trait, request};
use rocket_dyn_templates::Template;
use rocket_i18n::I18n;

use serde::Serialize;
use util::get_commit_sha;

use std::env;

use crate::i18n_helpers::describe_query_error;
use crate::template_helpers::TemplateOverrides;

use crate::app::state::ApplicationState;
use hagrid_database::{Database, Query, Sqlite, types::Fingerprint};

pub mod hkp;
pub mod maintenance;
pub mod manage;
pub mod util;
pub mod vks;
pub mod vks_api;
pub mod vks_web;

pub struct HagridTemplate(&'static str, serde_json::Value, I18n, RequestOrigin);

impl<'r> Responder<'r, 'static> for HagridTemplate {
    fn respond_to(self, req: &'r rocket::Request) -> Result<Response<'static>, Status> {
        let HagridTemplate(tmpl, ctx, i18n, origin) = self;

        let template_overrides: &TemplateOverrides = req
            .rocket()
            .state()
            .expect("TemplateOverrides must be in managed state");
        let template_override = template_overrides.get_template_override(i18n.lang, tmpl);
        let layout_context = templates::HagridLayout::new(ctx, i18n, origin);

        if let Some(template_override) = template_override {
            Template::render(template_override, layout_context)
        } else {
            Template::render(tmpl, layout_context)
        }
        .respond_to(req)
    }
}

#[derive(Responder)]
pub enum MyResponse {
    #[response(status = 200, content_type = "html")]
    Success(HagridTemplate),
    #[response(status = 200, content_type = "plain")]
    Plain(String),
    #[response(status = 200, content_type = "xml")]
    Xml(HagridTemplate),
    #[response(status = 200, content_type = "application/pgp-keys")]
    Key(String, Header<'static>),
    #[response(status = 200, content_type = "application/octet-stream")]
    WkdKey(Vec<u8>, Header<'static>),
    #[response(status = 500, content_type = "html")]
    ServerError(Template),
    #[response(status = 404, content_type = "html")]
    NotFound(HagridTemplate),
    #[response(status = 404, content_type = "html")]
    NotFoundPlain(String),
    #[response(status = 400, content_type = "html")]
    BadRequest(HagridTemplate),
    #[response(status = 400, content_type = "html")]
    BadRequestPlain(String),
    #[response(status = 501, content_type = "html")]
    NotImplementedPlain(String),
    #[response(status = 503, content_type = "html")]
    Maintenance(Template),
    #[response(status = 503, content_type = "json")]
    MaintenanceJson(serde_json::Value),
    #[response(status = 503, content_type = "plain")]
    MaintenancePlain(String),
}

impl MyResponse {
    pub fn ok(tmpl: &'static str, ctx: impl Serialize, i18n: I18n, origin: RequestOrigin) -> Self {
        let context_json = serde_json::to_value(ctx).unwrap();
        MyResponse::Success(HagridTemplate(tmpl, context_json, i18n, origin))
    }

    pub fn ok_bare(tmpl: &'static str, i18n: I18n, origin: RequestOrigin) -> Self {
        let context_json = serde_json::to_value(templates::Bare { dummy: () }).unwrap();
        MyResponse::Success(HagridTemplate(tmpl, context_json, i18n, origin))
    }

    pub fn xml(tmpl: &'static str, i18n: I18n, origin: RequestOrigin) -> Self {
        let context_json = serde_json::to_value(templates::Bare { dummy: () }).unwrap();
        MyResponse::Xml(HagridTemplate(tmpl, context_json, i18n, origin))
    }

    pub fn plain(s: String) -> Self {
        MyResponse::Plain(s)
    }

    pub fn key(armored_key: String, fp: &Fingerprint) -> Self {
        let content_disposition = Header::new(
            rocket::http::hyper::header::CONTENT_DISPOSITION.as_str(),
            ContentDisposition {
                disposition: DispositionType::Attachment,
                parameters: vec![DispositionParam::Filename(
                    Charset::Us_Ascii,
                    None,
                    (fp.to_string() + ".asc").into_bytes(),
                )],
            }
            .to_string(),
        );
        MyResponse::Key(armored_key, content_disposition)
    }

    pub fn wkd(binary_key: Vec<u8>, wkd_hash: &str) -> Self {
        let content_disposition = Header::new(
            rocket::http::hyper::header::CONTENT_DISPOSITION.as_str(),
            ContentDisposition {
                disposition: DispositionType::Attachment,
                parameters: vec![DispositionParam::Filename(
                    Charset::Us_Ascii,
                    None,
                    (wkd_hash.to_string() + ".pgp").into_bytes(),
                )],
            }
            .to_string(),
        );
        MyResponse::WkdKey(binary_key, content_disposition)
    }

    pub fn ise(e: anyhow::Error) -> Self {
        eprintln!("Internal error: {:?}", e);
        let ctx = templates::FiveHundred {
            internal_error: e.to_string(),
            version: env!("CARGO_PKG_VERSION").to_string(),
            commit: get_commit_sha(),
            lang: "en".to_string(),
        };
        MyResponse::ServerError(Template::render("500", ctx))
    }

    pub fn bad_request(
        template: &'static str,
        e: anyhow::Error,
        i18n: I18n,
        origin: RequestOrigin,
    ) -> Self {
        let ctx = templates::Error {
            error: format!("{}", e),
        };
        let context_json = serde_json::to_value(ctx).unwrap();
        MyResponse::BadRequest(HagridTemplate(template, context_json, i18n, origin))
    }

    pub fn bad_request_plain(message: impl Into<String>) -> Self {
        MyResponse::BadRequestPlain(message.into())
    }

    pub fn not_found_plain(message: impl Into<String>) -> Self {
        MyResponse::NotFoundPlain(message.into())
    }

    pub fn not_implemented_plain(message: impl Into<String>) -> Self {
        MyResponse::NotImplementedPlain(message.into())
    }

    pub fn not_found(
        tmpl: Option<&'static str>,
        message: impl Into<Option<String>>,
        i18n: I18n,
        origin: RequestOrigin,
    ) -> Self {
        let ctx = templates::Error {
            error: message.into().unwrap_or_else(|| "Key not found".to_owned()),
        };
        let context_json = serde_json::to_value(ctx).unwrap();
        MyResponse::NotFound(HagridTemplate(
            tmpl.unwrap_or("index"),
            context_json,
            i18n,
            origin,
        ))
    }
}

pub mod templates {
    use super::{I18n, RequestOrigin, util::get_commit_sha};
    use serde_derive::Serialize;

    #[derive(Serialize)]
    pub struct FiveHundred {
        pub internal_error: String,
        pub commit: String,
        pub version: String,
        pub lang: String,
    }

    #[derive(Serialize)]
    pub struct HagridLayout<T: serde::Serialize> {
        pub error: Option<String>,
        pub commit: String,
        pub version: String,
        pub base_uri: String,
        pub lang: String,
        pub htmldir: String,
        pub htmlclass: String,
        pub page: T,
    }

    #[derive(Serialize)]
    pub struct Error {
        pub error: String,
    }

    #[derive(Serialize)]
    pub struct Bare {
        // Dummy value to make sure {{#with page}} always passes
        pub dummy: (),
    }

    impl<T: serde::Serialize> HagridLayout<T> {
        pub fn new(page: T, i18n: I18n, origin: RequestOrigin) -> Self {
            let is_rtl = (i18n.lang) == "ar";
            Self {
                error: None,
                version: env!("CARGO_PKG_VERSION").to_string(),
                commit: get_commit_sha(),
                base_uri: origin.get_base_uri().to_string(),
                page,
                lang: i18n.lang.to_string(),
                htmldir: if is_rtl {
                    "rtl".to_owned()
                } else {
                    "ltr".to_owned()
                },
                htmlclass: if is_rtl {
                    "rtl".to_owned()
                } else {
                    "".to_owned()
                },
            }
        }
    }
}

#[derive(Debug)]
pub enum RequestOrigin {
    Direct(String),
    OnionService(String),
}

#[async_trait]
impl<'r> request::FromRequest<'r> for RequestOrigin {
    type Error = ();

    async fn from_request(
        request: &'r request::Request<'_>,
    ) -> request::Outcome<Self, Self::Error> {
        let state = request.rocket().state::<ApplicationState>().unwrap();
        let result = match request.headers().get("x-is-onion").next() {
            Some(_) => RequestOrigin::OnionService(state.base_uri_onion.clone()),
            None => RequestOrigin::Direct(state.base_uri.clone()),
        };
        Outcome::Success(result)
    }
}

impl RequestOrigin {
    pub fn get_base_uri(&self) -> &str {
        match self {
            RequestOrigin::Direct(uri) => uri.as_str(),
            RequestOrigin::OnionService(uri) => uri.as_str(),
        }
    }
}

pub fn key_to_response_plain(db: &rocket::State<Sqlite>, i18n: I18n, query: Query) -> MyResponse {
    if query.is_invalid() {
        return MyResponse::bad_request_plain(describe_query_error(&i18n, &query));
    }

    let fp = if let Some(fp) = db.lookup_primary_fingerprint(&query) {
        fp
    } else {
        return MyResponse::not_found_plain(describe_query_error(&i18n, &query));
    };

    match db.by_primary_fpr(&fp) {
        Some(armored) => MyResponse::key(armored, &fp),
        None => MyResponse::not_found_plain(describe_query_error(&i18n, &query)),
    }
}

#[cfg(test)]
pub mod tests {
    use ::rocket::http::ContentType;
    use ::rocket::http::Header;
    use ::rocket::http::Status;
    use ::rocket::local::blocking::Client;
    use tempfile::TempDir;

    use sequoia_openpgp::Cert;
    use sequoia_openpgp::serialize::Serialize;

    use crate::mail::pop_mail;
    use rstest::rstest;

    use crate::routes::tests::common::*;

    pub mod common {
        pub mod assert {
            use crate::mail::pop_mail;
            use crate::web::vks_api;
            use rocket::http::{ContentType, Header, Status};
            use rocket::local::blocking::{Client, LocalResponse};
            use sequoia_openpgp::Cert;
            use sequoia_openpgp::parse::Parse;
            use std::path::Path;
            use std::time::SystemTime;

            /// Asserts that the given URI 404s.
            pub fn check_null_response(client: &Client, uri: &str) {
                let response = client.get(uri).dispatch();

                assert_eq!(response.status(), Status::NotFound);
            }

            /// Asserts that lookups by the given email 404.
            pub fn check_null_responses_by_email(client: &Client, addr: &str) {
                check_null_response(client, &format!("/vks/v1/by-email/{}", addr));
                check_null_response(client, &format!("/pks/lookup?op=get&search={}", addr));
                check_null_response(
                    client,
                    &format!("/pks/lookup?op=get&options=mr&search={}", addr),
                );

                let (wkd_hash, domain) = hagrid_database::wkd::encode_wkd(addr).unwrap();
                check_null_response(
                    client,
                    &format!("/.well-known/openpgpkey/{}/hu/{}", domain, wkd_hash),
                );
            }

            /// Asserts that lookups by the given email are successful.
            pub fn check_responses_by_email(
                client: &Client,
                addr: &str,
                tpk: &Cert,
                nr_uids: usize,
                base_uri: &str,
                base_uri_onion: &str,
            ) {
                check_mr_response(client, &format!("/vks/v1/by-email/{}", addr), tpk, nr_uids);
                check_mr_response(
                    client,
                    &format!("/vks/v1/by-email/{}", addr.replace("@", "%40")),
                    tpk,
                    nr_uids,
                );
                check_mr_response(
                    client,
                    &format!("/pks/lookup?op=get&options=mr&search={}", addr),
                    tpk,
                    nr_uids,
                );
                check_mr_response(
                    client,
                    &format!(
                        "/pks/lookup?op=get&options=mr&search={}",
                        addr.replace("@", "%40")
                    ),
                    tpk,
                    nr_uids,
                );
                check_hr_response(
                    client,
                    &format!("/search?q={}", addr),
                    tpk,
                    nr_uids,
                    base_uri,
                );
                check_hr_response_onion(
                    client,
                    &format!("/search?q={}", addr),
                    tpk,
                    nr_uids,
                    base_uri_onion,
                );

                let (wkd_hash, domain) = hagrid_database::wkd::encode_wkd(addr).unwrap();
                check_wkd_response(
                    client,
                    &format!("/.well-known/openpgpkey/{}/hu/{}", domain, wkd_hash),
                    tpk,
                    nr_uids,
                );
            }

            /// Asserts that the given URI returns a Cert matching the given
            /// one, with the given number of userids.
            pub fn check_mr_response(client: &Client, uri: &str, tpk: &Cert, nr_uids: usize) {
                let response = client.get(uri).dispatch();
                assert_eq!(response.status(), Status::Ok);
                assert_eq!(
                    response.content_type(),
                    Some(ContentType::new("application", "pgp-keys"))
                );
                let body = response.into_string().unwrap();
                assert!(body.contains("END PGP PUBLIC KEY BLOCK"));
                let tpk_ = Cert::from_bytes(body.as_bytes()).unwrap();
                assert_eq!(tpk.fingerprint(), tpk_.fingerprint());
                assert_eq!(
                    tpk.keys()
                        .map(|skb| skb.key().fingerprint())
                        .collect::<Vec<_>>(),
                    tpk_.keys()
                        .map(|skb| skb.key().fingerprint())
                        .collect::<Vec<_>>()
                );
                assert_eq!(tpk_.userids().count(), nr_uids);
            }

            // it's a rather "reverse implementation" style test.. can we do better?
            /// Asserts that the given URI returns a correct hkp "index"
            /// response for the given Cert.
            pub fn check_index_response(client: &Client, uri: &str, tpk: &Cert) {
                let response = client.get(uri).dispatch();
                assert_eq!(response.status(), Status::Ok);
                assert_eq!(
                    response.content_type(),
                    Some(ContentType::new("text", "plain"))
                );
                let body = response.into_string().unwrap();

                assert!(body.contains("info:1:1"));
                let primary_fpr = tpk.fingerprint().to_hex();
                let algo: u8 = tpk.primary_key().key().pk_algo().into();
                assert!(body.contains(&format!("pub:{}:{}:", primary_fpr, algo)));

                let creation_time = tpk
                    .primary_key()
                    .key()
                    .creation_time()
                    .duration_since(SystemTime::UNIX_EPOCH)
                    .unwrap()
                    .as_secs();
                assert!(body.contains(&format!(":{}:", creation_time)));
            }

            /// Asserts that we can get the given Cert back using the various
            /// by-fingerprint or by-keyid lookup mechanisms.
            pub fn check_mr_responses_by_fingerprint(client: &Client, tpk: &Cert, nr_uids: usize) {
                let fp = tpk.fingerprint().to_hex();
                let keyid = sequoia_openpgp::KeyID::from(tpk.fingerprint()).to_hex();

                check_mr_response(client, &format!("/vks/v1/by-keyid/{}", keyid), tpk, nr_uids);
                check_mr_response(
                    client,
                    &format!("/vks/v1/by-fingerprint/{}", fp),
                    tpk,
                    nr_uids,
                );
                check_mr_response(
                    client,
                    &format!("/pks/lookup?op=get&options=mr&search={}", fp),
                    tpk,
                    nr_uids,
                );
                check_mr_response(
                    client,
                    &format!("/pks/lookup?op=get&options=mr&search=0x{}", fp),
                    tpk,
                    nr_uids,
                );
                check_mr_response(
                    client,
                    &format!("/pks/lookup?op=get&options=mr&search={}", keyid),
                    tpk,
                    nr_uids,
                );
                check_mr_response(
                    client,
                    &format!("/pks/lookup?op=get&options=mr&search=0x{}", keyid),
                    tpk,
                    nr_uids,
                );
                check_mr_response(
                    client,
                    &format!("/pks/lookup?op=get&search=0x{}", keyid),
                    tpk,
                    nr_uids,
                );

                check_index_response(client, &format!("/pks/lookup?op=index&search={}", fp), tpk);
            }

            /// Asserts that the given URI returns human readable response
            /// page that contains a URI pointing to the Cert.
            pub fn check_hr_response(
                client: &Client,
                uri: &str,
                tpk: &Cert,
                nr_uids: usize,
                base_uri: &str,
            ) {
                let response = client.get(uri).dispatch();
                assert_eq!(response.status(), Status::Ok);
                assert_eq!(response.content_type(), Some(ContentType::HTML));
                let body = response.into_string().unwrap();
                assert!(body.contains("found"));
                assert!(body.contains(&tpk.fingerprint().to_hex()));

                // Extract the links.
                let link_re =
                    regex::Regex::new(&format!("{}(/vks/[^ \t\n\"<]*)", base_uri)).unwrap();
                let mut n = 0;
                for link in link_re.captures_iter(&body) {
                    check_mr_response(client, link.get(1).unwrap().as_str(), tpk, nr_uids);
                    n += 1;
                }
                assert!(n > 0);
            }

            /// Asserts that the given URI returns human readable response
            /// page that contains an onion URI pointing to the Cert.
            pub fn check_hr_response_onion(
                client: &Client,
                uri: &str,
                tpk: &Cert,
                _nr_uids: usize,
                base_uri_onion: &str,
            ) {
                let response = client
                    .get(uri)
                    .header(Header::new("X-Is-Onion", "true"))
                    .dispatch();
                assert_eq!(response.status(), Status::Ok);
                let body = response.into_string().unwrap();
                assert!(body.contains("found"));
                assert!(body.contains(&tpk.fingerprint().to_hex()));

                // Extract the links.
                let link_re =
                    regex::Regex::new(&format!("{}(/vks/[^ \t\n\"<]*)", base_uri_onion)).unwrap();
                assert!(link_re.is_match(&body));
            }

            /// Asserts that we can get the given Cert back using the various
            /// by-fingerprint or by-keyid lookup mechanisms.
            pub fn check_hr_responses_by_fingerprint(
                client: &Client,
                tpk: &Cert,
                nr_uids: usize,
                base_uri: &str,
            ) {
                let fp = tpk.fingerprint().to_hex();
                let keyid = sequoia_openpgp::KeyID::from(tpk.fingerprint()).to_hex();

                check_hr_response(client, &format!("/search?q={}", fp), tpk, nr_uids, base_uri);
                check_hr_response(
                    client,
                    &format!("/search?q=0x{}", fp),
                    tpk,
                    nr_uids,
                    base_uri,
                );
                check_hr_response(
                    client,
                    &format!("/search?q={}", keyid),
                    tpk,
                    nr_uids,
                    base_uri,
                );
                check_hr_response(
                    client,
                    &format!("/search?q=0x{}", keyid),
                    tpk,
                    nr_uids,
                    base_uri,
                );
            }

            /// Asserts that the given URI returns correct WKD response with a Cert
            /// matching the given one, with the given number of userids.
            pub fn check_wkd_response(client: &Client, uri: &str, tpk: &Cert, nr_uids: usize) {
                let response = client.get(uri).dispatch();
                assert_eq!(response.status(), Status::Ok);
                assert_eq!(
                    response.content_type(),
                    Some(ContentType::new("application", "octet-stream"))
                );
                let body = response.into_bytes().unwrap();
                let tpk_ = Cert::from_bytes(&body).unwrap();
                assert_eq!(tpk.fingerprint(), tpk_.fingerprint());
                assert_eq!(
                    tpk.keys()
                        .map(|skb| skb.key().fingerprint())
                        .collect::<Vec<_>>(),
                    tpk_.keys()
                        .map(|skb| skb.key().fingerprint())
                        .collect::<Vec<_>>()
                );
                assert_eq!(tpk_.userids().count(), nr_uids);
            }

            pub fn check_verify_link(
                client: &Client,
                token: &str,
                address: &str,
                lang: &'static str,
            ) {
                let encoded = ::url::form_urlencoded::Serializer::new(String::new())
                    .append_pair("token", token)
                    .append_pair("address", address)
                    .finish();

                let response = client
                    .post("/upload/request-verify")
                    .header(ContentType::Form)
                    .header(Header::new("Accept-Language", lang))
                    .body(encoded.as_bytes())
                    .dispatch();
                assert_eq!(response.status(), Status::Ok);
            }

            pub fn check_verify_link_json(client: &Client, token: &str, address: &str) {
                let json = format!(r#"{{"token":"{}","addresses":["{}"]}}"#, token, address);

                let response = client
                    .post("/vks/v1/request-verify")
                    .header(ContentType::JSON)
                    .body(json.as_bytes())
                    .dispatch();
                assert_eq!(response.status(), Status::Ok);
                assert!(response.into_string().unwrap().contains("pending"));
            }

            pub fn check_mails_and_verify_email(
                client: &Client,
                filemail_path: &Path,
                base_uri: &str,
            ) {
                let pattern = format!("{}(/verify/[^ \t\n]*)", base_uri);
                let confirm_uri = pop_mail_capture_pattern(filemail_path, &pattern);

                let response = client.post(&confirm_uri).dispatch();
                assert_eq!(response.status(), Status::Ok);

                let response_second = client.post(&confirm_uri).dispatch();
                assert_eq!(response_second.status(), Status::BadRequest);
                assert!(
                    response_second
                        .into_string()
                        .unwrap()
                        .contains("already been verified")
                );
            }

            pub fn check_mails_and_confirm_deletion(
                client: &Client,
                filemail_path: &Path,
                address: &str,
                base_uri: &str,
            ) {
                let pattern = format!("{}/manage/([^ \t\n]*)", base_uri);
                let token = pop_mail_capture_pattern(filemail_path, &pattern);
                vks_manage_delete(client, &token, address);
            }

            pub fn pop_mail_capture_pattern(filemail_path: &Path, pattern: &str) -> String {
                let mail_content = pop_mail(filemail_path).unwrap().unwrap();

                let capture_re = regex::bytes::Regex::new(pattern).unwrap();
                let capture_content = capture_re
                    .captures(mail_content.as_ref())
                    .unwrap()
                    .get(1)
                    .unwrap()
                    .as_bytes();
                String::from_utf8_lossy(capture_content).to_string()
            }

            pub fn vks_publish_submit_multiple(client: &Client, data: &[u8]) {
                let response = vks_publish_submit_response(client, data);
                let status = response.status();
                let response_body = response.into_string().unwrap();

                assert_eq!(status, Status::Ok);
                assert!(response_body.contains("you must upload them individually"));
            }

            pub fn vks_publish_submit_get_token(client: &Client, data: &[u8]) -> String {
                let response = vks_publish_submit_response(client, data);
                let status = response.status();
                let response_body = response.into_string().unwrap();

                let pattern = "name=\"token\" value=\"([^\"]*)\"";
                let capture_re = regex::bytes::Regex::new(pattern).unwrap();
                let capture_content = capture_re
                    .captures(response_body.as_bytes())
                    .unwrap()
                    .get(1)
                    .unwrap()
                    .as_bytes();
                let token = String::from_utf8_lossy(capture_content).to_string();

                assert_eq!(status, Status::Ok);
                token
            }

            pub fn vks_publish_submit_response<'a>(
                client: &'a Client,
                data: &[u8],
            ) -> LocalResponse<'a> {
                let ct = ContentType::new("multipart", "form-data").with_params((
                    "boundary",
                    "---------------------------14733842173518794281682249499",
                ));

                let header = b"-----------------------------14733842173518794281682249499\r\n\
              Content-Disposition: form-data; name=\"csrf\"\r\n\
              \r\n\
              \r\n\
              -----------------------------14733842173518794281682249499\r\n\
              Content-Disposition: form-data; name=\"keytext\"; filename=\".k\"\r\n\
              Content-Type: application/octet-stream\r\n\
              \r\n";
                let footer = b"\r\n-----------------------------14733842173518794281682249499--";

                let mut body = Vec::new();
                body.extend_from_slice(header);
                body.extend_from_slice(data);
                body.extend_from_slice(footer);
                client
                    .post("/upload/submit")
                    .header(ct)
                    .body(&body[..])
                    .dispatch()
            }

            pub fn vks_publish_shortcut_get_token(
                client: &Client,
                data: &[u8],
                base_uri: &str,
            ) -> String {
                let response = client.put("/").body(data).dispatch();

                assert_eq!(response.status(), Status::Ok);

                let response_body = response.into_string().unwrap();

                assert!(response_body.contains("Key successfully uploaded"));

                let pattern = format!("{}/upload/([^ \t\n]*)", base_uri);
                let capture_re = regex::bytes::Regex::new(&pattern).unwrap();
                let capture_content = capture_re
                    .captures(response_body.as_bytes())
                    .unwrap()
                    .get(1)
                    .unwrap()
                    .as_bytes();

                String::from_utf8_lossy(capture_content).to_string()
            }

            pub fn vks_publish_json_get_token(client: &Client, data: &[u8]) -> String {
                let response = client
                    .post("/vks/v1/upload")
                    .header(ContentType::JSON)
                    .body(format!(r#"{{ "keytext": "{}" }}"#, base64::encode(data)))
                    .dispatch();
                let status = response.status();
                let response_body = response.into_string().unwrap();
                let result: vks_api::json::UploadResult =
                    serde_json::from_str(&response_body).unwrap();

                assert_eq!(status, Status::Ok);
                result.token
            }

            pub fn vks_manage(client: &Client, search_term: &str) {
                let encoded = ::url::form_urlencoded::Serializer::new(String::new())
                    .append_pair("search_term", search_term)
                    .finish();
                let response = client
                    .post("/manage")
                    .header(ContentType::Form)
                    .body(encoded.as_bytes())
                    .dispatch();
                assert_eq!(response.status(), Status::Ok);
            }

            pub fn vks_manage_delete(client: &Client, token: &str, address: &str) {
                let encoded = ::url::form_urlencoded::Serializer::new(String::new())
                    .append_pair("token", token)
                    .append_pair("address", address)
                    .finish();
                let response = client
                    .post("/manage/unpublish")
                    .header(ContentType::Form)
                    .body(encoded.as_bytes())
                    .dispatch();
                assert_eq!(response.status(), Status::Ok);
            }
        }
    }

    #[rstest]
    fn upload_verify_single(
        base_uri: &str,
        base_uri_onion: &str,
        #[from(cert)] (cert_name, tpk): (&str, Cert),
        #[from(client)] (tmpdir, client): (TempDir, Client),
    ) {
        let filemail_into = tmpdir.path().join("filemail");

        // Upload generated key.
        let mut tpk_serialized = Vec::new();
        tpk.serialize(&mut tpk_serialized).unwrap();
        let token = common::assert::vks_publish_submit_get_token(&client, &tpk_serialized);

        // Prior to email confirmation, we should not be able to look
        // it up by email address.
        common::assert::check_null_responses_by_email(&client, cert_name);

        // And check that we can get it back via the machine readable
        // interface.
        common::assert::check_mr_responses_by_fingerprint(&client, &tpk, 0);

        // And check that we can see the human-readable result page.
        common::assert::check_hr_responses_by_fingerprint(&client, &tpk, 0, base_uri);

        // Check the verification link
        common::assert::check_verify_link(&client, &token, cert_name, "");

        // Now check for the verification mail.
        common::assert::check_mails_and_verify_email(&client, filemail_into.as_path(), base_uri);

        // Now lookups using the mail address should work.
        common::assert::check_responses_by_email(
            &client,
            cert_name,
            &tpk,
            1,
            base_uri,
            base_uri_onion,
        );

        // And check that we can see the human-readable result page.
        common::assert::check_hr_responses_by_fingerprint(&client, &tpk, 1, base_uri);

        // Request deletion of the binding.
        common::assert::vks_manage(&client, cert_name);

        // Confirm deletion.
        common::assert::check_mails_and_confirm_deletion(
            &client,
            filemail_into.as_path(),
            cert_name,
            base_uri,
        );

        // Now, we should no longer be able to look it up by email
        // address.
        common::assert::check_null_responses_by_email(&client, cert_name);

        // But lookup by fingerprint should still work.
        common::assert::check_mr_responses_by_fingerprint(&client, &tpk, 0);

        // And check that we can see the human-readable result page.
        common::assert::check_hr_responses_by_fingerprint(&client, &tpk, 0, base_uri);

        assert::consistency(client.rocket());
    }

    #[rstest]
    fn upload_verify_lang(
        #[from(cert)] (cert_name, tpk): (&str, Cert),
        #[from(client)] (tmpdir, client): (TempDir, Client),
    ) {
        let filemail_into = tmpdir.path().join("filemail");

        // Upload it generated key.
        let mut tpk_serialized = Vec::new();
        tpk.serialize(&mut tpk_serialized).unwrap();
        let token = common::assert::vks_publish_submit_get_token(&client, &tpk_serialized);

        common::assert::check_verify_link(&client, &token, cert_name, "de");
        let mail_content = pop_mail(&filemail_into).unwrap().unwrap();
        assert!(mail_content.contains("Dies ist eine automatisierte Nachricht"));
        assert!(mail_content.contains("Subject: =?utf-8?b?QmVzdMOkdGlnZQ==?= foo@invalid.example.com \n =?utf-8?b?ZsO8cg==?= deinen =?utf-8?b?U2NobMO8c3NlbA==?= auf \n local.connection"));
    }

    #[rstest]
    fn upload_two(
        base_uri: &str,
        #[from(cert)] (cert_name_0, tpk_0): (&str, Cert),
        #[with(alt_cert_name())]
        #[from(cert)]
        (cert_name_1, tpk_1): (&str, Cert),
        #[from(client)] (_tmpdir, client): (TempDir, Client),
    ) {
        // Upload generated keys.
        let mut tpk_serialized = Vec::new();
        tpk_0.serialize(&mut tpk_serialized).unwrap();
        tpk_1.serialize(&mut tpk_serialized).unwrap();
        common::assert::vks_publish_submit_multiple(&client, &tpk_serialized);

        // Prior to email confirmation, we should not be able to look
        // them up by email address.
        common::assert::check_null_responses_by_email(&client, cert_name_0);
        common::assert::check_null_responses_by_email(&client, cert_name_1);

        // And check that we can get them back via the machine readable
        // interface.
        common::assert::check_mr_responses_by_fingerprint(&client, &tpk_0, 0);
        common::assert::check_mr_responses_by_fingerprint(&client, &tpk_1, 0);

        // And check that we can see the human-readable result page.
        common::assert::check_hr_responses_by_fingerprint(&client, &tpk_0, 0, base_uri);
        common::assert::check_hr_responses_by_fingerprint(&client, &tpk_1, 0, base_uri);
    }

    #[rstest]
    fn upload_verify_two(
        base_uri: &str,
        base_uri_onion: &str,
        #[from(cert)] (cert_name_1, tpk_1): (&str, Cert),
        #[with(alt_cert_name())]
        #[from(cert)]
        (cert_name_2, tpk_2): (&str, Cert),
        #[from(client)] (tmpdir, client): (TempDir, Client),
    ) {
        let filemail_into = tmpdir.path().join("filemail");

        // Upload generated keys.
        let mut tpk_serialized_1 = Vec::new();
        tpk_1.serialize(&mut tpk_serialized_1).unwrap();
        let token_1 = common::assert::vks_publish_submit_get_token(&client, &tpk_serialized_1);

        let mut tpk_serialized_2 = Vec::new();
        tpk_2.serialize(&mut tpk_serialized_2).unwrap();
        let token_2 = common::assert::vks_publish_json_get_token(&client, &tpk_serialized_2);

        // Prior to email confirmation, we should not be able to look
        // them up by email address.
        common::assert::check_null_responses_by_email(&client, cert_name_1);
        common::assert::check_null_responses_by_email(&client, cert_name_2);

        // And check that we can get them back via the machine readable
        // interface.
        common::assert::check_mr_responses_by_fingerprint(&client, &tpk_1, 0);
        common::assert::check_mr_responses_by_fingerprint(&client, &tpk_2, 0);

        // And check that we can see the human-readable result page.
        common::assert::check_hr_responses_by_fingerprint(&client, &tpk_1, 0, base_uri);
        common::assert::check_hr_responses_by_fingerprint(&client, &tpk_2, 0, base_uri);

        // Check the verification link
        common::assert::check_verify_link(&client, &token_1, cert_name_1, "");
        common::assert::check_verify_link_json(&client, &token_2, cert_name_2);

        // Now check for the verification mails.
        common::assert::check_mails_and_verify_email(&client, &filemail_into, base_uri);
        common::assert::check_mails_and_verify_email(&client, &filemail_into, base_uri);

        // Now lookups using the mail address should work.
        common::assert::check_responses_by_email(
            &client,
            cert_name_1,
            &tpk_1,
            1,
            base_uri,
            base_uri_onion,
        );
        common::assert::check_responses_by_email(
            &client,
            cert_name_2,
            &tpk_2,
            1,
            base_uri,
            base_uri_onion,
        );

        // Request deletion of the bindings.
        common::assert::vks_manage(&client, cert_name_1);
        common::assert::check_mails_and_confirm_deletion(
            &client,
            &filemail_into,
            cert_name_1,
            base_uri,
        );
        common::assert::vks_manage(&client, cert_name_2);
        common::assert::check_mails_and_confirm_deletion(
            &client,
            &filemail_into,
            cert_name_2,
            base_uri,
        );

        // Now, we should no longer be able to look it up by email
        // address.
        common::assert::check_null_responses_by_email(&client, cert_name_1);
        common::assert::check_null_responses_by_email(&client, cert_name_2);

        // But lookup by fingerprint should still work.
        common::assert::check_mr_responses_by_fingerprint(&client, &tpk_1, 0);
        common::assert::check_mr_responses_by_fingerprint(&client, &tpk_2, 0);

        // And check that we can see the human-readable result page.
        common::assert::check_hr_responses_by_fingerprint(&client, &tpk_1, 0, base_uri);
        common::assert::check_hr_responses_by_fingerprint(&client, &tpk_2, 0, base_uri);

        assert::consistency(client.rocket());
    }

    #[rstest]
    fn upload_no_key(#[from(client)] (_tmpdir, client): (TempDir, Client)) {
        let response = common::assert::vks_publish_submit_response(&client, b"");

        assert_eq!(response.status(), Status::BadRequest);
    }

    #[rstest]
    fn upload_verify_onion(
        base_uri_onion: &str,
        #[from(cert)] (cert_name, tpk): (&str, Cert),
        #[from(client)] (tmpdir, client): (TempDir, Client),
    ) {
        let filemail_into = tmpdir.path().join("filemail");

        // Upload generated key.
        let mut tpk_serialized = Vec::new();
        tpk.serialize(&mut tpk_serialized).unwrap();
        let token = common::assert::vks_publish_submit_get_token(&client, &tpk_serialized);

        // Check the verification link
        let encoded = ::url::form_urlencoded::Serializer::new(String::new())
            .append_pair("token", &token)
            .append_pair("address", cert_name)
            .finish();

        let response = client
            .post("/upload/request-verify")
            .header(ContentType::Form)
            .header(Header::new("X-Is-Onion", "true"))
            .body(encoded.as_bytes())
            .dispatch();
        assert_eq!(response.status(), Status::Ok);

        // Now check for the verification mail.
        let pattern = format!("{}(/verify/[^ \t\n]*)", base_uri_onion);
        let confirm_uri = common::assert::pop_mail_capture_pattern(&filemail_into, &pattern);

        let response = client.get(&confirm_uri).dispatch();
        assert_eq!(response.status(), Status::Ok);

        assert::consistency(client.rocket());
    }

    #[rstest]
    fn upload_curl_shortcut(
        base_uri: &str,
        #[from(cert)] (cert_name, tpk): (&str, Cert),
        #[from(client)] (_tmpdir, client): (TempDir, Client),
    ) {
        let mut tpk_serialized = Vec::new();
        tpk.serialize(&mut tpk_serialized).unwrap();

        let _token =
            common::assert::vks_publish_shortcut_get_token(&client, &tpk_serialized, base_uri);

        common::assert::check_mr_responses_by_fingerprint(&client, &tpk, 0);
        common::assert::check_null_responses_by_email(&client, cert_name);
    }
}
