//! ZK-Rollup manager for batch processing and proof coordination.
//!
//! This module orchestrates the entire rollup workflow:
//! 1. Collect transactions into batches
//! 2. Apply state transitions
//! 3. Generate validity proofs
//! 4. Submit to L1 bridge
//!
//! # Architecture
//!
//! ```text
//! User Transactions
//!        │
//!        ▼
//! ┌──────────────┐     ┌──────────────┐
//! │ Transaction  │────▶│   Mempool    │
//! │  Validator   │     │   (pending)  │
//! └──────────────┘     └──────────────┘
//!                              │
//!                              ▼ (batch full or timeout)
//!                      ┌──────────────┐
//!                      │ Batch Builder│
//!                      └──────────────┘
//!                              │
//!            ┌─────────────────┼─────────────────┐
//!            ▼                 ▼                 ▼
//!     ┌──────────┐      ┌──────────┐      ┌──────────┐
//!     │ State    │      │ Circuit  │      │ Proof    │
//!     │ Update   │      │ Builder  │      │ Generator│
//!     └──────────┘      └──────────┘      └──────────┘
//!            │                 │                 │
//!            └─────────────────┴─────────────────┘
//!                              │
//!                              ▼
//!                      ┌──────────────┐
//!                      │ L1 Submitter │
//!                      └──────────────┘
//! ```

use parking_lot::RwLock;
use serde::{Deserialize, Serialize};
use std::collections::VecDeque;
use std::time::{Duration, Instant};
use thiserror::Error;

use crate::circuit::BatchCircuit;
use crate::proof::{Proof, ProofError, ProofSystem, ProvingKey, VerificationKey};
use crate::state::{AccountState, StateError, StateRoot, StateTree};

/// Rollup configuration.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct RollupConfig {
    /// Maximum transactions per batch
    pub max_batch_size: usize,
    /// Minimum transactions before generating proof
    pub min_batch_size: usize,
    /// Maximum time to wait for batch fill
    pub batch_timeout: Duration,
    /// State tree depth
    pub tree_depth: usize,
    /// L1 bridge address
    pub bridge_address: Option<String>,
}

impl Default for RollupConfig {
    fn default() -> Self {
        Self {
            max_batch_size: crate::constants::MAX_BATCH_SIZE,
            min_batch_size: crate::constants::MIN_BATCH_SIZE,
            batch_timeout: Duration::from_secs(60),
            tree_depth: crate::constants::STATE_TREE_DEPTH,
            bridge_address: None,
        }
    }
}

/// Transaction types for the rollup.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum BatchTransaction {
    /// Transfer between L2 accounts
    Transfer {
        from: u64,
        to: u64,
        amount: u128,
        nonce: u64,
        signature: Vec<u8>,
    },
    /// Deposit from L1 to L2
    Deposit {
        to: u64,
        amount: u128,
        l1_tx_hash: [u8; 32],
    },
    /// Withdrawal from L2 to L1
    Withdraw {
        from: u64,
        l1_recipient: [u8; 20],
        amount: u128,
        nonce: u64,
        signature: Vec<u8>,
    },
}

impl BatchTransaction {
    /// Creates a transfer transaction.
    pub fn transfer(from: u64, to: u64, amount: u128) -> Self {
        Self::Transfer {
            from,
            to,
            amount,
            nonce: 0,
            signature: Vec::new(),
        }
    }

    /// Creates a deposit transaction.
    pub fn deposit(to: u64, amount: u128, l1_tx_hash: [u8; 32]) -> Self {
        Self::Deposit {
            to,
            amount,
            l1_tx_hash,
        }
    }

    /// Creates a withdrawal transaction.
    pub fn withdraw(from: u64, l1_recipient: [u8; 20], amount: u128) -> Self {
        Self::Withdraw {
            from,
            l1_recipient,
            amount,
            nonce: 0,
            signature: Vec::new(),
        }
    }

    /// Sets the signature for this transaction.
    pub fn with_signature(mut self, signature: Vec<u8>) -> Self {
        match &mut self {
            Self::Transfer { signature: sig, .. } => *sig = signature,
            Self::Withdraw { signature: sig, .. } => *sig = signature,
            Self::Deposit { .. } => {} // Deposits don't need L2 signatures
        }
        self
    }

    /// Sets the nonce for this transaction.
    pub fn with_nonce(mut self, nonce: u64) -> Self {
        match &mut self {
            Self::Transfer { nonce: n, .. } => *n = nonce,
            Self::Withdraw { nonce: n, .. } => *n = nonce,
            Self::Deposit { .. } => {}
        }
        self
    }

    /// Returns the hash of this transaction.
    pub fn hash(&self) -> [u8; 32] {
        let mut hasher = blake3::Hasher::new();
        match self {
            Self::Transfer { from, to, amount, nonce, .. } => {
                hasher.update(b"transfer");
                hasher.update(&from.to_le_bytes());
                hasher.update(&to.to_le_bytes());
                hasher.update(&amount.to_le_bytes());
                hasher.update(&nonce.to_le_bytes());
            }
            Self::Deposit { to, amount, l1_tx_hash } => {
                hasher.update(b"deposit");
                hasher.update(&to.to_le_bytes());
                hasher.update(&amount.to_le_bytes());
                hasher.update(l1_tx_hash);
            }
            Self::Withdraw { from, l1_recipient, amount, nonce, .. } => {
                hasher.update(b"withdraw");
                hasher.update(&from.to_le_bytes());
                hasher.update(l1_recipient);
                hasher.update(&amount.to_le_bytes());
                hasher.update(&nonce.to_le_bytes());
            }
        }
        *hasher.finalize().as_bytes()
    }
}

