use crate::wallet::Wallet;
use cubehash::CubeHash256;
use ed25519_dalek::{VerifyingKey, Signature, Verifier};
use rand::Rng;
use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct Transaction {
    pub sender: String,
    pub receiver: String,
    pub amount: u64,
    pub fee: u64,
    /// Random uniqueness salt. Generated by the sender and included in the
    /// signed message to prevent replay attacks via signature deduplication.
    /// Ignored for coinbase (empty sender).
    pub nonce: u64,
    pub public_key: [u8; 32],
    pub signature: Vec<u8>,
}

impl Transaction {
    pub fn new_signed(sender_wallet: &Wallet, receiver: String, amount: u64, fee: u64) -> Self {
        let sender = sender_wallet.get_address();
        // Random uniqueness salt. Masked to 53 bits so the value round-trips
        // through JSON without precision loss in JavaScript (float64 can represent
        // integers exactly up to 2^53 − 1, giving ~9 quadrillion unique values).
        let nonce: u64 = rand::rngs::OsRng.gen::<u64>() & ((1u64 << 53) - 1);

        let mut temp_tx = Transaction {
            sender: sender.clone(),
            receiver,
            amount,
            fee,
            nonce,
            public_key: sender_wallet.get_public_key_bytes(),
            signature: vec![],
        };

        let tx_bytes = bincode::serialize(&(
            &temp_tx.sender,
            &temp_tx.receiver,
            &temp_tx.amount,
            &temp_tx.fee,
            &temp_tx.nonce,
        )).unwrap();
        let signature = sender_wallet.sign(&tx_bytes);
        temp_tx.signature = signature.to_bytes().to_vec();
        temp_tx
    }

    pub fn verify(&self) -> bool {
        // Fix 1: public key must hash to sender address
        let hash: [u8; 32] = CubeHash256::digest(&self.public_key).into();
        let expected_addr = crate::wallet::encode_address(&hash);
        if expected_addr != self.sender {
            return false;
        }

        let pubkey = match VerifyingKey::from_bytes(&self.public_key) {
            Ok(pk) => pk,
            Err(_) => return false,
        };

        // Fix 2: nonce is part of the signed message
        let tx_bytes = bincode::serialize(&(
            &self.sender,
            &self.receiver,
            &self.amount,
            &self.fee,
            &self.nonce,
        )).unwrap();

        let sig_bytes: [u8; 64] = match self.signature.clone().try_into() {
            Ok(arr) => arr,
            Err(_) => return false,
        };
        let signature = Signature::from_bytes(&sig_bytes);

        pubkey.verify(&tx_bytes, &signature).is_ok()
    }
}
