//! Transaction types.

use borsh::{BorshDeserialize, BorshSerialize};

use super::{AccountId, Action, CryptoHash, PublicKey, SecretKey, Signature};

/// An unsigned transaction.
#[derive(Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
pub struct Transaction {
    /// The account that signs and pays for the transaction.
    pub signer_id: AccountId,
    /// The public key of the signer.
    pub public_key: PublicKey,
    /// Nonce for replay protection (must be greater than previous nonce).
    pub nonce: u64,
    /// The account that receives the transaction.
    pub receiver_id: AccountId,
    /// A recent block hash for transaction validity.
    pub block_hash: CryptoHash,
    /// The actions to execute.
    pub actions: Vec<Action>,
}

impl Transaction {
    /// Create a new transaction.
    pub fn new(
        signer_id: AccountId,
        public_key: PublicKey,
        nonce: u64,
        receiver_id: AccountId,
        block_hash: CryptoHash,
        actions: Vec<Action>,
    ) -> Self {
        Self {
            signer_id,
            public_key,
            nonce,
            receiver_id,
            block_hash,
            actions,
        }
    }

    /// Get the hash of this transaction (for signing).
    pub fn get_hash(&self) -> CryptoHash {
        let bytes = borsh::to_vec(self).expect("transaction serialization should never fail");
        CryptoHash::hash(&bytes)
    }

    /// Get the raw bytes of this transaction (for signing).
    pub fn get_hash_and_size(&self) -> (CryptoHash, usize) {
        let bytes = borsh::to_vec(self).expect("transaction serialization should never fail");
        (CryptoHash::hash(&bytes), bytes.len())
    }

    /// Sign this transaction with a secret key.
    pub fn sign(self, signer: &SecretKey) -> SignedTransaction {
        let hash = self.get_hash();
        let signature = signer.sign(hash.as_bytes());
        SignedTransaction {
            transaction: self,
            signature,
        }
    }
}

/// A signed transaction ready to be sent.
#[derive(Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
pub struct SignedTransaction {
    /// The unsigned transaction.
    pub transaction: Transaction,
    /// The signature.
    pub signature: Signature,
}

impl SignedTransaction {
    /// Get the hash of the signed transaction (transaction hash).
    pub fn get_hash(&self) -> CryptoHash {
        self.transaction.get_hash()
    }

    /// Serialize to bytes for RPC submission.
    pub fn to_bytes(&self) -> Vec<u8> {
        borsh::to_vec(self).expect("signed transaction serialization should never fail")
    }

    /// Serialize to base64 for RPC submission.
    pub fn to_base64(&self) -> String {
        use base64::{engine::general_purpose::STANDARD, Engine as _};
        STANDARD.encode(self.to_bytes())
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_transaction_hash() {
        let secret = SecretKey::generate_ed25519();
        let public = secret.public_key();

        let tx = Transaction::new(
            "alice.testnet".parse().unwrap(),
            public,
            1,
            "bob.testnet".parse().unwrap(),
            CryptoHash::ZERO,
            vec![],
        );

        let hash = tx.get_hash();
        assert!(!hash.is_zero());
    }

    #[test]
    fn test_sign_transaction() {
        let secret = SecretKey::generate_ed25519();
        let public = secret.public_key();

        let tx = Transaction::new(
            "alice.testnet".parse().unwrap(),
            public,
            1,
            "bob.testnet".parse().unwrap(),
            CryptoHash::ZERO,
            vec![],
        );

        let signed = tx.sign(&secret);
        assert!(!signed.to_bytes().is_empty());
    }
}
