// Copyright (c) Subzero Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

use serde::{Deserialize, Serialize};

use crate::{
    constants::ConfigHashPrefix,
    error::{Result, RialoError},
    keyring::Keyring,
    rpc::types::Pubkey,
    transaction::instruction::{self, Instruction},
};

/// A fully-signed transaction compatible with Rialo blockchain
///
/// This structure represents a transaction that has been completely assembled and signed,
/// ready for submission to the network. It contains both the transaction message (which
/// specifies the operations to perform) and the cryptographic signatures that authorize it.
#[derive(Debug, Serialize, Deserialize)]
pub struct Transaction {
    /// A vector of signatures, one per required signer in the transaction's `Message`
    /// The signatures appear in the same order as the signed keys in the Message accounts
    pub signatures: Vec<Vec<u8>>,
    /// The encoded transaction message containing all instructions and account references
    pub message: Vec<u8>,
}

impl Transaction {
    /// Create a new transaction with empty signatures
    pub fn new(message: Vec<u8>, num_signatures: usize) -> Self {
        Self {
            signatures: vec![vec![0u8; 64]; num_signatures],
            message,
        }
    }

    /// Serialize the transaction to wire format bytes
    pub fn serialize_wired(&self) -> Result<Vec<u8>> {
        serialize_transaction(self)
    }

    /// Deserialize a transaction from wire format bytes
    pub fn deserialize_wired(data: &[u8]) -> Result<Self> {
        deserialize_transaction(data)
    }

    /// Add a signature at the specified index
    pub fn add_signature(&mut self, index: usize, signature: Vec<u8>) -> Result<()> {
        if signature.len() != 64 {
            return Err(RialoError::Transaction(
                "Signature must be 64 bytes long".to_string(),
            ));
        }
        if index >= self.signatures.len() {
            return Err(RialoError::Transaction(format!(
                "Signature index {} out of bounds (max: {})",
                index,
                self.signatures.len() - 1
            )));
        }
        self.signatures[index] = signature;
        Ok(())
    }

    /// Add a signature for the given public key
    pub fn add_signature_for_pubkey(&mut self, pubkey: &Pubkey, signature: Vec<u8>) -> Result<()> {
        let message_header = parse_message_header(&self.message)?;
        let account_keys = parse_account_keys(&self.message, &message_header)?;

        // Find the index of the pubkey in signer accounts
        let signer_index = account_keys
            .iter()
            .take(message_header.num_required_signatures as usize)
            .position(|key| key == pubkey)
            .ok_or_else(|| {
                RialoError::Transaction(format!("Public key {} not found as a signer", pubkey))
            })?;

        self.add_signature(signer_index, signature)
    }

    /// Clear all signatures
    pub fn clear_signatures(&mut self) {
        for sig in &mut self.signatures {
            sig.fill(0);
        }
    }

    /// Get the number of signatures
    pub fn signature_count(&self) -> usize {
        self.signatures.len()
    }

    /// Get a signature by index
    pub fn get_signature(&self, index: usize) -> Option<&Vec<u8>> {
        self.signatures.get(index)
    }

    /// Check if all signatures are present (non-zero)
    pub fn is_fully_signed(&self) -> bool {
        self.signatures
            .iter()
            .all(|sig| sig.iter().any(|&b| b != 0))
    }

    /// Get the message bytes
    pub fn message(&self) -> &[u8] {
        &self.message
    }

    /// Get the parsed message with all instructions and accounts resolved
    ///
    /// This method provides access to the transaction's instructions,
    /// allowing inspection of side effects before signing.
    ///
    /// # Returns
    ///
    /// A deserialized message with all instructions and their data
    pub fn deserialize_message(&self) -> Result<DeserializedMessage> {
        deserialize_message(&self.message)
    }
}

