//! Transaction building for Altius USD multi-token transactions.
//!
//! Supports the 0x7a transaction type.

use alloy_primitives::{Address, Bytes, B256, U256, keccak256};
use alloy_rlp::Encodable;

/// Transaction type for USD Multi-Token (EIP-2718)
pub const TX_TYPE_USD_MULTI_TOKEN: u8 = 0x7a;

/// Magic byte for fee payer signature
pub const FEE_PAYER_SIGNATURE_MAGIC_BYTE: u8 = 0x7b;

/// Encode Option<Address> for RLP (None = empty string, Some = address)
fn encode_opt_address(addr: &Option<Address>, out: &mut Vec<u8>) {
    match addr {
        Some(a) => a.encode(out),
        None => {
            // Empty string in RLP
            out.push(0x80);
        }
    }
}

/// Get length of encoded Option<Address>
fn len_opt_address(addr: &Option<Address>) -> usize {
    match addr {
        Some(a) => a.length(),
        None => 1, // single byte for empty string
    }
}

/// TxBuilder for building USD Multi-Token transactions
#[derive(Debug, Clone)]
pub struct TxBuilder {
    pub chain_id: u64,
    pub nonce: u64,
    pub gas_limit: u64,
    pub to: Option<Address>,
    pub value: U256,
    pub data: Bytes,
    pub max_priority_fee_per_gas: u128,
    pub max_fee_per_gas: u128,
    pub fee_token: Address,
    pub fee_payer: Option<Address>,
    pub max_fee_per_gas_usd: Option<u128>,
    pub fee_payer_signature: Option<Bytes>,
}

impl Default for TxBuilder {
    fn default() -> Self {
        Self::new()
    }
}

impl TxBuilder {
    pub fn new() -> Self {
        Self {
            chain_id: 0,
            nonce: 0,
            gas_limit: 21000,
            to: None,
            value: U256::ZERO,
            data: Bytes::new(),
            max_priority_fee_per_gas: 0,
            max_fee_per_gas: 0,
            fee_token: Address::ZERO,
            fee_payer: None,
            max_fee_per_gas_usd: None,
            fee_payer_signature: None,
        }
    }

    pub fn chain_id(mut self, chain_id: u64) -> Self {
        self.chain_id = chain_id;
        self
    }

    pub fn nonce(mut self, nonce: u64) -> Self {
        self.nonce = nonce;
        self
    }

    pub fn gas_limit(mut self, gas_limit: u64) -> Self {
        self.gas_limit = gas_limit;
        self
    }

    pub fn to(mut self, to: Option<Address>) -> Self {
        self.to = to;
        self
    }

    /// Set recipient address (convenience method)
    pub fn to_address(mut self, address: Address) -> Self {
        self.to = Some(address);
        self
    }

    pub fn value(mut self, value: U256) -> Self {
        self.value = value;
        self
    }

    pub fn data(mut self, data: Bytes) -> Self {
        self.data = data;
        self
    }

    pub fn max_priority_fee_per_gas(mut self, max_priority_fee_per_gas: u128) -> Self {
        self.max_priority_fee_per_gas = max_priority_fee_per_gas;
        self
    }

    pub fn max_fee_per_gas(mut self, max_fee_per_gas: u128) -> Self {
        self.max_fee_per_gas = max_fee_per_gas;
        self
    }

    pub fn fee_token(mut self, fee_token: Address) -> Self {
        self.fee_token = fee_token;
        self
    }

    pub fn fee_payer(mut self, fee_payer: Option<Address>) -> Self {
        self.fee_payer = fee_payer;
        self
    }

    pub fn max_fee_per_gas_usd(mut self, max_fee_per_gas_usd: u128) -> Self {
        self.max_fee_per_gas_usd = Some(max_fee_per_gas_usd);
        self
    }

    pub fn fee_payer_signature(mut self, signature: Bytes) -> Self {
        self.fee_payer_signature = Some(signature);
        self
    }

    /// Build ERC20 transfer transaction
    pub fn erc20_transfer(mut self, token: Address, to: Address, amount: U256) -> Self {
        // ERC20 transfer selector: 0xa9059cbb
        let mut data = vec![0xa9, 0x05, 0x9c, 0xbb];
        // Add recipient (padded to 32 bytes)
        let mut recipient = [0u8; 32];
        recipient[12..].copy_from_slice(to.as_slice());
        data.extend_from_slice(&recipient);
        // Add amount (padded to 32 bytes)
        let mut amount_padded = [0u8; 32];
        let amount_bytes: [u8; 32] = amount.to_le_bytes();
        amount_padded.copy_from_slice(&amount_bytes);
        data.extend_from_slice(&amount_padded);

        self.to = Some(token);
        self.data = Bytes::from(data);
        self
    }

