// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0

// for time being assume the bandwidth credential consists of public identity of the requester
// and private (though known... just go along with it) infinite bandwidth value
// right now this has no double-spending protection, spender binding, etc
// it's the simplest possible case

use cosmrs::tendermint::hash::Algorithm;
use cosmrs::tendermint::Hash;
use nym_coconut_interface::{
    hash_to_scalar, prepare_blind_sign, Attribute, BlindSignRequest, Credential, Parameters,
    PrivateAttribute, PublicAttribute, Signature, VerificationKey,
};
use nym_crypto::asymmetric::{encryption, identity};

use super::utils::prepare_credential_for_spending;
use crate::error::Error;

pub const PUBLIC_ATTRIBUTES: u32 = 2;
pub const PRIVATE_ATTRIBUTES: u32 = 2;
pub const TOTAL_ATTRIBUTES: u32 = PUBLIC_ATTRIBUTES + PRIVATE_ATTRIBUTES;

pub struct BandwidthVoucher {
    // a random secret value generated by the client used for double-spending detection
    serial_number: PrivateAttribute,
    // a random secret value generated by the client used to bind multiple credentials together
    binding_number: PrivateAttribute,
    // the value (e.g., bandwidth) encoded in this voucher
    voucher_value: PublicAttribute,
    // the plain text value (e.g., bandwidth) encoded in this voucher
    voucher_value_plain: String,
    // a field with public information, e.g., type of voucher, interval etc.
    voucher_info: PublicAttribute,
    // the plain text information
    voucher_info_plain: String,
    // the hash of the deposit transaction
    tx_hash: Hash,
    // base58 encoded private key ensuring the depositer requested these attributes
    signing_key: identity::PrivateKey,
    // base58 encoded private key ensuring only this client receives the signature share
    encryption_key: encryption::PrivateKey,
    pedersen_commitments_openings: Vec<Attribute>,
    blind_sign_request: BlindSignRequest,
}

impl BandwidthVoucher {
    pub fn new(
        params: &Parameters,
        voucher_value: String,
        voucher_info: String,
        tx_hash: Hash,
        signing_key: identity::PrivateKey,
        encryption_key: encryption::PrivateKey,
    ) -> Self {
        let serial_number = params.random_scalar();
        let binding_number = params.random_scalar();
        let voucher_value_plain = voucher_value.clone();
        let voucher_info_plain = voucher_info.clone();
        let voucher_value = hash_to_scalar(voucher_value.as_bytes());
        let voucher_info = hash_to_scalar(voucher_info.as_bytes());
        let (pedersen_commitments_openings, blind_sign_request) = prepare_blind_sign(
            params,
            &[serial_number, binding_number],
            &[voucher_value, voucher_info],
        )
        .unwrap();
        BandwidthVoucher {
            serial_number,
            binding_number,
            voucher_value,
            voucher_value_plain,
            voucher_info,
            voucher_info_plain,
            tx_hash,
            signing_key,
            encryption_key,
            pedersen_commitments_openings,
            blind_sign_request,
        }
    }

    pub fn to_bytes(&self) -> Vec<u8> {
        let serial_number_b = self.serial_number.to_bytes();
        let binding_number_b = self.binding_number.to_bytes();
        let voucher_value_plain_b = self.voucher_value_plain.as_bytes();
        let voucher_info_plain_b = self.voucher_info_plain.as_bytes();
        let tx_hash_b = self.tx_hash.as_bytes();
        let signing_key_b = self.signing_key.to_bytes();
        let encryption_key_b = self.encryption_key.to_bytes();
        let blind_sign_request_b = self.blind_sign_request.to_bytes();

        let mut ret = Vec::new();

        ret.extend_from_slice(&serial_number_b);
        ret.extend_from_slice(&binding_number_b);
        ret.extend_from_slice(tx_hash_b);
        ret.extend_from_slice(&signing_key_b);
        ret.extend_from_slice(&encryption_key_b);
        ret.extend_from_slice(&(voucher_value_plain_b.len() as u64).to_be_bytes());
        ret.extend_from_slice(&(voucher_info_plain_b.len() as u64).to_be_bytes());
        ret.extend_from_slice(&(blind_sign_request_b.len() as u64).to_be_bytes());
        ret.extend_from_slice(&(self.pedersen_commitments_openings.len() as u64).to_be_bytes());
        ret.extend_from_slice(voucher_value_plain_b);
        ret.extend_from_slice(voucher_info_plain_b);
        ret.extend_from_slice(&blind_sign_request_b);
        for commitment in self.pedersen_commitments_openings.iter() {
            ret.extend_from_slice(&commitment.to_bytes());
        }

        ret
    }