/// Builder for creating and signing blockchain transactions
///
/// The `TransactionBuilder` provides a fluent interface for constructing transactions
/// step by step. It handles the complex logic of account collection, deduplication,
/// and sorting according to blockchain rules, as well as proper serialization into
/// the wire format.
///
/// # Examples
///
/// ```
/// # use rialo_cdk::transaction::TransactionBuilder;
/// # use rialo_cdk::keyring::Keyring;
/// # use rialo_cdk::rpc::types::{Pubkey, Hash};
/// # use rialo_cdk::EXAMPLE_CONFIG_HASH;
/// # use std::str::FromStr;
/// # fn example(keyring: &Keyring) -> Result<(), Box<dyn std::error::Error>> {
/// let fee_payer = Pubkey::from_str("FGda4aQty5NKU2as4AV7ob8J9FdPWWG2tYnTWsolWUBX").unwrap();
/// let valid_from = std::time::SystemTime::now()
///    .duration_since(std::time::UNIX_EPOCH)
///    .expect("Time went backwards")
///    .as_millis() as i64;
/// // In production, fetch from network: client.get_recent_validator_config_hash()
/// // Using EXAMPLE_CONFIG_HASH for documentation purposes only
/// let config_hash_prefix = EXAMPLE_CONFIG_HASH;
/// let from = Pubkey::from_str("FGda4aQty5NKU2as4AV7ob8J9FdPWWG2tYnTWsolWUBX").unwrap();
/// let to = Pubkey::from_str("AQTdzvx3HCf1TMwU9CZfSKyj2jJkdmNQWM9tFMrUhmiv").unwrap();
/// let transaction = TransactionBuilder::new(fee_payer, valid_from, config_hash_prefix)
/// .add_transfer_instruction(&from, &to, 1000000000)
/// .sign(keyring)?;
/// # Ok(())
/// # }
/// ```
pub struct TransactionBuilder {
    /// The account that will pay for this transaction
    fee_payer: Pubkey,
    /// Transaction valid from in milliseconds since Unix epoch
    valid_from: i64,
    /// Config hash prefix for replay protection across chains
    config_hash_prefix: ConfigHashPrefix,
    /// Collection of instructions to be executed
    instructions: Vec<Instruction>,
    /// Optional account index to use for signing (defaults to primary account)
    account_index: Option<usize>,
    /// Should the transaction be executed with the OCC scheduler.
    ///
    /// Defaults to `false`.
    occ: bool,
}

impl TransactionBuilder {
    /// Creates a new transaction builder with the specified fee_payer, valid_from, and config_hash_prefix
    ///
    /// # Arguments
    ///
    /// * `fee_payer` - The account that will pay for transaction fees (base58-encoded public key)
    /// * `valid_from` - The Transaction valid from in milliseconds since Unix epoch
    /// * `config_hash_prefix` - The config hash prefix for replay protection across chains
    ///
    /// # Returns
    ///
    /// A new `TransactionBuilder` instance
    pub fn new(fee_payer: Pubkey, valid_from: i64, config_hash_prefix: ConfigHashPrefix) -> Self {
        Self {
            fee_payer,
            valid_from,
            config_hash_prefix,
            instructions: Vec::new(),
            account_index: None,
            occ: false,
        }
    }

    /// Adds an instruction to the transaction
    ///
    /// # Arguments
    ///
    /// * `instruction` - The instruction to add
    ///
    /// # Returns
    ///
    /// Self reference for method chaining
    pub fn add_instruction(&mut self, instruction: Instruction) -> &mut Self {
        self.instructions.push(instruction);
        self
    }

    /// Adds a token transfer instruction to the transaction
    ///
    /// # Arguments
    ///
    /// * `from` - The sender's account (base58-encoded public key)
    /// * `to` - The recipient's account (base58-encoded public key)
    /// * `kelvin` - The amount to transfer in kelvin (the smallest unit)
    ///
    /// # Returns
    ///
    /// Self reference for method chaining
    pub fn add_transfer_instruction(
        &mut self,
        from: &Pubkey,
        to: &Pubkey,
        kelvin: u64,
    ) -> &mut Self {
        let instruction = instruction::transfer_instruction(from, to, kelvin);
        self.add_instruction(instruction)
    }

    /// Specifies which account in the wallet should be used for signing
    ///
    /// # Arguments
    ///
    /// * `account_index` - The index of the account to use for signing
    ///
    /// # Returns
    ///
    /// Self reference for method chaining
    pub fn with_account_index(&mut self, account_index: usize) -> &mut Self {
        self.account_index = Some(account_index);
        self
    }

    /// Specifies whether the transaction should be executed with the OCC scheduler.
    ///
    /// # Arguments
    ///
    /// * `occ` - `true` if OCC scheduler should be used
    ///
    /// # Returns
    ///
    /// Self reference for method chaining
    pub fn with_occ(&mut self, occ: bool) -> &mut Self {
        self.occ = occ;
        self
    }

