//! Protocol Extensions Tests
//!
//! Tests for UTXO commitments protocol extensions.

use blvm_node::network::protocol::{FilterPreferences, GetFilteredBlockMessage, GetUTXOSetMessage};
use blvm_node::network::protocol_extensions::{handle_get_filtered_block, handle_get_utxo_set};
use blvm_node::storage::Storage;
use blvm_node::storage::hashing::double_sha256;
use blvm_node::{Block, BlockHeader, Transaction};
use std::sync::Arc;

/// Compute the Bitcoin double-SHA256 block hash the same way as BlockStore::block_hash.
fn block_hash(block: &Block) -> [u8; 32] {
    let mut header_data = [0u8; 80];
    header_data[0..4].copy_from_slice(&(block.header.version as i32).to_le_bytes());
    header_data[4..36].copy_from_slice(&block.header.prev_block_hash);
    header_data[36..68].copy_from_slice(&block.header.merkle_root);
    header_data[68..72].copy_from_slice(&(block.header.timestamp as u32).to_le_bytes());
    header_data[72..76].copy_from_slice(&(block.header.bits as u32).to_le_bytes());
    header_data[76..80].copy_from_slice(&(block.header.nonce as u32).to_le_bytes());
    double_sha256(&header_data)
}

/// Unique temp dir per call; `Storage::new` initializes all backend tables.
fn create_test_storage() -> Arc<Storage> {
    let temp_dir = std::env::temp_dir().join(format!(
        "blvm_pe_test_{}_{}",
        std::process::id(),
        std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .unwrap()
            .as_nanos()
    ));
    std::fs::create_dir_all(&temp_dir).unwrap();
    Arc::new(Storage::new(&temp_dir).unwrap())
}

#[tokio::test]
async fn test_handle_get_utxo_set_no_storage() {
    // Test GetUTXOSet with no storage available
    let message = GetUTXOSetMessage {
        height: 0,
        block_hash: [0; 32],
    };

    let result = handle_get_utxo_set(message, None).await;

    assert!(result.is_err());
    assert!(
        result
            .unwrap_err()
            .to_string()
            .contains("Storage not available")
    );
}

#[tokio::test]
async fn test_handle_get_utxo_set_with_storage() {
    // Test GetUTXOSet with storage available
    let storage = create_test_storage();
    let message = GetUTXOSetMessage {
        height: 0,
        block_hash: [0; 32],
    };

    let result = handle_get_utxo_set(message, Some(storage)).await;

    // Should succeed even with empty storage
    assert!(result.is_ok());
    let response = result.unwrap();
    assert_eq!(response.commitment.block_height, 0);
}

#[tokio::test]
async fn test_handle_get_utxo_set_specific_height() {
    // Test GetUTXOSet with specific height
    let storage = create_test_storage();
    let message = GetUTXOSetMessage {
        height: 100,
        block_hash: [0x42; 32],
    };

    let result = handle_get_utxo_set(message, Some(storage)).await;

    assert!(result.is_ok());
    let response = result.unwrap();
    assert_eq!(response.commitment.block_height, 100);
}

#[tokio::test]
async fn test_handle_get_filtered_block_no_storage() {
    // Test GetFilteredBlock with no storage
    let message = GetFilteredBlockMessage {
        request_id: 12345,
        block_hash: [0x42; 32],
        filter_preferences: FilterPreferences {
            filter_ordinals: false,
            filter_dust: false,
            filter_brc20: false,
            min_output_value: 0,
        },
        include_bip158_filter: false,
    };

    let result = handle_get_filtered_block(message, None, None).await;

    assert!(result.is_err());
    assert!(
        result
            .unwrap_err()
            .to_string()
            .contains("Storage not available")
    );
}

#[tokio::test]
async fn test_handle_get_filtered_block_block_not_found() {
    // Test GetFilteredBlock with block not in storage
    let storage = create_test_storage();
    let message = GetFilteredBlockMessage {
        request_id: 12345,
        block_hash: [0xFF; 32], // Non-existent block
        filter_preferences: FilterPreferences {
            filter_ordinals: false,
            filter_dust: false,
            filter_brc20: false,
            min_output_value: 0,
        },
        include_bip158_filter: false,
    };

    let result = handle_get_filtered_block(message, Some(storage), None).await;

    // Should return error for block not found
    assert!(result.is_err());
}