    pub fn try_from_bytes(bytes: &[u8]) -> Result<Self, Error> {
        if bytes.len() < 32 * 5 + 4 * 8 {
            return Err(Error::BandwidthVoucherDeserializationError(format!(
                "Less then {} bytes needed",
                32 * 5 + 4 * 8
            )));
        }
        let mut buff = [0u8; 32];
        let mut small_buff = [0u8; 8];
        let scalar_err =
            || Error::BandwidthVoucherDeserializationError(String::from("Invalid Scalar"));
        buff.copy_from_slice(&bytes[..32]);
        let serial_number = Option::<PrivateAttribute>::from(PrivateAttribute::from_bytes(&buff))
            .ok_or_else(scalar_err)?;
        buff.copy_from_slice(&bytes[32..2 * 32]);
        let binding_number = Option::<PrivateAttribute>::from(PrivateAttribute::from_bytes(&buff))
            .ok_or_else(scalar_err)?;
        buff.copy_from_slice(&bytes[2 * 32..3 * 32]);
        let tx_hash = Hash::from_bytes(Algorithm::Sha256, &buff).map_err(|_| {
            Error::BandwidthVoucherDeserializationError(String::from("Invalid transaction Hash"))
        })?;
        buff.copy_from_slice(&bytes[3 * 32..4 * 32]);
        let signing_key = identity::PrivateKey::from_bytes(&buff).map_err(|_| {
            Error::BandwidthVoucherDeserializationError(String::from("Invalid key"))
        })?;
        buff.copy_from_slice(&bytes[4 * 32..5 * 32]);
        let encryption_key = encryption::PrivateKey::from_bytes(&buff).map_err(|_| {
            Error::BandwidthVoucherDeserializationError(String::from("Invalid key"))
        })?;
        small_buff.copy_from_slice(&bytes[5 * 32..5 * 32 + 8]);
        let voucher_value_plain_no = u64::from_be_bytes(small_buff) as usize;
        small_buff.copy_from_slice(&bytes[5 * 32 + 8..5 * 32 + 2 * 8]);
        let voucher_info_plain_no = u64::from_be_bytes(small_buff) as usize;
        small_buff.copy_from_slice(&bytes[5 * 32 + 2 * 8..5 * 32 + 3 * 8]);
        let blind_sign_request_no = u64::from_be_bytes(small_buff) as usize;
        small_buff.copy_from_slice(&bytes[5 * 32 + 3 * 8..5 * 32 + 4 * 8]);
        let pedersen_commitments_openings_no = u64::from_be_bytes(small_buff) as usize;

        let total_length = 32 * 5
            + 4 * 8
            + voucher_value_plain_no
            + voucher_info_plain_no
            + blind_sign_request_no
            + pedersen_commitments_openings_no * 32;
        if bytes.len() != total_length {
            return Err(Error::BandwidthVoucherDeserializationError(format!(
                "Expected {total_length} bytes",
            )));
        }

        let utf_err = |_| {
            Err(Error::BandwidthVoucherDeserializationError(String::from(
                "Invalid UTF8 string",
            )))
        };
        let mut var_length_pointer = 5 * 32 + 4 * 8;
        let voucher_value_plain = String::from_utf8(
            bytes[var_length_pointer..var_length_pointer + voucher_value_plain_no].to_vec(),
        )
        .or_else(utf_err)?;
        let voucher_value = hash_to_scalar(&voucher_value_plain);
        var_length_pointer += voucher_value_plain_no;
        let voucher_info_plain = String::from_utf8(
            bytes[var_length_pointer..var_length_pointer + voucher_info_plain_no].to_vec(),
        )
        .or_else(utf_err)?;
        let voucher_info = hash_to_scalar(&voucher_info_plain);
        var_length_pointer += voucher_info_plain_no;
        let blind_sign_request = BlindSignRequest::from_bytes(
            &bytes[var_length_pointer..var_length_pointer + blind_sign_request_no],
        )?;
        var_length_pointer += blind_sign_request_no;

        let mut pedersen_commitments_openings = Vec::new();
        for _ in 0..pedersen_commitments_openings_no {
            buff.copy_from_slice(&bytes[var_length_pointer..var_length_pointer + 32]);
            let commitment =
                Option::<Attribute>::from(Attribute::from_bytes(&buff)).ok_or_else(scalar_err)?;
            var_length_pointer += 32;
            pedersen_commitments_openings.push(commitment);
        }

        Ok(Self {
            serial_number,
            binding_number,
            voucher_value,
            voucher_value_plain,
            voucher_info,
            voucher_info_plain,
            tx_hash,
            signing_key,
            encryption_key,
            pedersen_commitments_openings,
            blind_sign_request,
        })
    }

    /// Check if the plain values correspond to the PublicAttributes
    pub fn verify_against_plain(values: &[PublicAttribute], plain_values: &[String]) -> bool {
        values.len() == 2
            && plain_values.len() == 2
            && values[0] == hash_to_scalar(&plain_values[0])
            && values[1] == hash_to_scalar(&plain_values[1])
    }

    pub fn tx_hash(&self) -> &Hash {
        &self.tx_hash
    }

    pub fn get_public_attributes(&self) -> Vec<PublicAttribute> {
        vec![self.voucher_value, self.voucher_info]
    }

    pub fn encryption_key(&self) -> &encryption::PrivateKey {
        &self.encryption_key
    }