    /// Builds the transaction message without signing it
    ///
    /// This method compiles all instructions into a single transaction message,
    /// collects and sorts all referenced accounts according to blockchain rules,
    /// and serializes the result into the wire format.
    ///
    /// # Returns
    ///
    /// The serialized transaction message as a byte vector
    ///
    /// # Errors
    ///
    /// Returns an error if message construction or serialization fails
    pub fn build(&self) -> Result<Vec<u8>> {
        // Collect all unique accounts from instructions
        let mut account_metas = Vec::new();
        let mut account_indices = std::collections::HashMap::new();

        // Always add payer as the first account (it's a signer and writable)
        account_metas.push(AccountMeta {
            pubkey: self.fee_payer,
            is_signer: true,
            is_writable: true,
        });
        account_indices.insert(self.fee_payer, 0);

        // Process all accounts from instructions
        for instruction in &self.instructions {
            // Add accounts from instruction
            for account_meta in &instruction.accounts {
                if let std::collections::hash_map::Entry::Vacant(e) =
                    account_indices.entry(account_meta.pubkey)
                {
                    e.insert(account_metas.len());
                    account_metas.push(AccountMeta {
                        pubkey: account_meta.pubkey,
                        is_signer: account_meta.is_signer,
                        is_writable: account_meta.is_writable,
                    });
                } else {
                    // Update existing account's signer and writable flags
                    let index = *account_indices.get(&account_meta.pubkey).unwrap();
                    let existing = &mut account_metas[index];
                    existing.is_signer |= account_meta.is_signer;
                    existing.is_writable |= account_meta.is_writable;
                }
            }

            // Add program ID if not already included
            if let std::collections::hash_map::Entry::Vacant(e) =
                account_indices.entry(instruction.program_id)
            {
                e.insert(account_metas.len());
                account_metas.push(AccountMeta {
                    pubkey: instruction.program_id,
                    is_signer: false,
                    is_writable: false,
                });
            }
        }

        // Sort accounts according to Solana rules:
        // 1. Signer + writable
        // 2. Signer + readonly
        // 3. Non-signer + writable
        // 4. Non-signer + readonly
        let mut pubkey_to_meta = std::collections::HashMap::new();
        for meta in &account_metas {
            pubkey_to_meta.insert(meta.pubkey, meta.clone());
        }

        // Sort keys first.
        // - This logic is similar to that in CompiledKeys::try_into_message_components().
        // - The first serialized key is considered to be the fee_payer (regardless
        //   of whether it is the sender, see e.g. SanitizedMessage::fee_payer()),
        // - For that reason explicitly force fee_payer to be first (the sort order
        //   provides some of that, minus the a.cmp(b) comparison).
        let mut keys_except_payer: Vec<Pubkey> = account_metas
            .iter()
            .skip(1)
            .map(|meta| meta.pubkey)
            .collect();
        keys_except_payer.sort_by(|a, b| {
            let a_meta = pubkey_to_meta.get(a).unwrap();
            let b_meta = pubkey_to_meta.get(b).unwrap();

            match (
                a_meta.is_signer,
                a_meta.is_writable,
                b_meta.is_signer,
                b_meta.is_writable,
            ) {
                // Signer + writable accounts first
                (true, true, true, false) => std::cmp::Ordering::Less,
                (true, false, true, true) => std::cmp::Ordering::Greater,

                // All signers before non-signers
                (true, _, false, _) => std::cmp::Ordering::Less,
                (false, _, true, _) => std::cmp::Ordering::Greater,

                // For same signer status, writable before readonly
                (true, true, true, true) | (false, true, false, true) => a.cmp(b),
                (true, false, true, false) | (false, false, false, false) => a.cmp(b),
                (false, true, false, false) => std::cmp::Ordering::Less,
                (false, false, false, true) => std::cmp::Ordering::Greater,
            }
        });
        let mut sorted_keys = vec![self.fee_payer];
        sorted_keys.extend(keys_except_payer);

        // Rebuild account_metas in sorted order and recreate indices map
        account_indices.clear();
        account_metas.clear();
        for (i, key) in sorted_keys.iter().enumerate() {
            let meta = pubkey_to_meta.get(key).unwrap();
            account_metas.push(meta.clone());
            account_indices.insert(*key, i);
        }

        // Create compiled instructions
        let compiled_instructions = self
            .instructions
            .iter()
            .map(|ix| {
                let program_idx = *account_indices.get(&ix.program_id).ok_or_else(|| {
                    RialoError::Transaction(format!(
                        "Program ID not found in accounts: {}",
                        ix.program_id
                    ))
                })?;

                let account_indices = ix
                    .accounts
                    .iter()
                    .map(|meta| {
                        account_indices.get(&meta.pubkey).copied().ok_or_else(|| {
                            RialoError::Transaction(format!("Account not found: {}", meta.pubkey))
                        })
                    })
                    .collect::<Result<Vec<_>>>()?;

                Ok(CompiledInstruction {
                    program_idx: program_idx as u8,
                    accounts: account_indices.into_iter().map(|idx| idx as u8).collect(),
                    data: ix.data.clone(),
                })
            })
            .collect::<Result<Vec<_>>>()?;

        // Count required signatures and read-only accounts
        let num_required_signatures = account_metas.iter().filter(|a| a.is_signer).count() as u8;
        let num_readonly_signed_accounts = account_metas
            .iter()
            .filter(|a| a.is_signer && !a.is_writable)
            .count() as u8;
        let num_readonly_unsigned_accounts = account_metas
            .iter()
            .filter(|a| !a.is_signer && !a.is_writable)
            .count() as u8;

        // Create message
        let message = Message {
            header: MessageHeader {
                num_required_signatures,
                num_readonly_signed_accounts,
                num_readonly_unsigned_accounts,
            },
            account_keys: account_metas.iter().map(|a| a.pubkey).collect(),
            valid_from: self.valid_from,
            config_hash_prefix: self.config_hash_prefix,
            occ: self.occ,
            instructions: compiled_instructions,
        };

        // Serialize message using borsh or bincode - here we'll use a basic binary format
        let message_bytes = serialize_message(&message)?;

        Ok(message_bytes)
    }