/// A batch of transactions with proof.
#[derive(Clone, Debug)]
pub struct RollupBatch {
    /// Batch number
    pub batch_number: u64,
    /// Transactions in this batch
    pub transactions: Vec<BatchTransaction>,
    /// State root before batch
    pub pre_state_root: StateRoot,
    /// State root after batch
    pub post_state_root: StateRoot,
    /// Validity proof
    pub proof: Option<Proof>,
    /// Batch hash (for L1 submission)
    pub batch_hash: [u8; 32],
    /// Timestamp
    pub timestamp: u64,
}

impl RollupBatch {
    /// Creates a new batch.
    pub fn new(batch_number: u64, pre_state_root: StateRoot) -> Self {
        Self {
            batch_number,
            transactions: Vec::new(),
            pre_state_root,
            post_state_root: pre_state_root,
            proof: None,
            batch_hash: [0u8; 32],
            timestamp: std::time::SystemTime::now()
                .duration_since(std::time::UNIX_EPOCH)
                .unwrap()
                .as_secs(),
        }
    }

    /// Computes the batch hash.
    pub fn compute_hash(&mut self) {
        let mut hasher = blake3::Hasher::new();
        hasher.update(&self.batch_number.to_le_bytes());
        hasher.update(&self.pre_state_root.0);
        hasher.update(&self.post_state_root.0);
        hasher.update(&(self.transactions.len() as u64).to_le_bytes());
        for tx in &self.transactions {
            hasher.update(&tx.hash());
        }
        self.batch_hash = *hasher.finalize().as_bytes();
    }

    /// Returns the transaction count.
    pub fn tx_count(&self) -> usize {
        self.transactions.len()
    }

    /// Returns whether the batch has a proof.
    pub fn is_proven(&self) -> bool {
        self.proof.is_some()
    }
}

/// Rollup manager state.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum RollupState {
    /// Accepting transactions
    Accepting,
    /// Building batch, not accepting new transactions
    Building,
    /// Generating proof
    Proving,
    /// Ready for L1 submission
    Ready,
    /// Paused (e.g., L1 congestion)
    Paused,
}

/// Rollup manager for coordinating batches and proofs.
pub struct RollupManager {
    config: RollupConfig,
    state_tree: StateTree,
    proof_system: ProofSystem,
    proving_key: Option<ProvingKey>,
    verification_key: Option<VerificationKey>,
    pending_txs: RwLock<VecDeque<BatchTransaction>>,
    current_batch: RwLock<Option<RollupBatch>>,
    committed_batches: RwLock<Vec<RollupBatch>>,
    next_batch_number: RwLock<u64>,
    state: RwLock<RollupState>,
    batch_start_time: RwLock<Option<Instant>>,
}

impl RollupManager {
    /// Creates a new rollup manager.
    pub fn new() -> Self {
        let config = RollupConfig::default();
        let state_tree = StateTree::new(config.tree_depth);

        Self {
            config,
            state_tree,
            proof_system: ProofSystem::groth16(),
            proving_key: None,
            verification_key: None,
            pending_txs: RwLock::new(VecDeque::new()),
            current_batch: RwLock::new(None),
            committed_batches: RwLock::new(Vec::new()),
            next_batch_number: RwLock::new(0),
            state: RwLock::new(RollupState::Accepting),
            batch_start_time: RwLock::new(None),
        }
    }

    /// Creates a rollup manager with custom config.
    pub fn with_config(config: RollupConfig) -> Self {
        let state_tree = StateTree::new(config.tree_depth);

        Self {
            config,
            state_tree,
            proof_system: ProofSystem::groth16(),
            proving_key: None,
            verification_key: None,
            pending_txs: RwLock::new(VecDeque::new()),
            current_batch: RwLock::new(None),
            committed_batches: RwLock::new(Vec::new()),
            next_batch_number: RwLock::new(0),
            state: RwLock::new(RollupState::Accepting),
            batch_start_time: RwLock::new(None),
        }
    }

    /// Sets up the proving/verification keys.
    pub fn setup(&mut self) -> Result<(), RollupError> {
        let circuit = BatchCircuit::new(StateRoot::zero(), StateRoot::zero());
        let (pk, vk) = self.proof_system.setup(&circuit)?;
        self.proving_key = Some(pk);
        self.verification_key = Some(vk);
        Ok(())
    }

    /// Returns the current rollup state.
    pub fn state(&self) -> RollupState {
        *self.state.read()
    }

    /// Returns the current state root.
    pub fn state_root(&self) -> StateRoot {
        self.state_tree.root()
    }

    /// Returns the config.
    pub fn config(&self) -> &RollupConfig {
        &self.config
    }

    /// Returns the verification key.
    pub fn verification_key(&self) -> Option<&VerificationKey> {
        self.verification_key.as_ref()
    }

    /// Returns the number of pending transactions.
    pub fn pending_count(&self) -> usize {
        self.pending_txs.read().len()
    }

    /// Returns the number of committed batches.
    pub fn batch_count(&self) -> usize {
        self.committed_batches.read().len()
    }

    /// Adds a transaction to the pending pool.
    pub fn add_transaction(&self, tx: BatchTransaction) -> Result<(), RollupError> {
        let state = *self.state.read();
        if state != RollupState::Accepting {
            return Err(RollupError::NotAccepting);
        }

        // Validate transaction
        self.validate_transaction(&tx)?;

        // Add to pending
        self.pending_txs.write().push_back(tx);

        // Start batch timer if this is the first tx
        let mut batch_start = self.batch_start_time.write();
        if batch_start.is_none() {
            *batch_start = Some(Instant::now());
        }

        // Check if we should start building
        if self.pending_txs.read().len() >= self.config.max_batch_size {
            drop(batch_start);
            self.start_building()?;
        }

        Ok(())
    }