    /// Build the transaction fields for signing
    pub fn build(&self) -> TxFields {
        TxFields {
            chain_id: self.chain_id,
            nonce: self.nonce,
            gas_limit: self.gas_limit,
            to: self.to,
            value: self.value,
            data: self.data.clone(),
            max_priority_fee_per_gas: self.max_priority_fee_per_gas,
            max_fee_per_gas: self.max_fee_per_gas,
            fee_token: self.fee_token,
            fee_payer: self.fee_payer.unwrap_or(Address::ZERO),
            max_fee_per_gas_usd: self.max_fee_per_gas_usd.unwrap_or(0),
            fee_payer_signature: self.fee_payer_signature.clone(),
        }
    }

    /// Compute the sender signature hash for this transaction
    /// Matches node's encode_for_signing (without fee_payer_signature)
    pub fn signature_hash(&self) -> B256 {
        let fields = self.build();

        // Encode with type byte 0x7a prefix
        let mut buf = Vec::new();
        buf.push(TX_TYPE_USD_MULTI_TOKEN);

        // RLP encode the fields (without fee_payer_signature)
        // Format: [chainId, nonce, maxPriorityFeePerGas, maxFeePerGas, gasLimit, to, value, input, accessList, feeToken, feePayer, maxFeePerGasUsdAttodollars]
        fields.encode_for_signing(&mut buf);

        keccak256(&buf)
    }

    /// Sign the transaction with a signer
    pub fn sign(&self, signer: &impl Signer) -> Result<SignedTx, Box<dyn std::error::Error>> {
        let hash = self.signature_hash();
        let signature = signer.sign_hash(&hash)?;

        let fields = self.build();
        let y_parity = signature.y_parity();

        // Encode the signed transaction
        // Format: [chainId, nonce, ..., feePayerSignature, yParity, r, s]
        let raw_tx = fields.encode_signed(y_parity, signature.r(), signature.s());

        let tx_hash = keccak256(&raw_tx);

        Ok(SignedTx {
            raw_transaction: format!("0x{}", hex::encode(&raw_tx)),
            transaction_hash: tx_hash,
            chain_id: fields.chain_id,
            nonce: fields.nonce,
            gas_limit: fields.gas_limit,
            to: fields.to,
            value: fields.value,
            data: fields.data,
            max_priority_fee_per_gas: fields.max_priority_fee_per_gas,
            max_fee_per_gas: fields.max_fee_per_gas,
            fee_token: fields.fee_token,
            fee_payer: fields.fee_payer,
            max_fee_per_gas_usd_attodollars: fields.max_fee_per_gas_usd,
            fee_payer_signature: fields.fee_payer_signature,
            v: y_parity,
            r: signature.r(),
            s: signature.s(),
        })
    }
}

/// Signer trait for transaction signing
pub trait Signer {
    fn sign_hash(&self, hash: &B256) -> Result<Signature, Box<dyn std::error::Error>>;
    fn address(&self) -> Address;
}

/// Signature components
#[derive(Debug, Clone)]
pub struct Signature {
    pub r: B256,
    pub s: B256,
    pub v: u8,
}

impl Signature {
    pub fn new(r: B256, s: B256, v: u8) -> Self {
        Self { r, s, v }
    }

    pub fn y_parity(&self) -> u8 {
        self.v
    }

    pub fn r(&self) -> B256 {
        self.r
    }

    pub fn s(&self) -> B256 {
        self.s
    }

    /// Convert signature to bytes (v + r + s)
    pub fn to_bytes(&self) -> Bytes {
        let mut buf = vec![self.v];
        buf.extend_from_slice(self.r.as_slice());
        buf.extend_from_slice(self.s.as_slice());
        Bytes::from(buf)
    }
}

/// Signed transaction
#[derive(Debug, Clone)]
pub struct SignedTx {
    pub raw_transaction: String,
    pub transaction_hash: B256,
    pub chain_id: u64,
    pub nonce: u64,
    pub gas_limit: u64,
    pub to: Option<Address>,
    pub value: U256,
    pub data: Bytes,
    pub max_priority_fee_per_gas: u128,
    pub max_fee_per_gas: u128,
    pub fee_token: Address,
    pub fee_payer: Address,
    pub max_fee_per_gas_usd_attodollars: u128,
    pub fee_payer_signature: Option<Bytes>,
    pub v: u8,
    pub r: B256,
    pub s: B256,
}