    /// Builds and signs the transaction with the wallet's default or specified account
    ///
    /// # Arguments
    ///
    /// * `wallet` - The wallet to use for signing
    ///
    /// # Returns
    ///
    /// The serialized, signed transaction as a byte vector ready for submission
    ///
    /// # Errors
    ///
    /// Returns an error if message building, signing, or serialization fails
    pub fn sign(&self, keyring: &Keyring) -> Result<Vec<u8>> {
        // Build the transaction message
        let message_bytes = self.build()?;

        // Sign the message with the specified keypair index or default keypair
        let signature = match self.account_index {
            Some(index) => keyring.sign_with_keypair(&message_bytes, index as u32)?,
            None => keyring.sign(&message_bytes),
        };

        // Create transaction structure
        let transaction = Transaction {
            signatures: vec![signature.to_bytes().to_vec()],
            message: message_bytes.clone(),
        };

        // Serialize the full transaction
        let transaction_bytes = serialize_transaction(&transaction)?;

        Ok(transaction_bytes)
    }

    /// Builds and signs the transaction with a specific keypair from the keyring
    ///
    /// # Arguments
    ///
    /// * `keyring` - The keyring to use for signing
    /// * `keypair_index` - The specific keypair index to use for signing
    ///
    /// # Returns
    ///
    /// The serialized, signed transaction as a byte vector ready for submission
    ///
    /// # Errors
    ///
    /// Returns an error if message building, signing, or serialization fails
    pub fn sign_with_keypair(&self, keyring: &Keyring, keypair_index: u32) -> Result<Vec<u8>> {
        // Build the transaction message
        let message_bytes = self.build()?;

        // Sign the message with the specified keypair
        let signature = keyring.sign_with_keypair(&message_bytes, keypair_index)?;

        // Create transaction structure
        let transaction = Transaction {
            signatures: vec![signature.to_bytes().to_vec()],
            message: message_bytes.clone(),
        };

        // Serialize the full transaction using Solana wire format
        let transaction_bytes = serialize_transaction(&transaction)?;

        Ok(transaction_bytes)
    }

    /// Builds and signs the transaction with a specific account from the wallet
    ///
    /// # Deprecated
    ///
    /// Use `sign_with_keypair` instead.
    #[deprecated(since = "0.2.0", note = "Use sign_with_keypair instead")]
    pub fn sign_with_account(&self, keyring: &Keyring, account_index: u32) -> Result<Vec<u8>> {
        self.sign_with_keypair(keyring, account_index)
    }