    /// Validates a transaction before adding to pool.
    fn validate_transaction(&self, tx: &BatchTransaction) -> Result<(), RollupError> {
        match tx {
            BatchTransaction::Transfer { from, amount, .. } => {
                let account = self
                    .state_tree
                    .get_account(*from)
                    .ok_or(RollupError::AccountNotFound(*from))?;
                if account.balance < *amount {
                    return Err(RollupError::InsufficientBalance);
                }
            }
            BatchTransaction::Withdraw { from, amount, .. } => {
                let account = self
                    .state_tree
                    .get_account(*from)
                    .ok_or(RollupError::AccountNotFound(*from))?;
                if account.balance < *amount {
                    return Err(RollupError::InsufficientBalance);
                }
            }
            BatchTransaction::Deposit { .. } => {
                // Deposits are validated against L1
            }
        }
        Ok(())
    }

    /// Checks if batch should be finalized due to timeout.
    pub fn check_timeout(&self) -> bool {
        let batch_start = self.batch_start_time.read();
        if let Some(start) = *batch_start {
            let pending = self.pending_txs.read().len();
            if pending >= self.config.min_batch_size
                && start.elapsed() >= self.config.batch_timeout
            {
                return true;
            }
        }
        false
    }

    /// Starts building a batch from pending transactions.
    fn start_building(&self) -> Result<(), RollupError> {
        *self.state.write() = RollupState::Building;

        let batch_number = *self.next_batch_number.read();
        let pre_state_root = self.state_tree.root();

        let mut batch = RollupBatch::new(batch_number, pre_state_root);

        // Move transactions from pending to batch
        let mut pending = self.pending_txs.write();
        while batch.transactions.len() < self.config.max_batch_size {
            if let Some(tx) = pending.pop_front() {
                batch.transactions.push(tx);
            } else {
                break;
            }
        }

        *self.current_batch.write() = Some(batch);
        *self.batch_start_time.write() = None;

        Ok(())
    }

    /// Finalizes the current batch and generates a proof.
    pub fn finalize_batch(&self) -> Result<RollupBatch, RollupError> {
        // If still accepting, start building
        if *self.state.read() == RollupState::Accepting {
            self.start_building()?;
        }

        *self.state.write() = RollupState::Proving;

        let mut batch = self
            .current_batch
            .write()
            .take()
            .ok_or(RollupError::NoBatch)?;

        // Apply transactions to state tree
        for tx in &batch.transactions {
            match tx {
                BatchTransaction::Transfer { from, to, amount, .. } => {
                    self.state_tree.apply_transfer(*from, *to, *amount)?;
                }
                BatchTransaction::Deposit { to, amount, .. } => {
                    self.state_tree.apply_deposit(*to, *amount)?;
                }
                BatchTransaction::Withdraw { from, amount, .. } => {
                    self.state_tree.apply_withdrawal(*from, *amount)?;
                }
            }
        }

        batch.post_state_root = self.state_tree.root();
        batch.compute_hash();

        // Generate proof
        if let Some(pk) = &self.proving_key {
            let circuit = BatchCircuit::new(batch.pre_state_root, batch.post_state_root);
            let proof = self.proof_system.prove(&circuit, pk)?;
            batch.proof = Some(proof);
        }

        // Commit batch
        self.committed_batches.write().push(batch.clone());
        *self.next_batch_number.write() += 1;

        // Ready for submission
        *self.state.write() = RollupState::Ready;

        Ok(batch)
    }

    /// Verifies a batch proof.
    pub fn verify_batch(&self, batch: &RollupBatch) -> Result<bool, RollupError> {
        let proof = batch.proof.as_ref().ok_or(RollupError::NoProof)?;
        let vk = self.verification_key.as_ref().ok_or(RollupError::NoVerificationKey)?;

        Ok(self.proof_system.verify(proof, vk)?)
    }

    /// Registers a new account.
    pub fn register_account(&self, index: u64, pubkey_hash: [u8; 32]) -> Result<StateRoot, RollupError> {
        let state = AccountState::new(pubkey_hash);
        Ok(self.state_tree.set_account(index, state)?)
    }

    /// Gets an account by index.
    pub fn get_account(&self, index: u64) -> Option<AccountState> {
        self.state_tree.get_account(index)
    }

    /// Returns to accepting state after batch submission.
    pub fn resume(&self) {
        *self.state.write() = RollupState::Accepting;
    }

    /// Pauses the rollup.
    pub fn pause(&self) {
        *self.state.write() = RollupState::Paused;
    }
}

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

impl std::fmt::Debug for RollupManager {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("RollupManager")
            .field("state", &self.state())
            .field("pending_txs", &self.pending_count())
            .field("batch_count", &self.batch_count())
            .field("state_root", &self.state_root().to_hex())
            .finish()
    }
}

/// Rollup errors.
#[derive(Debug, Error)]
pub enum RollupError {
    #[error("Rollup is not accepting transactions")]
    NotAccepting,

    #[error("No batch to finalize")]
    NoBatch,

    #[error("No proof available")]
    NoProof,

    #[error("No verification key available")]
    NoVerificationKey,

    #[error("Account not found: {0}")]
    AccountNotFound(u64),

    #[error("Insufficient balance")]
    InsufficientBalance,

    #[error("Invalid transaction: {0}")]
    InvalidTransaction(String),

