use aes_gcm_stream::{Aes256GcmStreamDecryptor, Aes256GcmStreamEncryptor};
use aes_kw::Kek;
use base64::engine::general_purpose::URL_SAFE_NO_PAD;
use base64::Engine;
use josekit::jwe;
use josekit::jwe::alg::aeskw::AeskwJweAlgorithm;
use josekit::jwe::alg::rsaes::RsaesJweAlgorithm;
use josekit::jwe::JweHeader;
use josekit::jwk::alg::rsa::RsaKeyPair;
use josekit::jwk::Jwk;
use rand::{random, thread_rng};
use rsa::{Oaep, RsaPrivateKey, RsaPublicKey};
use rust_util::{opt_result, simple_error, XResult};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use sha1::Sha1;

const LOCAL_KMS_PREFIX: &str = "LKMS:";

// JWE format:
// BASE64URL(UTF8(JWE Protected Header)) || '.' ||
// BASE64URL(JWE Encrypted Key) || '.' || BASE64URL(JWE Initialization Vector)
// || '.' || BASE64URL(JWE Ciphertext) || '.' || BASE64URL(JWE Authentication Tag).
//
// RSA JWE Header:
// {"enc":"A256GCM","vendor":"local-mini-kms","alg":"RSA-OAEP"}
// eyJlbmMiOiJBMjU2R0NNIiwidmVuZG9yIjoibG9jYWwtbWluaS1rbXMiLCJhbGciOiJSU0EtT0FFUCJ9.VQ_R
// yGjXqQlUIbRIMgaYRSaX5FMRBzZ6ApfdZ2yAwiG70hjNfR3ss7x4PYqMm6QtITm1O4_fp7I3bY8iUz5Njyth_
// Min7Xm2-WsQ6gq9yN58btkUBFm60c7SC5XLaqE1pEtBAz7786bJk6M4NeOtDAOFAmIb2j1EwnS5vweBtmNv7N
// UFIgvx806T3WkCFDOkMSJ10_6LSa0z-lIac-s68svsU5WW8CXVKxHAbxaHyX_otu2HxXzDZlF5Goamh5ZJtr0
// 0yW_bzDCx3hZ2nMK3Ve7IJ2ZLAMmvhj9LKWkPtoqH0dGHaPHWff5P3rZ4vtKywt_h5b6SYII_mEoJcpByMyGw
// TXCtZymDt82Tyv_FesW2721JgyGxnukuOxQRTw4MfGYIO5bldL3uGGI_H4HXlXhM_kp3wuPAZ0vH4Jj2KD6MV
// DDTJQaEBQIEF07i7WiNynr57kbahYwextRXYP7LgoUHfFwA5GGGpN-UkuWLlKkYLTmXGrPYnL6Cf9D3euP7nF
// ml2oA2hjig-UuYf9A_QSEqNsMxYDuG-rggn3H_iXNl4ooYcxSVOXhTKfoV578MkNwG75BdHN5FeRYIKq0HCTM
// lGqqBWmDibPtMd7Uq1JrDd8774lnA8JcZcCMSia4m6WJSbG0kOuJ4NJPOUrYtNEJXgWKU3FQzDB-apLMQdac.
// WYJgsdZRLk310KWd.P333-S2VYg.PCfruTdk8vh3a8wcjJCe-g
//
// RSA-OAEP	RSA using Optimal Asymmetric Encryption Padding (OAEP), as defined in RFC 3447 [RFC3447]
// A256GCM	Advanced Encryption Standard (AES) using 256 bit keys in Galois/Counter Mode, as defined in [FIPS‑197] and [NIST‑800‑38D]
//
// AES JWE Header:
// {"enc":"A256GCM","vendor":"local-mini-kms","version":"5b90f66a1c6a918d","alg":"A256KW"}
// eyJlbmMiOiJBMjU2R0NNIiwidmVuZG9yIjoibG9jYWwtbWluaS1rbXMiLCJ2ZXJzaW9uIjoiNWI5MGY2NmExYz
// ZhOTE4ZCIsImFsZyI6IkEyNTZLVyJ9.K2_P-b_Gq9wbrssbcS5AmiUwcnNTnnZSe7rBI1SixVrC7TfFK0fruw.
// ez3OKjOHAIIYnfM0.wSO3aXo.-vGJwk8JQKhi3voIlAA9gQ
//
// A256GCM	Advanced Encryption Standard (AES) using 256 bit keys in Galois/Counter Mode, as defined in [FIPS‑197] and [NIST‑800‑38D]
// A256KW	Advanced Encryption Standard (AES) Key Wrap Algorithm using 256 bit keys, as defined in RFC 3394 [RFC3394]