/// Transaction fields for encoding
#[derive(Debug, Clone)]
pub struct TxFields {
    pub chain_id: u64,
    pub nonce: u64,
    pub gas_limit: u64,
    pub to: Option<Address>,
    pub value: U256,
    pub data: Bytes,
    pub max_priority_fee_per_gas: u128,
    pub max_fee_per_gas: u128,
    pub fee_token: Address,
    pub fee_payer: Address,
    pub max_fee_per_gas_usd: u128,
    pub fee_payer_signature: Option<Bytes>,
}

impl TxFields {
    /// Encode for signing (without fee_payer_signature)
    /// Matches node's encode_for_signing
    pub fn encode_for_signing(&self, out: &mut Vec<u8>) {
        // Use a list header
        let payload_len = self.payload_len_for_signing();
        alloy_rlp::Header { list: true, payload_length: payload_len }.encode(out);

        self.chain_id.encode(out);
        self.nonce.encode(out);
        self.max_priority_fee_per_gas.encode(out);
        self.max_fee_per_gas.encode(out);
        self.gas_limit.encode(out);
        encode_opt_address(&self.to, out);
        self.value.encode(out);
        self.data.encode(out);
        // Empty access list
        alloy_rlp::Header { list: true, payload_length: 0 }.encode(out);
        self.fee_token.encode(out);
        self.fee_payer.encode(out);
        self.max_fee_per_gas_usd.encode(out);
    }

    fn payload_len_for_signing(&self) -> usize {
        self.chain_id.length() +
            self.nonce.length() +
            self.max_priority_fee_per_gas.length() +
            self.max_fee_per_gas.length() +
            self.gas_limit.length() +
            len_opt_address(&self.to) +
            self.value.length() +
            self.data.length() +
            1 + // empty access list header
            self.fee_token.length() +
            self.fee_payer.length() +
            self.max_fee_per_gas_usd.length()
    }

    /// Encode signed transaction (with fee_payer_signature and signature)
    pub fn encode_signed(&self, y_parity: u8, r: B256, s: B256) -> Vec<u8> {
        let mut buf = Vec::new();

        // Type byte
        buf.push(TX_TYPE_USD_MULTI_TOKEN);

        // RLP encode fields
        let payload_len = self.payload_len_for_signed(&r, &s);
        alloy_rlp::Header { list: true, payload_length: payload_len }.encode(&mut buf);

        self.chain_id.encode(&mut buf);
        self.nonce.encode(&mut buf);
        self.max_priority_fee_per_gas.encode(&mut buf);
        self.max_fee_per_gas.encode(&mut buf);
        self.gas_limit.encode(&mut buf);
        encode_opt_address(&self.to, &mut buf);
        self.value.encode(&mut buf);
        self.data.encode(&mut buf);
        // Empty access list
        alloy_rlp::Header { list: true, payload_length: 0 }.encode(&mut buf);
        self.fee_token.encode(&mut buf);
        self.fee_payer.encode(&mut buf);
        self.max_fee_per_gas_usd.encode(&mut buf);

        // fee_payer_signature
        if let Some(ref sig) = self.fee_payer_signature {
            sig.encode(&mut buf);
        } else {
            Bytes::new().encode(&mut buf);
        }

        // Signature: y_parity, r, s
        y_parity.encode(&mut buf);
        r.encode(&mut buf);
        s.encode(&mut buf);

        buf
    }

    fn payload_len_for_signed(&self, r: &B256, s: &B256) -> usize {
        let fee_payer_sig_len = self.fee_payer_signature.as_ref()
            .map(|sig| sig.length())
            .unwrap_or_else(|| Bytes::new().length());

        self.payload_len_for_signing() +
            fee_payer_sig_len +
            1 + // y_parity
            r.length() +
            s.length()
    }
}

/// Compute fee payer signature hash
///
/// This creates the message that the fee payer signs to authorize
/// the transaction fees to be paid from their account.
pub fn fee_payer_signature_hash(
    chain_id: u64,
    nonce: u64,
    gas_limit: u64,
    fee_token: Address,
    fee_payer: Address,
    max_fee_per_gas_usd: u128,
    sender: Address,
) -> B256 {
    // RLP encode the fields
    let mut buf = Vec::new();
    chain_id.encode(&mut buf);
    nonce.encode(&mut buf);
    gas_limit.encode(&mut buf);
    fee_token.encode(&mut buf);
    fee_payer.encode(&mut buf);
    max_fee_per_gas_usd.encode(&mut buf);
    sender.encode(&mut buf);

    // Prepend magic byte 0x7B
    let mut data = vec![FEE_PAYER_SIGNATURE_MAGIC_BYTE];
    data.extend_from_slice(&buf);

    keccak256(&data)
}

/// Create a new transaction (convenience function)
pub fn create_transaction() -> TxBuilder {
    TxBuilder::new()
}