    /// Builds the transaction as a Transaction struct (unsigned)
    ///
    /// This method creates a Transaction object with empty signatures that can be
    /// serialized, signed manually, and deserialized later.
    ///
    /// # Returns
    ///
    /// A Transaction struct with the message and empty signatures
    ///
    /// # Errors
    ///
    /// Returns an error if message construction fails
    pub fn build_transaction(&self) -> Result<Transaction> {
        // Build the transaction message
        let message_bytes = self.build()?;

        // Parse the message to determine number of required signatures
        let message_header = parse_message_header(&message_bytes)?;
        let num_signatures = message_header.num_required_signatures as usize;

        // Create transaction with empty signatures
        Ok(Transaction::new(message_bytes, num_signatures))
    }
}

/// Internal structure representing an account reference in a transaction
///
/// Each account used in a transaction needs metadata about whether it's
/// signing the transaction and whether it's being written to.
#[derive(Clone, Debug)]
struct AccountMeta {
    /// The account's public key (base58-encoded)
    pubkey: Pubkey,
    /// Whether this account is a signer of the transaction
    is_signer: bool,
    /// Whether this account's data will be modified by the transaction
    is_writable: bool,
}

/// Header information for a transaction message
///
/// The header contains counts of different types of accounts in the transaction,
/// which is used during verification and execution.
#[derive(Debug, Clone)]
pub struct MessageHeader {
    /// Number of signatures required for this transaction
    pub num_required_signatures: u8,
    /// Number of read-only signed accounts
    pub num_readonly_signed_accounts: u8,
    /// Number of read-only unsigned accounts
    pub num_readonly_unsigned_accounts: u8,
}

/// Deserialized representation of a transaction message
///
/// This structure contains the fully parsed transaction message with all
/// instructions and account information accessible for inspection.
#[derive(Debug, Clone)]
pub struct DeserializedMessage {
    /// Message header with signature and account type counts
    pub header: MessageHeader,
    /// Ordered list of all accounts referenced by this transaction
    pub account_keys: Vec<Pubkey>,
    /// List of instructions to execute in sequence
    pub instructions: Vec<DeserializedInstruction>,
}

/// A deserialized instruction with resolved account references
///
/// Unlike CompiledInstruction which uses indices, this structure
/// contains the actual public keys for easier inspection.
#[derive(Debug, Clone)]
pub struct DeserializedInstruction {
    /// The program that will execute this instruction
    pub program_id: Pubkey,
    /// The accounts used by this instruction
    pub accounts: Vec<Pubkey>,
    /// Program-specific instruction data
    pub data: Vec<u8>,
}

/// A compiled instruction in the transaction format
///
/// This is the binary representation of an instruction, with accounts
/// referenced by their index in the message's account list.
#[derive(Debug, Clone)]
struct CompiledInstruction {
    /// Index of the program account in the message's account list
    program_idx: u8,
    /// Indices of the accounts used by this instruction
    accounts: Vec<u8>,
    /// Program-specific instruction data
    data: Vec<u8>,
}

/// Complete transaction message ready for serialization
///
/// A message contains all the information needed for transaction execution,
/// including account references, a transaction valid from, and instructions.
#[derive(Debug)]
struct Message {
    /// Message header with signature and account type counts
    header: MessageHeader,
    /// Ordered list of all accounts referenced by this transaction
    account_keys: Vec<Pubkey>,
    /// Transaction valid from in milliseconds since Unix epoch
    valid_from: i64,
    /// Config hash prefix for replay protection across chains
    config_hash_prefix: ConfigHashPrefix,
    /// Should the transaction be executed with the OCC scheduler.
    occ: bool,
    /// List of instructions to execute in sequence
    instructions: Vec<CompiledInstruction>,
}