//          JWE Header:  {"alg":"dir","enc":"A256GCM"}
// Encrypted key (CEK):  (blank)
//                  IV:  Vlf_WdLm-spHbfJe
//          Ciphertext:  RxMPrw
//  Authentication Tag:  5VC8Y_qSPdSubbGNGyfn6A
//
//          JWE Header:  {"alg":"A256KW","enc":"A256GCM"}
// Encrypted key (CEK):  66xZoxFI18zfvLMO6WU1zzqqX1tT8xu_qZzMQyPcfVuajPNkOJUXQA
//                  IV:  X5ZL8yaOektXmfny
//          Ciphertext:  brz-Lg
//  Authentication Tag:  xG-EvM-9hrw0XRiuRW7HrA
//
// https://security.stackexchange.com/questions/80966/what-is-the-point-of-aes-key-wrap-with-json-web-encryption

const JWE_ENC_A256GCM: &str = "A256GCM";
const JWE_ALG_A256KW: &str = "A256KW";
const JWE_ALG_RSA_OAEP: &str = "RSA-OAEP";
const JWE_DOT: &str = ".";

pub fn generate_rsa_key_2(bits: u32) -> XResult<RsaPrivateKey> {
    let mut rng = thread_rng();
    Ok(RsaPrivateKey::new(&mut rng, bits as usize)?)
}

pub fn generate_rsa_key(bits: u32) -> XResult<RsaKeyPair> {
    Ok(RsaKeyPair::generate(bits)?)
}

pub fn serialize_jwe_rsa_2(payload: &[u8], rsa_public_key: &RsaPublicKey) -> XResult<String> {
    let header = JweHeader2 {
        enc: JWE_ENC_A256GCM.to_string(),
        alg: JWE_ALG_RSA_OAEP.to_string(),
        vendor: "local-mini-kms".to_string(),
        version: None,
    };
    serialize_jwe_fn(&header, payload, |data_key| -> XResult<Vec<u8>> {
        let mut r = thread_rng();
        Ok(opt_result!(rsa_public_key.encrypt(&mut r, Oaep::new::<Sha1>(), data_key), "Wrap key failed: {}"))
    })
}

pub fn serialize_jwe_rsa(payload: &[u8], jwk: &Jwk) -> XResult<String> {
    let mut header = JweHeader::new();
    header.set_content_encryption(JWE_ENC_A256GCM);
    header.set_claim("vendor", Some(Value::String("local-mini-kms".to_string())))?;
    let encrypter = RsaesJweAlgorithm::RsaOaep.encrypter_from_jwk(jwk)?;
    Ok(format!("{}{}", LOCAL_KMS_PREFIX, jwe::serialize_compact(payload, &header, &encrypter)?))
}

pub fn deserialize_jwe_rsa_2(jwe: &str, rsa: &RsaPrivateKey) -> XResult<(Vec<u8>, JweHeader2)> {
    deserialize_jwe_fn(jwe, |alg, key_wrap| -> XResult<Vec<u8>> {
        if alg != JWE_ALG_RSA_OAEP {
            return simple_error!("Invalid JWE header alg: {}", alg);
        }
        Ok(opt_result!(rsa.decrypt(Oaep::new::<Sha1>(), &key_wrap), "Unwrap key failed: {}"))
    })
}

pub fn deserialize_jwe_rsa(jwe: &str, jwk: &Jwk) -> XResult<(Vec<u8>, JweHeader)> {
    let decrypter = RsaesJweAlgorithm::RsaOaep.decrypter_from_jwk(jwk)?;
    Ok(jwe::deserialize_compact(&get_jwe(jwe), &decrypter)?)
}

#[derive(Debug, Serialize, Deserialize)]
pub struct JweHeader2 {
    pub enc: String,
    pub alg: String,
    pub vendor: String,
    pub version: Option<String>,
}

pub fn serialize_jwe_aes_2(payload: &[u8], key: [u8; 32]) -> XResult<String> {
    let header = JweHeader2 {
        enc: JWE_ENC_A256GCM.to_string(),
        alg: JWE_ALG_A256KW.to_string(),
        vendor: "local-mini-kms".to_string(),
        version: None,
    };
    serialize_jwe_fn(&header, payload, |data_key| -> XResult<Vec<u8>> {
        let kek = Kek::from(key);
        Ok(opt_result!(kek.wrap_vec(&data_key[..]), "Wrap key failed: {}"))
    })
}

pub fn serialize_jwe_aes(payload: &[u8], key: &[u8]) -> XResult<String> {
    let mut header = JweHeader::new();
    header.set_content_encryption(JWE_ENC_A256GCM);
    header.set_claim("vendor", Some(Value::String("local-mini-kms".to_string())))?;
    // header.set_claim("version", Some(Value::String(get_master_key_checksum(key))))?;
    let encrypter = AeskwJweAlgorithm::A256kw.encrypter_from_bytes(key)?;
    Ok(format!("{}{}", LOCAL_KMS_PREFIX, jwe::serialize_compact(payload, &header, &encrypter)?))
}

