use serde::{Deserialize, Serialize};
use std::fmt::{Debug, Display, Formatter};

#[cfg(not(target_arch = "wasm32"))]
use {
    anyhow::{anyhow, Result},
    base64::{prelude::BASE64_STANDARD, Engine},
    json_ld::{JsonLdProcessor, RemoteDocument, ReqwestLoader},

    // RDF-related imports
    oxrdf::Dataset,
    oxttl::NQuadsParser,
    rdf_canon::canonicalize,

    // Cryptography-related imports
    rsa::{pkcs8::DecodePublicKey, signature::Verifier, RsaPublicKey},
    // Serialization and IO
    serde_json::{json, Value},
    sha2::{Digest, Sha256},
    std::io::Cursor,
};

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub enum ApSignatureType {
    #[serde(alias = "rsa_signature_2017")]
    RsaSignature2017,
}

impl Display for ApSignatureType {
    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
        Debug::fmt(self, f)
    }
}

#[derive(Serialize, Deserialize, Clone, Default, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct ApSignature {
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(rename = "type")]
    pub kind: Option<ApSignatureType>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub creator: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub created: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub signature_value: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub nonce: Option<String>,
}

impl Debug for ApSignature {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "Sig[{}{}{}{}]",
            self.creator
                .as_ref()
                .map(|c| format!("by: {}", c))
                .unwrap_or_default(),
            self.created
                .as_ref()
                .map(|t| format!(", at: {}", t))
                .unwrap_or_default(),
            self.nonce
                .as_ref()
                .map(|n| format!(", nonce: {}", n))
                .unwrap_or_default(),
            self.signature_value
                .as_ref()
                .map(|n| format!(", signature: {}", n))
                .unwrap_or_default()
        )
    }
}

impl Display for ApSignature {
    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
        write!(
            f,
            "Sig[{}]",
            self.creator
                .as_ref()
                .map(|c| format!("by: {}", c))
                .unwrap_or_default()
        )
    }
}

#[cfg(not(target_arch = "wasm32"))]
fn remove_key_from_json(mut json: Value, key: &str) -> (Value, Option<Value>) {
    let removed = {
        if let Value::Object(map) = &mut json {
            map.remove(key)
        } else {
            None
        }
    };

    (json, removed)
}

#[cfg(not(target_arch = "wasm32"))]
async fn get_canonicalized_hash(loader: &ReqwestLoader, json: Value) -> String {
    let input = RemoteDocument::new(
        None,
        Some("application/ld+json".parse().unwrap()),
        json.clone().into(),
    );

    let generator = rdf_types::generator::Blank::new();
    let mut rdf = input.to_rdf(generator, &loader).await.expect("rdf failed");

    let quads: String = rdf
        .cloned_quads()
        .map(|x| x.to_string())
        .collect::<Vec<_>>()
        .join(" .\n");

    let quads = format!("{} .", quads);

    let input_quads = NQuadsParser::new()
        .for_reader(Cursor::new(quads))
        .map(|x| x.unwrap());

    let input_dataset = Dataset::from_iter(input_quads);
    let canonicalized = canonicalize(&input_dataset).unwrap();

    let mut hasher = Sha256::new();
    hasher.update(canonicalized);
    hex::encode(hasher.finalize())
}

#[cfg(not(target_arch = "wasm32"))]
pub async fn verify_jsonld_signature(json: Value, public_key_pem: String) -> Result<bool> {
    let (message, signature) = remove_key_from_json(json, "signature");

    let signature = signature.ok_or(anyhow!("signature must be Some"))?;
    let signature = serde_json::from_value::<ApSignature>(signature).map_err(anyhow::Error::msg)?;
    let loader = ReqwestLoader::new();

    let created = signature.created;
    let creator = signature.creator;

    let options =
        json!({"creator": creator, "created": created, "@context": "https://w3id.org/security/v1"});
    let options_hex = get_canonicalized_hash(&loader, options).await;
    let message_hex = get_canonicalized_hash(&loader, message).await;
    let combined_hex = format!("{options_hex}{message_hex}");

    let public_key =
        RsaPublicKey::from_public_key_pem(public_key_pem.trim_end()).map_err(|e| anyhow!(e))?;

    let verifying_key = rsa::pkcs1v15::VerifyingKey::<Sha256>::new(public_key.clone());
    let signature = BASE64_STANDARD
        .decode(signature.signature_value.unwrap())
        .map_err(anyhow::Error::msg)?;
    let signature = rsa::pkcs1v15::Signature::try_from(signature.as_slice()).unwrap();
    Ok(verifying_key
        .verify(combined_hex.as_bytes(), &signature)
        .is_ok())
}