/// Serializes a transaction message into the wire format
///
/// # Arguments
///
/// * `message` - The transaction message to serialize
///
/// # Returns
///
/// The serialized message as a byte vector
///
/// # Errors
///
/// Returns an error if serialization fails
fn serialize_message(message: &Message) -> Result<Vec<u8>> {
    let mut buffer = Vec::new();

    // Serialize header (3 bytes)
    buffer.push(message.header.num_required_signatures);
    buffer.push(message.header.num_readonly_signed_accounts);
    buffer.push(message.header.num_readonly_unsigned_accounts);

    // Serialize account keys (compact-array)
    serialize_compact_array(&mut buffer, &message.account_keys, |buf, key: &Pubkey| {
        // Pubkey is already a 32-byte array, just copy the bytes
        buf.extend_from_slice(key.as_ref());
        Ok(())
    })?;

    // Serialize valid_from (8 bytes, little-endian i64)
    buffer.extend_from_slice(&message.valid_from.to_le_bytes());

    // Serialize config_hash_prefix (8 bytes, little-endian u64)
    buffer.extend_from_slice(&message.config_hash_prefix.to_le_bytes());

    // Serialize OCC flag (1 byte)
    buffer.push(message.occ as u8);

    // Serialize instructions (compact-array)
    serialize_compact_array(&mut buffer, &message.instructions, |buf, instruction| {
        // Program ID index (1 byte)
        buf.push(instruction.program_idx);

        // Account indices (compact-array of bytes)
        serialize_compact_array(buf, &instruction.accounts, |b, &idx| {
            b.push(idx);
            Ok(())
        })?;

        // Instruction data (compact-array of bytes)
        serialize_compact_array(buf, &instruction.data, |b, &byte| {
            b.push(byte);
            Ok(())
        })?;

        Ok(())
    })?;

    Ok(buffer)
}

/// Serializes a complete signed transaction into the wire format
///
/// # Arguments
///
/// * `transaction` - The signed transaction to serialize
///
/// # Returns
///
/// The serialized transaction as a byte vector
///
/// # Errors
///
/// Returns an error if serialization fails
fn serialize_transaction(transaction: &Transaction) -> Result<Vec<u8>> {
    let mut buffer = Vec::new();

    // Serialize signatures (compact-array)
    serialize_compact_array(&mut buffer, &transaction.signatures, |buf, sig| {
        if sig.len() != 64 {
            return Err(RialoError::Transaction(
                "Invalid signature length".to_string(),
            ));
        }
        buf.extend_from_slice(sig);
        Ok(())
    })?;

    // Append message
    buffer.extend_from_slice(&transaction.message);

    Ok(buffer)
}

/// Helper function to serialize arrays with a compact-u16 length prefix
///
/// This implementation supports the Solana wire format, which uses a variable-length
/// encoding for array lengths to save space.
///
/// # Arguments
///
/// * `buffer` - The output buffer to write to
/// * `items` - The items to serialize
/// * `serialize_item` - Function to serialize each item
///
/// # Returns
///
/// Ok(()) if serialization succeeds
///
/// # Errors
///
/// Returns an error if serialization fails or the array is too large
fn serialize_compact_array<T, F>(
    buffer: &mut Vec<u8>,
    items: &[T],
    mut serialize_item: F,
) -> Result<()>
where
    F: FnMut(&mut Vec<u8>, &T) -> Result<()>,
{
    // Write compact-u16 length prefix
    let len = items.len();
    if len > 0x7FFF {
        return Err(RialoError::Transaction(
            "Array too large (max 32767 items)".to_string(),
        ));
    }

    // Encode compact-u16 following standard variable-length encoding:
    // - If the value is < 128, it's encoded as a single byte
    // - If the value is >= 128, it's encoded as two bytes:
    //   - First byte: (value & 0x7F) | 0x80 (sets the high bit)
    //   - Second byte: value >> 7
    if len < 128 {
        buffer.push(len as u8);
    } else {
        let lower_seven_bits = (len & 0x7F) as u8;
        let upper_bits = ((len >> 7) & 0x7F) as u8;
        buffer.push(lower_seven_bits | 0x80); // Set high bit to indicate multi-byte encoding
        buffer.push(upper_bits);
    }

    // Serialize each item
    for item in items {
        serialize_item(buffer, item)?;
    }

    Ok(())
}

/// Deserializes a complete signed transaction from the wire format
///
/// # Arguments
///
/// * `data` - The serialized transaction bytes
///
/// # Returns
///
/// The deserialized transaction or an error if deserialization fails
///
/// # Errors
///
/// Returns an error if the data is malformed or doesn't contain a valid transaction
fn deserialize_transaction(data: &[u8]) -> Result<Transaction> {
    if data.is_empty() {
        return Err(RialoError::Transaction(
            "Empty transaction data".to_string(),
        ));
    }

    let mut cursor = 0;

    // Deserialize signatures
    let (signatures, new_cursor) = deserialize_compact_array(data, cursor, |data, cursor| {
        if data.len() < cursor + 64 {
            return Err(RialoError::Transaction(
                "Insufficient data for signature".to_string(),
            ));
        }
        let signature = data[cursor..cursor + 64].to_vec();
        Ok((signature, cursor + 64))
    })?;
    cursor = new_cursor;

    // Remaining data is the message
    if cursor >= data.len() {
        return Err(RialoError::Transaction("No message data found".to_string()));
    }
    let message = data[cursor..].to_vec();

    Ok(Transaction {
        signatures,
        message,
    })
}

