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

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

/// 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;

/// Access list entry (address + storage keys)
#[derive(Debug, Clone, Default)]
pub struct AccessListItem {
    pub address: Address,
    pub storage_keys: Vec<B256>,
}

/// 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: Address,
    pub value: U256,
    pub data: Bytes,
    pub access_list: Vec<AccessListItem>,  // Added: access_list field
    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: Address::ZERO,
            value: U256::ZERO,
            data: Bytes::new(),
            access_list: Vec::new(),  // Default: empty access list
            max_priority_fee_per_gas: 0,
            max_fee_per_gas: 0,
            fee_token: Address::ZERO,
            fee_payer: None, // None means use sender (sender-pays)
            max_fee_per_gas_usd: Some(40_000_000_000), // $0.04/gas default
            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: Address) -> Self {
        self.to = to;
        self
    }

    /// Set recipient address (convenience method)
    pub fn to_address(mut self, address: Address) -> Self {
        self.to = 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, big-endian for ERC20)
        let mut amount_padded = [0u8; 32];
        let amount_bytes: [u8; 32] = amount.to_be_bytes();
        amount_padded.copy_from_slice(&amount_bytes);
        data.extend_from_slice(&amount_padded);

        self.to = 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(),
            access_list: self.access_list.clone(),  // Added: access_list
            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,
            // NOTE: fee_payer is resolved to sender during signing (line below)
            // This is just for the RLP encoding, actual fee_payer is set in sign()
            fee_payer: self.fee_payer.unwrap_or(Address::ZERO),
            max_fee_per_gas_usd: self.max_fee_per_gas_usd.unwrap_or(40_000_000_000),
            fee_payer_signature: self.fee_payer_signature.clone(),
        }
    }

    /// Compute the sender signature hash for this transaction
    /// Matches node's encode_for_signing EXACTLY (without fee_payer_signature)
    /// NOTE: Node calculates hash WITH type byte (0x7a) AND with outer RLP list header!
    pub fn signature_hash(&self) -> B256 {
        let fields = self.build();

        // Get payload length (same as node's payload_len_for_signature)
        let payload_len = fields.payload_len_for_signing();

        // Build buffer: type byte + RLP list header + fields
        let mut full_buf = Vec::new();
        full_buf.push(TX_TYPE_USD_MULTI_TOKEN);
        alloy_rlp::Header { list: true, payload_length: payload_len }.encode(&mut full_buf);

        // Encode all fields
        fields.encode_for_signing(&mut full_buf);

        // DEBUG: Log signature_hash
        eprintln!("[SDK] signature_hash input: {:02x?}", full_buf);
        eprintln!("[SDK] signature_hash input (hex): {}", hex::encode(&full_buf));

        let hash = keccak256(&full_buf);
        eprintln!("[SDK] signature_hash result: {}", hex::encode(hash));

        hash
    }

    /// Sign the transaction with a signer
    pub fn sign(&self, signer: &impl Signer) -> Result<SignedTx, Box<dyn std::error::Error>> {
        // DEBUG: Log fee_payer before signing
        eprintln!("[SDK] sign() called: self.fee_payer = {:?}", self.fee_payer);

        let hash = self.signature_hash();
        let signature = signer.sign_hash(&hash)?;

        let sender = signer.address();
        eprintln!("[SDK] sender = {:?}", sender);

        // Determine fee_payer: use sender if not specified
        let fee_payer = self.fee_payer.unwrap_or(sender);
        eprintln!("[SDK] fee_payer resolved = {:?}", fee_payer);

        // Auto-generate fee_payer_signature if not set
        // When fee_payer == sender, use the SAME signature for both fee_payer_signature and transaction signature
        // When fee_payer != sender, need to sign fee_payer_signature_hash separately
        let fee_payer_signature = if self.fee_payer_signature.is_none() {
            if fee_payer == sender {
                // fee_payer == sender: use the same r, s, v from the sender's signature
                Some(signature.to_bytes())
            } else {
                // fee_payer != sender: requires external fee_payer_signature
                return Err("fee_payer != sender requires external fee_payer_signature".into());
            }
        } else {
            self.fee_payer_signature.clone()
        };

        // Build fields with resolved fee_payer and fee_payer_signature
        let fields = TxFields {
            chain_id: self.chain_id,
            nonce: self.nonce,
            gas_limit: self.gas_limit,
            to: self.to,
            value: self.value.clone(),
            data: self.data.clone(),
            access_list: self.access_list.clone(),  // Added: access_list
            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,
            max_fee_per_gas_usd: self.max_fee_per_gas_usd.unwrap_or(40_000_000_000),
            fee_payer_signature,
        };

        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 (r + s + v) - matches node's expected layout
    /// Uses v = 27 + y_parity format (Ethereum legacy format)
    pub fn to_bytes(&self) -> Bytes {
        let mut buf = Vec::with_capacity(65);
        buf.extend_from_slice(self.r.as_slice()); // r first (32 bytes)
        buf.extend_from_slice(self.s.as_slice()); // s next (32 bytes)
        let v_legacy = 27 + self.v; // Convert y_parity to legacy v format
        buf.push(v_legacy);                         // v last (1 byte)
        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: 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: Address,
    pub value: U256,
    pub data: Bytes,
    pub access_list: Vec<AccessListItem>,  // Added: access_list field
    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 access list (EIP-2930 format)
    fn encode_access_list(access_list: &[AccessListItem], out: &mut Vec<u8>) {
        // Access list is encoded as a list of [address, [storage_keys...]]
        if access_list.is_empty() {
            // Empty list: 0xc0 (empty list)
            alloy_rlp::Header { list: true, payload_length: 0 }.encode(out);
        } else {
            let payload_len = access_list.iter().map(|item| {
                item.address.length() + item.storage_keys.iter().map(|k| k.length()).sum::<usize>()
            }).sum::<usize>() + access_list.len(); // +1 for each inner list header

            alloy_rlp::Header { list: true, payload_length: payload_len }.encode(out);

            for item in access_list {
                // Inner list: [address, [storage_keys...]]
                let inner_len = item.address.length() +
                    item.storage_keys.iter().map(|k| k.length()).sum::<usize>() +
                    1; // for inner list header
                alloy_rlp::Header { list: true, payload_length: inner_len }.encode(out);
                item.address.encode(out);
                // Storage keys list
                if item.storage_keys.is_empty() {
                    alloy_rlp::Header { list: true, payload_length: 0 }.encode(out);
                } else {
                    let keys_len: usize = item.storage_keys.iter().map(|k| k.length()).sum();
                    alloy_rlp::Header { list: true, payload_length: keys_len }.encode(out);
                    for key in &item.storage_keys {
                        key.encode(out);
                    }
                }
            }
        }
    }

    /// Encode for signing (without fee_payer_signature)
    /// EXACTLY matches node's encode_for_signing
    pub fn encode_for_signing(&self, out: &mut Vec<u8>) {
        // NOTE: Node encodes fields directly without outer list header
        // Order must match node exactly!

        ChainId::from(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);
        // Direct Address encoding (same as EIP-1559)
        self.to.encode(out);
        self.value.encode(out);
        self.data.encode(out);
        // Access list (added for 0x7a compatibility)
        Self::encode_access_list(&self.access_list, out);
        self.fee_token.encode(out);
        self.fee_payer.encode(out);
        self.max_fee_per_gas_usd.encode(out);

        // DEBUG
        eprintln!("[SDK] encode_for_signing out (hex): {}", hex::encode(out));
    }

    fn access_list_len(access_list: &[AccessListItem]) -> usize {
        if access_list.is_empty() {
            return 1; // Empty list header
        }
        let keys_len: usize = access_list.iter()
            .map(|item| item.address.length() + item.storage_keys.iter().map(|k| k.length()).sum::<usize>() + 1)
            .sum();
        // Outer list header
        1 + keys_len
    }

    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() +
            self.to.length() +
            self.value.length() +
            self.data.length() +
            Self::access_list_len(&self.access_list) +
            self.fee_token.length() +
            self.fee_payer.length() +
            self.max_fee_per_gas_usd.length()
    }

    /// Encode signed transaction (with fee_payer_signature and signature)
    /// Must match node's encoding exactly
    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);
        // Direct Address encoding
        self.to.encode(&mut buf);
        self.value.encode(&mut buf);
        self.data.encode(&mut buf);
        // Access list (added for 0x7a compatibility)
        Self::encode_access_list(&self.access_list, &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.
///
/// IMPORTANT: The node encodes fields as an RLP list with a header.
/// Format: keccak256(0x7B || RLP_LIST_HEADER || chain_id || nonce || gas_limit || fee_token || fee_payer || max_fee_per_gas_usd || sender)
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 {
    use alloy_rlp::{Encodable, Header};

    // Calculate RLP payload length (sum of all field lengths)
    let payload_len = chain_id.length()
        + nonce.length()
        + gas_limit.length()
        + fee_token.length()
        + fee_payer.length()
        + max_fee_per_gas_usd.length()
        + sender.length();

    // Build the buffer: magic byte + RLP list header + fields
    let mut buf = Vec::with_capacity(1 + Header { list: true, payload_length: payload_len }.length() + payload_len);

    // Magic byte prefix (0x7B)
    buf.push(FEE_PAYER_SIGNATURE_MAGIC_BYTE);

    // RLP list header
    Header { list: true, payload_length: payload_len }.encode(&mut buf);

    // Encode all fields
    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);

    // DEBUG: Print the hash being signed
    eprintln!("[SDK] fee_payer_signature_hash input bytes: {:02x?}", buf);
    eprintln!("[SDK] fee_payer_signature_hash input (hex): {}", hex::encode(&buf));

    let hash = keccak256(&buf);
    eprintln!("[SDK] fee_payer_signature_hash result: {}", hex::encode(hash));

    hash
}

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