#[tokio::test]
async fn test_handle_get_filtered_block_with_preferences() {
    // Test GetFilteredBlock with different filter preferences
    let storage = create_test_storage();

    // Add a test block to storage
    let block = Block {
        header: BlockHeader {
            version: 1,
            prev_block_hash: [0; 32],
            merkle_root: [0; 32],
            timestamp: 1231006505,
            bits: 0x1d00ffff,
            nonce: 0,
        },
        transactions: vec![Transaction {
            version: 1,
            inputs: vec![].into(),
            outputs: vec![].into(),
            lock_time: 0,
        }]
        .into_boxed_slice(),
    };

    let block_hash_val = block_hash(&block);
    storage.blocks().store_block(&block).unwrap();
    storage.blocks().store_height(0, &block_hash_val).unwrap();

    let message = GetFilteredBlockMessage {
        request_id: 12345,
        block_hash: block_hash_val,
        filter_preferences: FilterPreferences {
            filter_ordinals: true,
            filter_dust: true,
            filter_brc20: true,
            min_output_value: 1000,
        },
        include_bip158_filter: false,
    };

    let result = handle_get_filtered_block(message, Some(storage), None).await;

    // Should succeed with filter preferences
    assert!(result.is_ok());
    let response = result.unwrap();
    assert_eq!(response.request_id, 12345);
}

#[tokio::test]
async fn test_handle_get_filtered_block_with_bip158() {
    // Test GetFilteredBlock with BIP158 filter requested
    let storage = create_test_storage();

    let block = Block {
        header: BlockHeader {
            version: 1,
            prev_block_hash: [0; 32],
            merkle_root: [0; 32],
            timestamp: 1231006505,
            bits: 0x1d00ffff,
            nonce: 0,
        },
        transactions: vec![Transaction {
            version: 1,
            inputs: vec![].into(),
            outputs: vec![].into(),
            lock_time: 0,
        }]
        .into_boxed_slice(),
    };

    let block_hash_val = block_hash(&block);
    storage.blocks().store_block(&block).unwrap();
    storage.blocks().store_height(0, &block_hash_val).unwrap();

    let message = GetFilteredBlockMessage {
        request_id: 12345,
        block_hash: block_hash_val,
        filter_preferences: FilterPreferences {
            filter_ordinals: false,
            filter_dust: false,
            filter_brc20: false,
            min_output_value: 0,
        },
        include_bip158_filter: true,
    };

    let result = handle_get_filtered_block(message, Some(storage), None).await;

    // Should succeed with BIP158 filter
    assert!(result.is_ok());
    let response = result.unwrap();
    // BIP158 filter may or may not be present depending on filter_service availability
    assert_eq!(response.request_id, 12345);
}

#[tokio::test]
async fn test_handle_get_filtered_block_uses_block_height_not_tip() {
    let storage = create_test_storage();

    let block = Block {
        header: BlockHeader {
            version: 1,
            prev_block_hash: [0; 32],
            merkle_root: [0; 32],
            timestamp: 1231006505,
            bits: 0x1d00ffff,
            nonce: 0,
        },
        transactions: vec![Transaction {
            version: 1,
            inputs: vec![].into(),
            outputs: vec![].into(),
            lock_time: 0,
        }]
        .into_boxed_slice(),
    };

    let block_hash_val = block_hash(&block);
    storage.blocks().store_block(&block).unwrap();
    storage.blocks().store_height(5, &block_hash_val).unwrap();

    // Tip at a different height than the requested block.
    let tip_header = BlockHeader {
        version: 1,
        prev_block_hash: block_hash_val,
        merkle_root: [1; 32],
        timestamp: 1231006600,
        bits: 0x1d00ffff,
        nonce: 1,
    };
    let tip_hash = block_hash(&Block {
        header: tip_header.clone(),
        transactions: vec![].into_boxed_slice(),
    });
    storage
        .chain()
        .update_tip(&tip_hash, &tip_header, 100)
        .unwrap();

    let message = GetFilteredBlockMessage {
        request_id: 99,
        block_hash: block_hash_val,
        filter_preferences: FilterPreferences {
            filter_ordinals: false,
            filter_dust: false,
            filter_brc20: false,
            min_output_value: 0,
        },
        include_bip158_filter: false,
    };

    let response = handle_get_filtered_block(message, Some(storage), None)
        .await
        .unwrap();
    assert_eq!(response.commitment.block_height, 5);
    assert_ne!(response.commitment.block_height, 100);
}