/// Helper function to deserialize a compact array
///
/// # Arguments
///
/// * `data` - The input data
/// * `cursor` - Current position in the data
/// * `deserialize_item` - Function to deserialize each item
///
/// # Returns
///
/// A tuple containing the deserialized items and the new cursor position
fn deserialize_compact_array<T, F>(
    data: &[u8],
    mut cursor: usize,
    mut deserialize_item: F,
) -> Result<(Vec<T>, usize)>
where
    F: FnMut(&[u8], usize) -> Result<(T, usize)>,
{
    // Read compact-u16 length prefix
    if cursor >= data.len() {
        return Err(RialoError::Transaction(
            "Insufficient data for array length".to_string(),
        ));
    }

    let first_byte = data[cursor];
    cursor += 1;

    let len = if first_byte & 0x80 == 0 {
        // Single byte encoding
        first_byte as usize
    } else {
        // Two byte encoding
        if cursor >= data.len() {
            return Err(RialoError::Transaction(
                "Insufficient data for array length continuation".to_string(),
            ));
        }
        let second_byte = data[cursor];
        cursor += 1;
        ((first_byte & 0x7F) as usize) | ((second_byte as usize) << 7)
    };

    // Deserialize each item
    let mut items = Vec::with_capacity(len);
    for _ in 0..len {
        let (item, new_cursor) = deserialize_item(data, cursor)?;
        items.push(item);
        cursor = new_cursor;
    }

    Ok((items, cursor))
}

/// Parse the message header from message bytes
///
/// # Arguments
///
/// * `message` - The message bytes
///
/// # Returns
///
/// The parsed message header
pub fn parse_message_header(message: &[u8]) -> Result<MessageHeader> {
    if message.len() < 3 {
        return Err(RialoError::Transaction(
            "Message too short for header".to_string(),
        ));
    }

    Ok(MessageHeader {
        num_required_signatures: message[0],
        num_readonly_signed_accounts: message[1],
        num_readonly_unsigned_accounts: message[2],
    })
}

/// Parse account keys from message bytes
///
/// # Arguments
///
/// * `message` - The message bytes
/// * `header` - The parsed message header
///
/// # Returns
///
/// The list of account public keys in order
pub fn parse_account_keys(message: &[u8], header: &MessageHeader) -> Result<Vec<Pubkey>> {
    let cursor = 3; // Skip header

    // Deserialize account keys
    let (account_keys, _) = deserialize_compact_array(message, cursor, |data, cursor| {
        if data.len() < cursor + 32 {
            return Err(RialoError::Transaction(
                "Insufficient data for pubkey".to_string(),
            ));
        }
        let mut pubkey_bytes = [0u8; 32];
        pubkey_bytes.copy_from_slice(&data[cursor..cursor + 32]);
        Ok((Pubkey::new_from_array(pubkey_bytes), cursor + 32))
    })?;

    // Validate that we have the expected number of signers
    let expected_signers = header.num_required_signatures as usize;
    if account_keys.len() < expected_signers {
        return Err(RialoError::Transaction(format!(
            "Not enough account keys for signers: expected {}, got {}",
            expected_signers,
            account_keys.len()
        )));
    }

    Ok(account_keys)
}