    pub fn pedersen_commitments_openings(&self) -> &Vec<Attribute> {
        &self.pedersen_commitments_openings
    }

    pub fn blind_sign_request(&self) -> &BlindSignRequest {
        &self.blind_sign_request
    }

    pub fn get_voucher_value(&self) -> String {
        self.voucher_value_plain.clone()
    }

    pub fn get_public_attributes_plain(&self) -> Vec<String> {
        vec![
            self.voucher_value_plain.clone(),
            self.voucher_info_plain.clone(),
        ]
    }

    pub fn get_private_attributes(&self) -> Vec<PrivateAttribute> {
        vec![self.serial_number, self.binding_number]
    }

    pub fn sign(&self, request: &BlindSignRequest) -> identity::Signature {
        let mut message = request.to_bytes();
        message.extend_from_slice(self.tx_hash.to_string().as_bytes());
        self.signing_key.sign(&message)
    }
}

pub fn prepare_for_spending(
    voucher_value: u64,
    voucher_info: String,
    serial_number: PrivateAttribute,
    binding_number: PrivateAttribute,
    epoch_id: u64,
    signature: &Signature,
    verification_key: &VerificationKey,
) -> Result<Credential, Error> {
    let params = Parameters::new(TOTAL_ATTRIBUTES)?;

    prepare_credential_for_spending(
        &params,
        voucher_value,
        voucher_info,
        serial_number,
        binding_number,
        epoch_id,
        signature,
        verification_key,
    )
}

#[cfg(test)]
mod test {
    use super::*;
    use cosmrs::tendermint::hash::Algorithm;
    use nym_coconut_interface::Base58;
    use rand::rngs::OsRng;

    fn voucher_fixture() -> BandwidthVoucher {
        let params = Parameters::new(4).unwrap();
        let mut rng = OsRng;
        BandwidthVoucher::new(
            &params,
            "1234".to_string(),
            "voucher info".to_string(),
            Hash::from_bytes(Algorithm::Sha256, &[0; 32]).unwrap(),
            identity::PrivateKey::from_base58_string(
                identity::KeyPair::new(&mut rng)
                    .private_key()
                    .to_base58_string(),
            )
            .unwrap(),
            encryption::PrivateKey::from_bytes(
                &encryption::KeyPair::new(&mut rng).private_key().to_bytes(),
            )
            .unwrap(),
        )
    }

    #[test]
    fn serde_voucher() {
        let voucher = voucher_fixture();
        let bytes = voucher.to_bytes();
        let deserialized_voucher = BandwidthVoucher::try_from_bytes(&bytes).unwrap();
        assert_eq!(voucher.serial_number, deserialized_voucher.serial_number);
        assert_eq!(voucher.binding_number, deserialized_voucher.binding_number);
        assert_eq!(voucher.voucher_value, deserialized_voucher.voucher_value);
        assert_eq!(
            voucher.voucher_value_plain,
            deserialized_voucher.voucher_value_plain
        );
        assert_eq!(voucher.voucher_info, deserialized_voucher.voucher_info);
        assert_eq!(
            voucher.voucher_info_plain,
            deserialized_voucher.voucher_info_plain
        );
        assert_eq!(voucher.tx_hash, deserialized_voucher.tx_hash);
        assert_eq!(
            voucher.signing_key.to_string(),
            deserialized_voucher.signing_key.to_string()
        );
        assert_eq!(
            voucher.encryption_key.to_string(),
            deserialized_voucher.encryption_key.to_string()
        );
        assert_eq!(
            voucher.pedersen_commitments_openings,
            deserialized_voucher.pedersen_commitments_openings
        );
        assert_eq!(
            voucher.blind_sign_request.to_bs58(),
            deserialized_voucher.blind_sign_request.to_bs58()
        );
    }

    #[test]
    fn voucher_consistency() {
        let voucher = voucher_fixture();
        assert!(!BandwidthVoucher::verify_against_plain(
            &[],
            &voucher.get_public_attributes_plain()
        ));
        assert!(!BandwidthVoucher::verify_against_plain(
            &voucher.get_public_attributes(),
            &[],
        ));
        assert!(!BandwidthVoucher::verify_against_plain(
            &voucher.get_public_attributes(),
            &[
                voucher.get_public_attributes_plain()[0].clone(),
                String::new()
            ]
        ));
        assert!(!BandwidthVoucher::verify_against_plain(
            &voucher.get_public_attributes(),
            &[
                String::new(),
                voucher.get_public_attributes_plain()[1].clone()
            ]
        ));
        assert!(!BandwidthVoucher::verify_against_plain(
            &[voucher.get_public_attributes()[0], Attribute::one()],
            &voucher.get_public_attributes_plain()
        ));
        assert!(!BandwidthVoucher::verify_against_plain(
            &[Attribute::one(), voucher.get_public_attributes()[1]],
            &voucher.get_public_attributes_plain()
        ));
        assert!(BandwidthVoucher::verify_against_plain(
            &voucher.get_public_attributes(),
            &voucher.get_public_attributes_plain()
        ));
    }
}