    #[error("Proof error: {0}")]
    ProofError(#[from] ProofError),

    #[error("State error: {0}")]
    StateError(#[from] StateError),
}

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

    // ============================================================================
    // RollupConfig Tests
    // ============================================================================

    #[test]
    fn test_rollup_config_default() {
        let config = RollupConfig::default();
        assert_eq!(config.max_batch_size, crate::constants::MAX_BATCH_SIZE);
        assert_eq!(config.min_batch_size, crate::constants::MIN_BATCH_SIZE);
        assert_eq!(config.tree_depth, crate::constants::STATE_TREE_DEPTH);
        assert!(config.bridge_address.is_none());
    }

    #[test]
    fn test_rollup_config_batch_size_order() {
        let config = RollupConfig::default();
        assert!(config.min_batch_size < config.max_batch_size);
    }

    #[test]
    fn test_rollup_config_custom() {
        let config = RollupConfig {
            max_batch_size: 500,
            min_batch_size: 5,
            batch_timeout: Duration::from_secs(30),
            tree_depth: 16,
            bridge_address: Some("0x1234...".to_string()),
        };

        assert_eq!(config.max_batch_size, 500);
        assert_eq!(config.min_batch_size, 5);
        assert_eq!(config.batch_timeout, Duration::from_secs(30));
        assert_eq!(config.tree_depth, 16);
        assert!(config.bridge_address.is_some());
    }

    #[test]
    fn test_rollup_config_clone() {
        let config = RollupConfig::default();
        let cloned = config.clone();
        assert_eq!(config.max_batch_size, cloned.max_batch_size);
        assert_eq!(config.min_batch_size, cloned.min_batch_size);
    }

    #[test]
    fn test_rollup_config_debug() {
        let config = RollupConfig::default();
        let debug_str = format!("{:?}", config);
        assert!(debug_str.contains("RollupConfig"));
        assert!(debug_str.contains("max_batch_size"));
    }

    // ============================================================================
    // BatchTransaction Tests
    // ============================================================================

    #[test]
    fn test_batch_transaction_transfer() {
        let tx = BatchTransaction::transfer(0, 1, 100);

        match tx {
            BatchTransaction::Transfer { from, to, amount, nonce, signature } => {
                assert_eq!(from, 0);
                assert_eq!(to, 1);
                assert_eq!(amount, 100);
                assert_eq!(nonce, 0);
                assert!(signature.is_empty());
            }
            _ => panic!("Expected Transfer variant"),
        }
    }

    #[test]
    fn test_batch_transaction_deposit() {
        let l1_tx_hash = [0xab; 32];
        let tx = BatchTransaction::deposit(5, 1000, l1_tx_hash);

        match tx {
            BatchTransaction::Deposit { to, amount, l1_tx_hash: hash } => {
                assert_eq!(to, 5);
                assert_eq!(amount, 1000);
                assert_eq!(hash, l1_tx_hash);
            }
            _ => panic!("Expected Deposit variant"),
        }
    }

    #[test]
    fn test_batch_transaction_withdraw() {
        let l1_recipient = [0xcc; 20];
        let tx = BatchTransaction::withdraw(10, l1_recipient, 500);

        match tx {
            BatchTransaction::Withdraw { from, l1_recipient: recipient, amount, nonce, signature } => {
                assert_eq!(from, 10);
                assert_eq!(recipient, l1_recipient);
                assert_eq!(amount, 500);
                assert_eq!(nonce, 0);
                assert!(signature.is_empty());
            }
            _ => panic!("Expected Withdraw variant"),
        }
    }

    #[test]
    fn test_batch_transaction_with_signature_transfer() {
        let sig = vec![1, 2, 3, 4, 5];
        let tx = BatchTransaction::transfer(0, 1, 100).with_signature(sig.clone());

        match tx {
            BatchTransaction::Transfer { signature, .. } => {
                assert_eq!(signature, sig);
            }
            _ => panic!("Expected Transfer"),
        }
    }

    #[test]
    fn test_batch_transaction_with_signature_withdraw() {
        let sig = vec![0xaa, 0xbb];
        let tx = BatchTransaction::withdraw(0, [0u8; 20], 100).with_signature(sig.clone());

        match tx {
            BatchTransaction::Withdraw { signature, .. } => {
                assert_eq!(signature, sig);
            }
            _ => panic!("Expected Withdraw"),
        }
    }

    #[test]
    fn test_batch_transaction_with_nonce_transfer() {
        let tx = BatchTransaction::transfer(0, 1, 100).with_nonce(42);

        match tx {
            BatchTransaction::Transfer { nonce, .. } => {
                assert_eq!(nonce, 42);
            }
            _ => panic!("Expected Transfer"),
        }
    }

    #[test]
    fn test_batch_transaction_with_nonce_withdraw() {
        let tx = BatchTransaction::withdraw(0, [0u8; 20], 100).with_nonce(99);

        match tx {
            BatchTransaction::Withdraw { nonce, .. } => {
                assert_eq!(nonce, 99);
            }
            _ => panic!("Expected Withdraw"),
        }
    }

    #[test]
    fn test_batch_transaction_hash_transfer() {
        let tx = BatchTransaction::transfer(0, 1, 100);
        let hash = tx.hash();
        assert_ne!(hash, [0u8; 32]);
    }

    #[test]
    fn test_batch_transaction_hash_deposit() {
        let tx = BatchTransaction::deposit(0, 100, [0xab; 32]);
        let hash = tx.hash();
        assert_ne!(hash, [0u8; 32]);
    }

    #[test]
    fn test_batch_transaction_hash_withdraw() {
        let tx = BatchTransaction::withdraw(0, [0xcc; 20], 100);
        let hash = tx.hash();
        assert_ne!(hash, [0u8; 32]);
    }

    #[test]
    fn test_batch_transaction_hash_consistency() {
        let tx = BatchTransaction::transfer(0, 1, 100);
        let hash1 = tx.hash();
        let hash2 = tx.hash();
        assert_eq!(hash1, hash2);
    }

    #[test]
    fn test_batch_transaction_hash_different_amounts() {
        let tx1 = BatchTransaction::transfer(0, 1, 100);
        let tx2 = BatchTransaction::transfer(0, 1, 200);
        assert_ne!(tx1.hash(), tx2.hash());
    }

    #[test]
    fn test_batch_transaction_hash_different_from_to() {
        let tx1 = BatchTransaction::transfer(0, 1, 100);
        let tx2 = BatchTransaction::transfer(1, 0, 100);
        assert_ne!(tx1.hash(), tx2.hash());
    }

    #[test]
    fn test_batch_transaction_builder_chain() {
        let tx = BatchTransaction::transfer(0, 1, 100)
            .with_nonce(5)
            .with_signature(vec![1, 2, 3]);

        match tx {
            BatchTransaction::Transfer { nonce, signature, .. } => {
                assert_eq!(nonce, 5);
                assert_eq!(signature, vec![1, 2, 3]);
            }
            _ => panic!("Expected Transfer"),
        }
    }

    #[test]
    fn test_batch_transaction_clone() {
        let tx = BatchTransaction::transfer(0, 1, 100);
        let cloned = tx.clone();

        assert_eq!(tx.hash(), cloned.hash());
    }

    #[test]
    fn test_batch_transaction_large_amount() {
        let tx = BatchTransaction::transfer(0, 1, u128::MAX);
        let hash = tx.hash();
        assert_ne!(hash, [0u8; 32]);
    }

    // ============================================================================
    // RollupBatch Tests
    // ============================================================================

    #[test]
    fn test_rollup_batch_new() {
        let batch = RollupBatch::new(0, StateRoot::zero());

        assert_eq!(batch.batch_number, 0);
        assert!(batch.transactions.is_empty());
        assert_eq!(batch.pre_state_root, StateRoot::zero());
        assert_eq!(batch.post_state_root, StateRoot::zero());
        assert!(batch.proof.is_none());
        assert_eq!(batch.batch_hash, [0u8; 32]);
        assert!(batch.timestamp > 0);
    }

    #[test]
    fn test_rollup_batch_with_transactions() {
        let mut batch = RollupBatch::new(1, StateRoot([1u8; 32]));
        batch.transactions.push(BatchTransaction::transfer(0, 1, 100));
        batch.transactions.push(BatchTransaction::transfer(1, 2, 50));

        assert_eq!(batch.tx_count(), 2);
    }

    #[test]
    fn test_rollup_batch_compute_hash() {
        let mut batch = RollupBatch::new(0, StateRoot::zero());
        batch.transactions.push(BatchTransaction::transfer(0, 1, 100));
        batch.post_state_root = StateRoot([1u8; 32]);
        batch.compute_hash();

        assert_ne!(batch.batch_hash, [0u8; 32]);
    }

    #[test]
    fn test_rollup_batch_hash_changes_with_transactions() {
        let mut batch1 = RollupBatch::new(0, StateRoot::zero());
        batch1.compute_hash();
        let hash1 = batch1.batch_hash;

        let mut batch2 = RollupBatch::new(0, StateRoot::zero());
        batch2.transactions.push(BatchTransaction::transfer(0, 1, 100));
        batch2.compute_hash();
        let hash2 = batch2.batch_hash;

        assert_ne!(hash1, hash2);
    }

    #[test]
    fn test_rollup_batch_tx_count() {
        let mut batch = RollupBatch::new(0, StateRoot::zero());

        for i in 0..10 {
            assert_eq!(batch.tx_count(), i);
            batch.transactions.push(BatchTransaction::transfer(0, 1, 100));
        }

        assert_eq!(batch.tx_count(), 10);
    }

    #[test]
    fn test_rollup_batch_is_proven() {
        let mut batch = RollupBatch::new(0, StateRoot::zero());
        assert!(!batch.is_proven());

        use crate::proof::{Proof, ProofSystemBackend};
        batch.proof = Some(Proof {
            backend: ProofSystemBackend::Groth16,
            data: vec![1, 2, 3],
            public_inputs: vec![],
        });

        assert!(batch.is_proven());
    }

    #[test]
    fn test_rollup_batch_clone() {
        let mut batch = RollupBatch::new(5, StateRoot([0xaa; 32]));
        batch.transactions.push(BatchTransaction::transfer(0, 1, 100));
        batch.post_state_root = StateRoot([0xbb; 32]);
        batch.compute_hash();

        let cloned = batch.clone();

        assert_eq!(batch.batch_number, cloned.batch_number);
        assert_eq!(batch.pre_state_root, cloned.pre_state_root);
        assert_eq!(batch.post_state_root, cloned.post_state_root);
        assert_eq!(batch.batch_hash, cloned.batch_hash);
        assert_eq!(batch.tx_count(), cloned.tx_count());
    }

    #[test]
    fn test_rollup_batch_debug() {
        let batch = RollupBatch::new(0, StateRoot::zero());
        let debug_str = format!("{:?}", batch);
        assert!(debug_str.contains("RollupBatch"));
        assert!(debug_str.contains("batch_number"));
    }

    // ============================================================================
    // RollupState Tests
    // ============================================================================

    #[test]
    fn test_rollup_state_variants() {
        assert_eq!(RollupState::Accepting, RollupState::Accepting);
        assert_ne!(RollupState::Accepting, RollupState::Building);
        assert_ne!(RollupState::Building, RollupState::Proving);
        assert_ne!(RollupState::Proving, RollupState::Ready);
        assert_ne!(RollupState::Ready, RollupState::Paused);
    }

    #[test]
    fn test_rollup_state_clone() {
        let state = RollupState::Accepting;
        let cloned = state;
        assert_eq!(state, cloned);
    }

    #[test]
    fn test_rollup_state_debug() {
        let state = RollupState::Proving;
        let debug_str = format!("{:?}", state);
        assert!(debug_str.contains("Proving"));
    }

    // ============================================================================
    // RollupManager Tests
    // ============================================================================

    #[test]
    fn test_rollup_manager_creation() {
        let manager = RollupManager::new();
        assert_eq!(manager.state(), RollupState::Accepting);
        assert_eq!(manager.pending_count(), 0);
        assert_eq!(manager.batch_count(), 0);
    }

    #[test]
    fn test_rollup_manager_with_config() {
        let config = RollupConfig {
            max_batch_size: 100,
            min_batch_size: 2,
            batch_timeout: Duration::from_secs(10),
            tree_depth: 16,
            bridge_address: None,
        };

        let manager = RollupManager::with_config(config.clone());
        assert_eq!(manager.config().max_batch_size, 100);
        assert_eq!(manager.config().min_batch_size, 2);
    }

    #[test]
    fn test_rollup_manager_default() {
        let manager = RollupManager::default();
        assert_eq!(manager.state(), RollupState::Accepting);
    }

    #[test]
    fn test_rollup_manager_state_root() {
        let manager = RollupManager::new();
        assert_eq!(manager.state_root(), StateRoot::zero());
    }

    #[test]
    fn test_rollup_manager_setup() {
        let mut manager = RollupManager::new();
        let result = manager.setup();
        assert!(result.is_ok());
        assert!(manager.verification_key().is_some());
    }

    #[test]
    fn test_rollup_manager_register_account() {
        let manager = RollupManager::new();
        let pubkey_hash = [0xab; 32];

        let root = manager.register_account(0, pubkey_hash).unwrap();
        assert_ne!(root, StateRoot::zero());

        let account = manager.get_account(0).unwrap();
        assert_eq!(account.pubkey_hash, pubkey_hash);
    }

    #[test]
    fn test_rollup_manager_register_multiple_accounts() {
        let manager = RollupManager::new();

        for i in 0..10 {
            let root = manager.register_account(i, [i as u8; 32]).unwrap();
            assert_ne!(root, StateRoot::zero());
        }

        for i in 0..10 {
            let account = manager.get_account(i).unwrap();
            assert_eq!(account.pubkey_hash, [i as u8; 32]);
        }
    }

    #[test]
    fn test_rollup_manager_get_nonexistent_account() {
        let manager = RollupManager::new();
        assert!(manager.get_account(999).is_none());
    }

    #[test]
    fn test_rollup_manager_add_transaction() {
        let manager = RollupManager::new();

        manager.register_account(0, [0xaa; 32]).unwrap();
        manager.register_account(1, [0xbb; 32]).unwrap();

        manager
            .state_tree
            .update_account(0, |acc| acc.balance = 1000)
            .unwrap();

        let tx = BatchTransaction::transfer(0, 1, 100);
        manager.add_transaction(tx).unwrap();

        assert_eq!(manager.pending_count(), 1);
    }

    #[test]
    fn test_rollup_manager_add_multiple_transactions() {
        let manager = RollupManager::new();

        manager.register_account(0, [0xaa; 32]).unwrap();
        manager.register_account(1, [0xbb; 32]).unwrap();

        manager
            .state_tree
            .update_account(0, |acc| acc.balance = 10000)
            .unwrap();

        for _ in 0..5 {
            let tx = BatchTransaction::transfer(0, 1, 100);
            manager.add_transaction(tx).unwrap();
        }

        assert_eq!(manager.pending_count(), 5);
    }

    #[test]
    fn test_rollup_manager_add_transaction_insufficient_balance() {
        let manager = RollupManager::new();

        manager.register_account(0, [0xaa; 32]).unwrap();
        manager.register_account(1, [0xbb; 32]).unwrap();

        manager
            .state_tree
            .update_account(0, |acc| acc.balance = 50)
            .unwrap();

        let tx = BatchTransaction::transfer(0, 1, 100);
        let result = manager.add_transaction(tx);

        assert!(matches!(result, Err(RollupError::InsufficientBalance)));
    }

    #[test]
    fn test_rollup_manager_add_transaction_account_not_found() {
        let manager = RollupManager::new();

        let tx = BatchTransaction::transfer(999, 1, 100);
        let result = manager.add_transaction(tx);

        assert!(matches!(result, Err(RollupError::AccountNotFound(999))));
    }

    #[test]
    fn test_rollup_manager_add_deposit_transaction() {
        let manager = RollupManager::new();
        manager.register_account(0, [0xaa; 32]).unwrap();

        let tx = BatchTransaction::deposit(0, 1000, [0xab; 32]);
        manager.add_transaction(tx).unwrap();

        assert_eq!(manager.pending_count(), 1);
    }

    #[test]
    fn test_rollup_manager_add_withdraw_transaction() {
        let manager = RollupManager::new();
        manager.register_account(0, [0xaa; 32]).unwrap();

        manager
            .state_tree
            .update_account(0, |acc| acc.balance = 1000)
            .unwrap();

        let tx = BatchTransaction::withdraw(0, [0xcc; 20], 500);
        manager.add_transaction(tx).unwrap();

        assert_eq!(manager.pending_count(), 1);
    }

    #[test]
    fn test_rollup_manager_pause_resume() {
        let manager = RollupManager::new();

        assert_eq!(manager.state(), RollupState::Accepting);

        manager.pause();
        assert_eq!(manager.state(), RollupState::Paused);

        manager.resume();
        assert_eq!(manager.state(), RollupState::Accepting);
    }

    #[test]
    fn test_rollup_manager_add_transaction_when_paused() {
        let manager = RollupManager::new();
        manager.register_account(0, [0xaa; 32]).unwrap();
        manager
            .state_tree
            .update_account(0, |acc| acc.balance = 1000)
            .unwrap();

        manager.pause();

        let tx = BatchTransaction::transfer(0, 1, 100);
        let result = manager.add_transaction(tx);

        assert!(matches!(result, Err(RollupError::NotAccepting)));
    }

    #[test]
    fn test_rollup_manager_finalize_batch() {
        let manager = RollupManager::new();
        manager.register_account(0, [0xaa; 32]).unwrap();
        manager.register_account(1, [0xbb; 32]).unwrap();
        manager
            .state_tree
            .update_account(0, |acc| acc.balance = 1000)
            .unwrap();

        let tx = BatchTransaction::transfer(0, 1, 100);
        manager.add_transaction(tx).unwrap();

        let batch = manager.finalize_batch().unwrap();

        assert_eq!(batch.tx_count(), 1);
        assert_ne!(batch.pre_state_root, batch.post_state_root);
        assert_eq!(manager.batch_count(), 1);
    }

    #[test]
    fn test_rollup_manager_finalize_empty_batch() {
        let manager = RollupManager::new();

        let result = manager.finalize_batch();

        assert!(result.is_ok());
        let batch = result.unwrap();
        assert_eq!(batch.tx_count(), 0);
    }

    #[test]
    fn test_rollup_manager_batch_state_transitions() {
        let manager = RollupManager::new();
        manager.register_account(0, [0xaa; 32]).unwrap();
        manager.register_account(1, [0xbb; 32]).unwrap();
        manager
            .state_tree
            .update_account(0, |acc| acc.balance = 1000)
            .unwrap();

        let tx = BatchTransaction::transfer(0, 1, 300);
        manager.add_transaction(tx).unwrap();

        let pre_balance_0 = manager.get_account(0).unwrap().balance;
        let pre_balance_1 = manager.get_account(1).unwrap().balance;

        manager.finalize_batch().unwrap();

        let post_balance_0 = manager.get_account(0).unwrap().balance;
        let post_balance_1 = manager.get_account(1).unwrap().balance;

        assert_eq!(post_balance_0, pre_balance_0 - 300);
        assert_eq!(post_balance_1, pre_balance_1 + 300);
    }

    #[test]
    fn test_rollup_manager_multiple_batches() {
        let manager = RollupManager::new();
        manager.register_account(0, [0xaa; 32]).unwrap();
        manager.register_account(1, [0xbb; 32]).unwrap();
        manager
            .state_tree
            .update_account(0, |acc| acc.balance = 10000)
            .unwrap();

        for i in 0..3 {
            let tx = BatchTransaction::transfer(0, 1, 100);
            manager.add_transaction(tx).unwrap();
            let batch = manager.finalize_batch().unwrap();
            assert_eq!(batch.batch_number, i);
            manager.resume();
        }

        assert_eq!(manager.batch_count(), 3);
    }

    #[test]
    fn test_rollup_manager_verify_batch_no_proof() {
        let manager = RollupManager::new();
        let batch = RollupBatch::new(0, StateRoot::zero());

        let result = manager.verify_batch(&batch);
        assert!(matches!(result, Err(RollupError::NoProof)));
    }

    #[test]
    fn test_rollup_manager_verify_batch_no_vk() {
        let manager = RollupManager::new();

        use crate::proof::{Proof, ProofSystemBackend};
        let mut batch = RollupBatch::new(0, StateRoot::zero());
        batch.proof = Some(Proof {
            backend: ProofSystemBackend::Groth16,
            data: vec![1, 2, 3],
            public_inputs: vec![],
        });

        let result = manager.verify_batch(&batch);
        assert!(matches!(result, Err(RollupError::NoVerificationKey)));
    }

    #[test]
    fn test_rollup_manager_check_timeout_no_pending() {
        let manager = RollupManager::new();
        assert!(!manager.check_timeout());
    }

    #[test]
    fn test_rollup_manager_config_accessor() {
        let manager = RollupManager::new();
        let config = manager.config();
        assert_eq!(config.max_batch_size, crate::constants::MAX_BATCH_SIZE);
    }

    #[test]
    fn test_rollup_manager_debug() {
        let manager = RollupManager::new();
        let debug_str = format!("{:?}", manager);
        assert!(debug_str.contains("RollupManager"));
        assert!(debug_str.contains("state"));
    }

    // ============================================================================
    // RollupError Tests
    // ============================================================================

    #[test]
    fn test_rollup_error_not_accepting_display() {
        let error = RollupError::NotAccepting;
        let display = format!("{}", error);
        assert!(display.contains("not accepting"));
    }

    #[test]
    fn test_rollup_error_no_batch_display() {
        let error = RollupError::NoBatch;
        let display = format!("{}", error);
        assert!(display.contains("No batch"));
    }

    #[test]
    fn test_rollup_error_no_proof_display() {
        let error = RollupError::NoProof;
        let display = format!("{}", error);
        assert!(display.contains("No proof"));
    }

    #[test]
    fn test_rollup_error_no_verification_key_display() {
        let error = RollupError::NoVerificationKey;
        let display = format!("{}", error);
        assert!(display.contains("No verification key"));
    }

    #[test]
    fn test_rollup_error_account_not_found_display() {
        let error = RollupError::AccountNotFound(42);
        let display = format!("{}", error);
        assert!(display.contains("Account not found"));
        assert!(display.contains("42"));
    }

    #[test]
    fn test_rollup_error_insufficient_balance_display() {
        let error = RollupError::InsufficientBalance;
        let display = format!("{}", error);
        assert!(display.contains("Insufficient balance"));
    }

    #[test]
    fn test_rollup_error_invalid_transaction_display() {
        let error = RollupError::InvalidTransaction("bad tx".to_string());
        let display = format!("{}", error);
        assert!(display.contains("Invalid transaction"));
        assert!(display.contains("bad tx"));
    }

    #[test]
    fn test_rollup_error_from_state_error() {
        let state_error = StateError::AccountNotFound(99);
        let rollup_error: RollupError = state_error.into();
        let display = format!("{}", rollup_error);
        assert!(display.contains("State error"));
    }

    #[test]
    fn test_rollup_error_from_proof_error() {
        use crate::proof::ProofError;
        let proof_error = ProofError::ProvingError("test".to_string());
        let rollup_error: RollupError = proof_error.into();
        let display = format!("{}", rollup_error);
        assert!(display.contains("Proof error"));
    }

    #[test]
    fn test_rollup_error_debug() {
        let error = RollupError::NotAccepting;
        let debug_str = format!("{:?}", error);
        assert!(debug_str.contains("NotAccepting"));
    }

    // ============================================================================
    // Integration Tests
    // ============================================================================

    #[test]
    fn test_full_workflow() {
        let mut manager = RollupManager::new();

        manager.setup().unwrap();

        manager.register_account(0, [0xaa; 32]).unwrap();
        manager.register_account(1, [0xbb; 32]).unwrap();
        manager.register_account(2, [0xcc; 32]).unwrap();

        manager
            .state_tree
            .update_account(0, |acc| acc.balance = 10000)
            .unwrap();

        manager
            .add_transaction(BatchTransaction::transfer(0, 1, 500))
            .unwrap();
        manager
            .add_transaction(BatchTransaction::deposit(2, 1000, [0x11; 32]))
            .unwrap();

        let batch = manager.finalize_batch().unwrap();

        assert_eq!(batch.tx_count(), 2);
        assert!(batch.is_proven());
        assert!(manager.verify_batch(&batch).is_ok());
    }

    #[test]
    fn test_deposit_then_transfer() {
        let manager = RollupManager::new();

        manager.register_account(0, [0xaa; 32]).unwrap();
        manager.register_account(1, [0xbb; 32]).unwrap();

        manager
            .add_transaction(BatchTransaction::deposit(0, 1000, [0x11; 32]))
            .unwrap();

        manager.finalize_batch().unwrap();
        manager.resume();

        assert_eq!(manager.get_account(0).unwrap().balance, 1000);

        manager
            .add_transaction(BatchTransaction::transfer(0, 1, 500))
            .unwrap();
        manager.finalize_batch().unwrap();

        assert_eq!(manager.get_account(0).unwrap().balance, 500);
        assert_eq!(manager.get_account(1).unwrap().balance, 500);
    }

    #[test]
    fn test_withdrawal_workflow() {
        let manager = RollupManager::new();

        manager.register_account(0, [0xaa; 32]).unwrap();
        manager
            .state_tree
            .update_account(0, |acc| acc.balance = 1000)
            .unwrap();

        manager
            .add_transaction(BatchTransaction::withdraw(0, [0xbb; 20], 300))
            .unwrap();

        manager.finalize_batch().unwrap();

        assert_eq!(manager.get_account(0).unwrap().balance, 700);
    }

    // ============================================================================
    // Edge Cases
    // ============================================================================

    #[test]
    fn test_batch_with_many_transactions() {
        let config = RollupConfig {
            max_batch_size: 100,
            min_batch_size: 1,
            batch_timeout: Duration::from_secs(60),
            tree_depth: 32,
            bridge_address: None,
        };

        let manager = RollupManager::with_config(config);
        manager.register_account(0, [0xaa; 32]).unwrap();
        manager.register_account(1, [0xbb; 32]).unwrap();
        manager
            .state_tree
            .update_account(0, |acc| acc.balance = 1_000_000)
            .unwrap();

        for _ in 0..50 {
            manager
                .add_transaction(BatchTransaction::transfer(0, 1, 10))
                .unwrap();
        }

        assert_eq!(manager.pending_count(), 50);

        let batch = manager.finalize_batch().unwrap();
        assert_eq!(batch.tx_count(), 50);
    }

    #[test]
    fn test_zero_amount_transactions() {
        let manager = RollupManager::new();
        manager.register_account(0, [0xaa; 32]).unwrap();
        manager.register_account(1, [0xbb; 32]).unwrap();
        manager
            .state_tree
            .update_account(0, |acc| acc.balance = 100)
            .unwrap();

        manager
            .add_transaction(BatchTransaction::transfer(0, 1, 0))
            .unwrap();

        assert_eq!(manager.pending_count(), 1);
    }

    #[test]
    fn test_large_account_indices() {
        let manager = RollupManager::new();

        let large_idx = u64::MAX - 1;
        manager.register_account(large_idx, [0xaa; 32]).unwrap();

        let account = manager.get_account(large_idx).unwrap();
        assert_eq!(account.pubkey_hash, [0xaa; 32]);
    }
}