pub fn deserialize_jwe_aes_2(jwe: &str, key: [u8; 32]) -> XResult<(Vec<u8>, JweHeader2)> {
    deserialize_jwe_fn(jwe, |alg, key_wrap| -> XResult<Vec<u8>> {
        if alg != JWE_ALG_A256KW {
            return simple_error!("Invalid JWE header alg: {}", alg);
        }
        let kek = Kek::from(key);
        Ok(opt_result!(kek.unwrap_vec(&key_wrap), "Unwrap key failed: {}"))
    })
}

pub fn deserialize_jwe_aes(jwe: &str, key: &[u8]) -> XResult<(Vec<u8>, JweHeader)> {
    let decrypter = AeskwJweAlgorithm::A256kw.decrypter_from_bytes(key)?;
    Ok(jwe::deserialize_compact(&get_jwe(jwe), &decrypter)?)
}

fn serialize_jwe_fn<F>(header: &JweHeader2, payload: &[u8], key_wrap_fn: F) -> XResult<String>
where
    F: Fn(&[u8]) -> XResult<Vec<u8>>,
{
    let header_str = opt_result!(serde_json::to_string(&header), "Invalid JWE header: {}");
    let header_b64 = URL_SAFE_NO_PAD.encode(header_str.as_bytes());

    let data_key: [u8; 32] = random();
    let iv: [u8; 12] = random();
    let mut encryptor = Aes256GcmStreamEncryptor::new(data_key, &iv);
    encryptor.init_adata(header_b64.as_bytes());
    let mut ciphertext = encryptor.update(payload);
    let (ciphertext_final, tag) = encryptor.finalize();
    ciphertext.extend_from_slice(&ciphertext_final);

    let cek = key_wrap_fn(&data_key)?;

    Ok(format!(
        "{}.{}.{}.{}.{}",
        header_b64,
        URL_SAFE_NO_PAD.encode(&cek),
        URL_SAFE_NO_PAD.encode(&iv),
        URL_SAFE_NO_PAD.encode(&ciphertext),
        URL_SAFE_NO_PAD.encode(&tag)
    ))
}

fn deserialize_jwe_fn<F>(jwe: &str, key_unwrap_fn: F) -> XResult<(Vec<u8>, JweHeader2)>
where
    F: Fn(&str, &[u8]) -> XResult<Vec<u8>>,
{
    let jwe_parts = jwe.split(JWE_DOT).collect::<Vec<&str>>();
    if jwe_parts.len() != 5 {
        return simple_error!("Invalid JWE: {}", jwe);
    }
    let header_bytes = opt_result!(decode_url_safe_no_pad(jwe_parts[0]), "Invalid JWE header: {}, JWE: {}", jwe);
    let header: JweHeader2 = opt_result!(serde_json::from_slice(&header_bytes), "Invalid JWE header: {}, JWE: {}", jwe);
    if header.enc != JWE_ENC_A256GCM {
        return simple_error!("Invalid JWE header enc: {}", header.enc);
    }

    let cek = opt_result!(decode_url_safe_no_pad(jwe_parts[1]), "Invalid JWE CEK: {}, JWE: {}", jwe);
    let iv = opt_result!(decode_url_safe_no_pad(jwe_parts[2]), "Invalid JWE IV: {}, JWE: {}", jwe);
    let ciphertext = opt_result!(decode_url_safe_no_pad(jwe_parts[3]), "Invalid JWE ciphertext: {}, JWE: {}", jwe);
    let tag = opt_result!(decode_url_safe_no_pad(jwe_parts[4]), "Invalid JWE tag: {}, JWE: {}", jwe);

    let data_key = key_unwrap_fn(&header.alg, &cek)?;
    let data_key_b32 = opt_result!(to_bytes32(&data_key), "Invalid JWE CEK: {}, JWE: {}", jwe);

    let mut decryptor = Aes256GcmStreamDecryptor::new(data_key_b32, &iv);
    decryptor.init_adata(jwe_parts[0].as_bytes());
    let mut plaintext = decryptor.update(&ciphertext);
    let plaintext_2 = decryptor.update(&tag);
    let plaintext_final = opt_result!(decryptor.finalize(), "Invalid JWE: {}, JWE: {}", jwe);
    plaintext.extend_from_slice(&plaintext_2);
    plaintext.extend_from_slice(&plaintext_final);
    Ok((plaintext, header))
}

#[inline]
fn decode_url_safe_no_pad(s: &str) -> XResult<Vec<u8>> {
    Ok(URL_SAFE_NO_PAD.decode(s.as_bytes())?)
}

#[inline]
fn to_bytes32(bytes: &[u8]) -> XResult<[u8; 32]> {
    if bytes.len() != 32 {
        return simple_error!("Not valid 32 bytes");
    }
    let mut ret = [0; 32];
    for i in 0..32 {
        ret[i] = bytes[i];
    }
    Ok(ret)
}

#[inline]
fn get_jwe(jwe: &str) -> String {
    if jwe.starts_with(LOCAL_KMS_PREFIX) {
        jwe.chars().skip(LOCAL_KMS_PREFIX.len()).collect()
    } else {
        jwe.to_string()
    }
}