/// Deserialize a complete transaction message from wire format
///
/// This function fully parses a serialized message, providing access to all
/// transaction details including instruction data for side effect analysis.
///
/// # Arguments
///
/// * `data` - The serialized message bytes
///
/// # Returns
///
/// A fully deserialized message with resolved account references
///
/// # Example
///
/// ```
/// # use rialo_cdk::transaction::builder::deserialize_message;
/// # fn example(message_bytes: Vec<u8>) -> Result<(), Box<dyn std::error::Error>> {
/// let deserialized = deserialize_message(&message_bytes)?;
/// for instruction in &deserialized.instructions {
///     println!("Program: {}", instruction.program_id);
///     println!("Accounts: {:?}", instruction.accounts);
///     println!("Data: {:?}", instruction.data);
/// }
/// # Ok(())
/// # }
/// ```
pub fn deserialize_message(data: &[u8]) -> Result<DeserializedMessage> {
    if data.len() < 3 {
        return Err(RialoError::Transaction(
            "Message too short for header".to_string(),
        ));
    }

    // Parse header
    let header = parse_message_header(data)?;
    let mut cursor = 3;

    // Parse account keys
    let (account_keys, new_cursor) = deserialize_compact_array(data, cursor, |data, cursor| {
        if data.len() < cursor + 32 {
            return Err(RialoError::Transaction(
                "Insufficient data for pubkey".to_string(),
            ));
        }
        let mut pubkey_bytes = [0u8; 32];
        pubkey_bytes.copy_from_slice(&data[cursor..cursor + 32]);
        Ok((Pubkey::new_from_array(pubkey_bytes), cursor + 32))
    })?;
    cursor = new_cursor;

    // Parse valid_from (8 bytes, little-endian i64)
    if data.len() < cursor + 8 {
        return Err(RialoError::Transaction(
            "Insufficient data for valid_from".to_string(),
        ));
    }
    let mut valid_from_bytes = [0u8; 8];
    valid_from_bytes.copy_from_slice(&data[cursor..cursor + 8]);
    let _valid_from = i64::from_le_bytes(valid_from_bytes);
    cursor += 8;

    // Parse config_hash_prefix (8 bytes, little-endian u64)
    if data.len() < cursor + 8 {
        return Err(RialoError::Transaction(
            "Insufficient data for config_hash_prefix".to_string(),
        ));
    }
    let mut config_hash_prefix_bytes = [0u8; 8];
    config_hash_prefix_bytes.copy_from_slice(&data[cursor..cursor + 8]);
    let _config_hash_prefix = u64::from_le_bytes(config_hash_prefix_bytes);
    cursor += 8;

    // Parse occ flag (1 byte)
    if data.len() < cursor + 1 {
        return Err(RialoError::Transaction(
            "Insufficient data for occ flag".to_string(),
        ));
    }
    let mut occ_bytes = [0u8; 1];
    occ_bytes.copy_from_slice(&data[cursor..cursor + 1]);
    let _occ = u8::from_le_bytes(occ_bytes);
    cursor += 1;

    // Parse instructions
    let (compiled_instructions, _) = deserialize_compact_array(data, cursor, |data, cursor| {
        if data.len() < cursor + 1 {
            return Err(RialoError::Transaction(
                "Insufficient data for program index".to_string(),
            ));
        }
        let program_idx = data[cursor];
        let mut cursor = cursor + 1;

        // Parse account indices
        let (accounts, new_cursor) = deserialize_compact_array(data, cursor, |data, cursor| {
            if data.len() < cursor + 1 {
                return Err(RialoError::Transaction(
                    "Insufficient data for account index".to_string(),
                ));
            }
            Ok((data[cursor], cursor + 1))
        })?;
        cursor = new_cursor;

        // Parse instruction data
        let (data_bytes, new_cursor) = deserialize_compact_array(data, cursor, |data, cursor| {
            if data.len() < cursor + 1 {
                return Err(RialoError::Transaction(
                    "Insufficient data for instruction data".to_string(),
                ));
            }
            Ok((data[cursor], cursor + 1))
        })?;
        cursor = new_cursor;

        Ok((
            CompiledInstruction {
                program_idx,
                accounts,
                data: data_bytes,
            },
            cursor,
        ))
    })?;

    // Convert compiled instructions to deserialized instructions with resolved pubkeys
    let instructions = compiled_instructions
        .into_iter()
        .map(|compiled| {
            // Resolve program ID
            let program_id = *account_keys
                .get(compiled.program_idx as usize)
                .ok_or_else(|| {
                    RialoError::Transaction(format!(
                        "Invalid program index: {}",
                        compiled.program_idx
                    ))
                })?;

            // Resolve account pubkeys
            let accounts = compiled
                .accounts
                .iter()
                .map(|&idx| {
                    account_keys.get(idx as usize).copied().ok_or_else(|| {
                        RialoError::Transaction(format!("Invalid account index: {}", idx))
                    })
                })
                .collect::<Result<Vec<_>>>()?;

            Ok(DeserializedInstruction {
                program_id,
                accounts,
                data: compiled.data,
            })
        })
        .collect::<Result<Vec<_>>>()?;

    Ok(DeserializedMessage {
        header,
        account_keys,
        instructions,
    })
}
