// Copyright 2020 The Matrix.org Foundation C.I.C.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::{collections::BTreeMap, convert::TryInto, sync::Arc};

use aes_gcm::{
    aead::{generic_array::GenericArray, Aead, NewAead},
    Aes256Gcm,
};
use getrandom::getrandom;
use matrix_sdk_common::locks::Mutex;
use olm_rs::pk::OlmPkSigning;
#[cfg(test)]
use olm_rs::{errors::OlmUtilityError, utility::OlmUtility};
use ruma::{
    encryption::{CrossSigningKey, DeviceKeys, KeyUsage},
    serde::CanonicalJsonValue,
    DeviceKeyAlgorithm, DeviceKeyId, UserId,
};
use serde::{Deserialize, Serialize};
use serde_json::{json, Error as JsonError, Value};
use thiserror::Error;
use zeroize::Zeroizing;

use crate::{
    error::SignatureError,
    identities::{MasterPubkey, SelfSigningPubkey, UserSigningPubkey},
    utilities::{decode_url_safe as decode, encode_url_safe as encode, DecodeError},
    UserIdentity,
};

const NONCE_SIZE: usize = 12;

/// Error type reporting failures in the Signign operations.
#[derive(Debug, Error)]
pub enum SigningError {
    /// Error decoding the base64 encoded pickle data.
    #[error(transparent)]
    Decode(#[from] DecodeError),

    /// Error decrypting the pickled signing seed
    #[error("Error decrypting the pickled signign seed")]
    Decryption(String),

    /// Error deserializing the pickle data.
    #[error(transparent)]
    Json(#[from] JsonError),
}

#[derive(Clone)]
pub struct Signing {
    inner: Arc<Mutex<OlmPkSigning>>,
    seed: Arc<Zeroizing<Vec<u8>>>,
    public_key: PublicSigningKey,
}

impl std::fmt::Debug for Signing {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("Signing").field("public_key", &self.public_key.as_str()).finish()
    }
}

impl PartialEq for Signing {
    fn eq(&self, other: &Signing) -> bool {
        self.seed == other.seed
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
struct InnerPickle {
    version: u8,
    nonce: String,
    ciphertext: String,
}

#[derive(Clone, PartialEq, Debug)]
pub struct MasterSigning {
    pub inner: Signing,
    pub public_key: MasterPubkey,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct PickledMasterSigning {
    pickle: PickledSigning,
    public_key: CrossSigningKey,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct PickledUserSigning {
    pickle: PickledSigning,
    public_key: CrossSigningKey,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct PickledSelfSigning {
    pickle: PickledSigning,
    public_key: CrossSigningKey,
}

impl Signature {
    #[cfg(test)]
    pub fn as_str(&self) -> &str {
        &self.0
    }
}

impl PickledSigning {
    pub fn as_str(&self) -> &str {
        &self.0
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PublicSigningKey(Arc<str>);

impl PublicSigningKey {
    pub fn as_str(&self) -> &str {
        &self.0
    }

    #[allow(clippy::inherent_to_string)]
    fn to_string(&self) -> String {
        self.0.to_string()
    }
}

impl MasterSigning {
    pub async fn pickle(&self, pickle_key: &[u8]) -> PickledMasterSigning {
        let pickle = self.inner.pickle(pickle_key).await;
        let public_key = self.public_key.clone().into();
        PickledMasterSigning { pickle, public_key }
    }

    pub fn from_pickle(
        pickle: PickledMasterSigning,
        pickle_key: &[u8],
    ) -> Result<Self, SigningError> {
        let inner = Signing::from_pickle(pickle.pickle, pickle_key)?;

        Ok(Self { inner, public_key: pickle.public_key.into() })
    }

    pub async fn sign_subkey<'a>(&self, subkey: &mut CrossSigningKey) {
        let subkey_wihtout_signatures = json!({
            "user_id": subkey.user_id.clone(),
            "keys": subkey.keys.clone(),
            "usage": subkey.usage.clone(),
        });

        let message = serde_json::to_string(&subkey_wihtout_signatures)
            .expect("Can't serialize cross signing subkey");
        let signature = self.inner.sign(&message).await;

        subkey
            .signatures
            .entry(self.public_key.user_id().to_owned())
            .or_insert_with(BTreeMap::new)
            .insert(
                DeviceKeyId::from_parts(
                    DeviceKeyAlgorithm::Ed25519,
                    self.inner.public_key().as_str().into(),
                )
                .to_string(),
                signature.0,
            );
    }
}

impl UserSigning {
    pub async fn pickle(&self, pickle_key: &[u8]) -> PickledUserSigning {
        let pickle = self.inner.pickle(pickle_key).await;
        let public_key = self.public_key.clone().into();
        PickledUserSigning { pickle, public_key }
    }

    pub async fn sign_user(
        &self,
        user: &UserIdentity,
    ) -> Result<BTreeMap<UserId, BTreeMap<String, Value>>, SignatureError> {
        let user_master: &CrossSigningKey = user.master_key().as_ref();
        let signature = self.inner.sign_json(serde_json::to_value(user_master)?).await?;

        let mut signatures = BTreeMap::new();

        signatures
            .entry(self.public_key.user_id().to_owned())
            .or_insert_with(BTreeMap::new)
            .insert(
                DeviceKeyId::from_parts(
                    DeviceKeyAlgorithm::Ed25519,
                    self.inner.public_key.as_str().into(),
                )
                .to_string(),
                serde_json::to_value(signature.0)?,
            );

        Ok(signatures)
    }

    pub fn from_pickle(
        pickle: PickledUserSigning,
        pickle_key: &[u8],
    ) -> Result<Self, SigningError> {
        let inner = Signing::from_pickle(pickle.pickle, pickle_key)?;

        Ok(Self { inner, public_key: pickle.public_key.into() })
    }
}

impl SelfSigning {
    pub async fn pickle(&self, pickle_key: &[u8]) -> PickledSelfSigning {
        let pickle = self.inner.pickle(pickle_key).await;
        let public_key = self.public_key.clone().into();
        PickledSelfSigning { pickle, public_key }
    }

    pub async fn sign_device_helper(&self, value: Value) -> Result<Signature, SignatureError> {
        self.inner.sign_json(value).await
    }

    pub async fn sign_device(&self, device_keys: &mut DeviceKeys) -> Result<(), SignatureError> {
        // Create a copy of the device keys containing only fields that will
        // get signed.
        let json_device = json!({
            "user_id": device_keys.user_id,
            "device_id": device_keys.device_id,
            "algorithms": device_keys.algorithms,
            "keys": device_keys.keys,
        });

        let signature = self.sign_device_helper(json_device).await?;

        device_keys
            .signatures
            .entry(self.public_key.user_id().to_owned())
            .or_insert_with(BTreeMap::new)
            .insert(
                DeviceKeyId::from_parts(
                    DeviceKeyAlgorithm::Ed25519,
                    self.inner.public_key.as_str().into(),
                ),
                signature.0,
            );

        Ok(())
    }

    pub fn from_pickle(
        pickle: PickledSelfSigning,
        pickle_key: &[u8],
    ) -> Result<Self, SigningError> {
        let inner = Signing::from_pickle(pickle.pickle, pickle_key)?;

        Ok(Self { inner, public_key: pickle.public_key.into() })
    }
}

#[derive(Clone, PartialEq, Debug)]
pub struct SelfSigning {
    pub inner: Signing,
    pub public_key: SelfSigningPubkey,
}

#[derive(Clone, PartialEq, Debug)]
pub struct UserSigning {
    pub inner: Signing,
    pub public_key: UserSigningPubkey,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PickledSignings {
    pub master_key: Option<PickledMasterSigning>,
    pub user_signing_key: Option<PickledUserSigning>,
    pub self_signing_key: Option<PickledSelfSigning>,
}

#[derive(Debug, Clone)]
pub struct Signature(String);

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PickledSigning(String);

impl Signing {
    pub fn new() -> Self {
        let seed = OlmPkSigning::generate_seed();
        Self::from_seed(seed)
    }

    pub fn from_seed(seed: Vec<u8>) -> Self {
        let inner = OlmPkSigning::new(seed.clone()).expect("Unable to create pk signing object");
        let public_key = PublicSigningKey(inner.public_key().into());

        Signing {
            inner: Arc::new(Mutex::new(inner)),
            seed: Arc::new(Zeroizing::from(seed)),
            public_key,
        }
    }

    pub fn from_pickle(pickle: PickledSigning, pickle_key: &[u8]) -> Result<Self, SigningError> {
        let pickled: InnerPickle = serde_json::from_str(pickle.as_str())?;

        let key = GenericArray::from_slice(pickle_key);
        let cipher = Aes256Gcm::new(key);

        let nonce = decode(pickled.nonce)?;
        let nonce = GenericArray::from_slice(&nonce);
        let ciphertext = &decode(pickled.ciphertext)?;

        let seed = cipher
            .decrypt(nonce, ciphertext.as_slice())
            .map_err(|e| SigningError::Decryption(e.to_string()))?;

        Ok(Self::from_seed(seed))
    }

    pub async fn pickle(&self, pickle_key: &[u8]) -> PickledSigning {
        let key = GenericArray::from_slice(pickle_key);
        let cipher = Aes256Gcm::new(key);

        let mut nonce = vec![0u8; NONCE_SIZE];
        getrandom(&mut nonce).expect("Can't generate nonce to pickle the signing object");
        let nonce = GenericArray::from_slice(nonce.as_slice());

        let ciphertext =
            cipher.encrypt(nonce, self.seed.as_slice()).expect("Can't encrypt signing pickle");

        let ciphertext = encode(ciphertext);

        let pickle = InnerPickle { version: 1, nonce: encode(nonce.as_slice()), ciphertext };

        PickledSigning(serde_json::to_string(&pickle).expect("Can't encode pickled signing"))
    }

    pub fn public_key(&self) -> &PublicSigningKey {
        &self.public_key
    }

    pub fn cross_signing_key(&self, user_id: UserId, usage: KeyUsage) -> CrossSigningKey {
        let mut keys = BTreeMap::new();

        keys.insert(
            DeviceKeyId::from_parts(DeviceKeyAlgorithm::Ed25519, self.public_key().as_str().into())
                .to_string(),
            self.public_key().to_string(),
        );

        CrossSigningKey::new(user_id, vec![usage], keys, BTreeMap::new())
    }

    #[cfg(test)]
    pub async fn verify(
        &self,
        message: &str,
        signature: &Signature,
    ) -> Result<bool, OlmUtilityError> {
        let utility = OlmUtility::new();
        utility.ed25519_verify(self.public_key.as_str(), message, signature.as_str())
    }

    pub async fn sign_json(&self, mut json: Value) -> Result<Signature, SignatureError> {
        let json_object = json.as_object_mut().ok_or(SignatureError::NotAnObject)?;
        let _ = json_object.remove("signatures");
        let canonical_json: CanonicalJsonValue =
            json.try_into().expect("Can't canonicalize the json value");
        Ok(self.sign(&canonical_json.to_string()).await)
    }

    pub async fn sign(&self, message: &str) -> Signature {
        Signature(self.inner.lock().await.sign(message))
    }
}
