use crate::rate_limiter::RateLimiter;
use crate::web::vks_api::{JsonErrorResponse, JsonResult, json};
use crate::web::{MyResponse, RequestOrigin, vks, vks_api};
use crate::{mail, tokens, web};
use hagrid_database::types::{Email, Fingerprint, KeyID};
use hagrid_database::{Query, Sqlite, StatefulTokens};
use rocket::http::Status;
use rocket::serde::json::{Error as JsonError, Json};
use rocket_codegen::{get, post};
use rocket_i18n::{I18n, Translations};

#[post("/vks/v1/upload", format = "json", data = "<data>")]
pub fn upload_json(
    db: &rocket::State<Sqlite>,
    tokens_stateless: &rocket::State<tokens::Service>,
    rate_limiter: &rocket::State<RateLimiter>,
    i18n: I18n,
    data: Result<Json<json::UploadRequest>, JsonError>,
) -> JsonResult {
    let data = vks_api::json_or_error(data)?;

    use crate::web::{vks, vks_api};
    use std::io::Cursor;

    let data_reader = Cursor::new(data.keytext.as_bytes());
    let result = vks::process_key(db, &i18n, tokens_stateless, rate_limiter, data_reader);

    vks_api::upload_ok_json(result)
}

#[post("/vks/v1/upload", rank = 2)]
pub fn upload_fallback(origin: RequestOrigin) -> JsonErrorResponse {
    let error_msg = format!(
        "expected application/json data. see {}/about/api for api docs.",
        origin.get_base_uri()
    );
    JsonErrorResponse(Status::BadRequest, error_msg)
}

#[post("/vks/v1/request-verify", format = "json", data = "<data>")]
pub fn request_verify_json(
    db: &rocket::State<Sqlite>,
    langs: &rocket::State<Translations>,
    origin: RequestOrigin,
    token_stateful: &rocket::State<StatefulTokens>,
    token_stateless: &rocket::State<tokens::Service>,
    mail_service: &rocket::State<mail::Service>,
    rate_limiter: &rocket::State<RateLimiter>,
    data: Result<Json<json::VerifyRequest>, JsonError>,
) -> JsonResult {
    let data = vks_api::json_or_error(data)?;
    let json::VerifyRequest {
        token,
        addresses,
        locale,
    } = data.into_inner();
    let i18n = vks_api::get_locale(langs, locale.unwrap_or_default());
    let result = vks::request_verify(
        db,
        &origin,
        token_stateful,
        token_stateless,
        mail_service,
        rate_limiter,
        &i18n,
        token,
        addresses,
    );
    vks_api::upload_ok_json(result)
}

#[post("/vks/v1/request-verify", rank = 2)]
pub fn request_verify_fallback(origin: RequestOrigin) -> JsonErrorResponse {
    let error_msg = format!(
        "expected application/json data. see {}/about/api for api docs.",
        origin.get_base_uri()
    );
    JsonErrorResponse(Status::BadRequest, error_msg)
}

#[get("/vks/v1/by-fingerprint/<fpr>")]
pub fn vks_v1_by_fingerprint(db: &rocket::State<Sqlite>, i18n: I18n, fpr: String) -> MyResponse {
    let query = match fpr.parse::<Fingerprint>() {
        Ok(fpr) => Query::ByFingerprint(fpr),
        Err(_) => return MyResponse::bad_request_plain("malformed fingerprint"),
    };

    web::key_to_response_plain(db, i18n, query)
}

#[get("/vks/v1/by-email/<email>")]
pub fn vks_v1_by_email(db: &rocket::State<Sqlite>, i18n: I18n, email: String) -> MyResponse {
    let email = email.replace("%40", "@");
    let query = match email.parse::<Email>() {
        Ok(email) => Query::ByEmail(email),
        Err(_) => return MyResponse::bad_request_plain("malformed e-mail address"),
    };

    web::key_to_response_plain(db, i18n, query)
}

#[get("/vks/v1/by-keyid/<kid>")]
pub fn vks_v1_by_keyid(db: &rocket::State<Sqlite>, i18n: I18n, kid: String) -> MyResponse {
    let query = match kid.parse::<KeyID>() {
        Ok(keyid) => Query::ByKeyID(keyid),
        Err(_) => return MyResponse::bad_request_plain("malformed key id"),
    };

    web::key_to_response_plain(db, i18n, query)
}

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

    mod post_vks_v1_upload {
        use rocket::http::Status;

        use super::*;

        const URI: &str = "/vks/v1/upload";

        #[rstest]
        fn success(
            #[from(client)] (_tmpdir, client): (TempDir, Client),
            #[from(serialized_cert)] (_cert_name, serialized_cert): (&str, Vec<u8>),
        ) {
            let request = client
                .post(URI)
                .header(ContentType::JSON)
                .body(format!(
                    r#"{{ "keytext": "{}" }}"#,
                    base64::encode(&serialized_cert)
                ))
                .dispatch();

            assert::response(
                request,
                Status::Ok,
                ContentType::JSON,
                "foo@invalid.example.com",
            );
        }

        #[rstest]
        fn rejectv5(
            #[from(client)] (_tmpdir, client): (TempDir, Client),
            #[from(armored_cert_v5)] armored_cert: &str,
        ) {
            let request = client
                .post(URI)
                .header(ContentType::JSON)
                .body(format!(r#"{{ "keytext": "{}" }}"#, armored_cert))
                .dispatch();

            assert::response(
                request,
                Status::BadRequest,
                ContentType::JSON,
                "{\"error\":\"OpenPGP v3 and OpenPGP v5 (LibrePGP) are not supported.\"}",
            );
        }

        #[rstest]
        fn rejectv6(
            #[from(client)] (_tmpdir, client): (TempDir, Client),
            #[from(serialized_cert_v6)] (_cert_name, serialized_cert): (&str, Vec<u8>),
        ) {
            let request = client
                .post(URI)
                .header(ContentType::JSON)
                .body(format!(
                    r#"{{ "keytext": "{}" }}"#,
                    base64::encode(&serialized_cert)
                ))
                .dispatch();

            assert::response(
                request,
                Status::BadRequest,
                ContentType::JSON,
                "{\"error\":\"OpenPGP v6 (RFC 9580) is not yet supported.\"}",
            );
        }

        #[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
                    .post(URI)
                    .header(ContentType::JSON)
                    .body(format!(
                        r#"{{ "keytext": "{}" }}"#,
                        base64::encode(&serialized_cert)
                    ))
                    .dispatch()
            };

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

    mod post_vks_v1_request_verify_json {
        use super::*;
        use crate::web::tests::common;

        const URI: &str = "/vks/v1/request-verify";

        #[rstest]
        fn maintenance(
            #[from(client)] (tmpdir, client): (TempDir, Client),
            maintenance_text: &str,
            #[from(serialized_cert)] (cert_name, serialized_cert): (&str, Vec<u8>),
        ) {
            let token = common::assert::vks_publish_json_get_token(&client, &serialized_cert);

            let json = format!(r#"{{"token":"{}","addresses":["{}"]}}"#, token, cert_name);

            let request_closure = || {
                client
                    .post(URI)
                    .header(ContentType::JSON)
                    .body(json.as_bytes())
                    .dispatch()
            };

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