//! Roundtrip tests for `.era1` files.
//!
//! These tests verify the full lifecycle of era files by:
//! - Reading files from their original source
//! - Decompressing and decoding their contents
//! - Re-encoding and recompressing the data
//! - Writing the data back to a new file
//! - Confirming that all original data is preserved throughout the process
//!
//! Only a couple of era1 files are downloaded from <https://era.ithaca.xyz/era1/> for mainnet
//! and <https://era.ithaca.xyz/sepolia-era1/> for sepolia to keep the tests efficient.

use alloy_consensus::{BlockBody, BlockHeader, Header, ReceiptEnvelope};
use reth_era::{
    common::file_ops::{EraFileFormat, StreamReader, StreamWriter},
    e2s::types::IndexEntry,
    era1::{
        file::{Era1File, Era1Reader, Era1Writer},
        types::{
            execution::{
                BlockTuple, CompressedBody, CompressedHeader, CompressedReceipts, TotalDifficulty,
            },
            group::{Era1Group, Era1Id},
        },
    },
};
use reth_ethereum_primitives::TransactionSigned;
use std::io::Cursor;

use crate::{EraTestDownloader, MAINNET, SEPOLIA};

// Helper function to test roundtrip compression/encoding for a specific file
async fn test_era1_file_roundtrip(
    downloader: &EraTestDownloader,
    filename: &str,
    network: &str,
) -> eyre::Result<()> {
    println!("\nTesting roundtrip for file: {filename}");

    let original_file = downloader.open_era1_file(filename, network).await?;

    // Select a few blocks to test
    let test_block_indices = [
        0,                                    // First block
        original_file.group.blocks.len() / 2, // Middle block
        original_file.group.blocks.len() - 1, // Last block
    ];

    // Write the entire file to a buffer
    let mut buffer = Vec::new();
    {
        let mut writer = Era1Writer::new(&mut buffer);
        writer.write_file(&original_file)?;
    }

    // Read back from buffer
    let reader = Era1Reader::new(Cursor::new(&buffer));
    let roundtrip_file = reader.read(network.to_string())?;

    assert_eq!(
        original_file.id.network_name, roundtrip_file.id.network_name,
        "Network name should match after roundtrip"
    );
    assert_eq!(
        original_file.id.start_block, roundtrip_file.id.start_block,
        "Start block should match after roundtrip"
    );
    assert_eq!(
        original_file.group.blocks.len(),
        roundtrip_file.group.blocks.len(),
        "Block count should match after roundtrip"
    );
    assert_eq!(
        original_file.group.accumulator.root, roundtrip_file.group.accumulator.root,
        "Accumulator root should match after roundtrip"
    );

    // Test individual blocks
    for &block_id in &test_block_indices {
        let original_block = &original_file.group.blocks[block_id];
        let roundtrip_block = &roundtrip_file.group.blocks[block_id];
        let block_number = original_file.group.block_index.starting_number() + block_id as u64;

        println!("Testing roundtrip for block {block_number}");

        // Test header decompression
        let original_header_data = original_block.header.decompress()?;
        let roundtrip_header_data = roundtrip_block.header.decompress()?;
        assert_eq!(
            original_header_data, roundtrip_header_data,
            "Block {block_number} header data should be identical after roundtrip"
        );

        // Test body decompression
        let original_body_data = original_block.body.decompress()?;
        let roundtrip_body_data = roundtrip_block.body.decompress()?;
        assert_eq!(
            original_body_data, roundtrip_body_data,
            "Block {block_number} body data should be identical after roundtrip"
        );

        // Test receipts decompression
        let original_receipts_data = original_block.receipts.decompress()?;

        let roundtrip_receipts_data = roundtrip_block.receipts.decompress()?;
        assert_eq!(
            original_receipts_data, roundtrip_receipts_data,
            "Block {block_number} receipts data should be identical after roundtrip"
        );

        // Test total difficulty preservation
        assert_eq!(
            original_block.total_difficulty.value, roundtrip_block.total_difficulty.value,
            "Block {block_number} total difficulty should be identical after roundtrip",
        );
        // Test decoding of header and body to ensure structural integrity
        let original_header = original_block.header.decode_header()?;
        let roundtrip_header = roundtrip_block.header.decode_header()?;
        assert_eq!(
            original_header.number, roundtrip_header.number,
            "Block number should match after roundtrip decoding"
        );
        assert_eq!(
            original_header.mix_hash(),
            roundtrip_header.mix_hash(),
            "Block hash should match after roundtrip decoding"
        );

        // Decode and verify body contents
        let original_decoded_body: BlockBody<TransactionSigned> =
            CompressedBody::decode_body_from_decompressed::<TransactionSigned, Header>(
                &original_body_data,
            )
            .expect("Failed to decode original body");

        let roundtrip_decoded_body: BlockBody<TransactionSigned> =
            CompressedBody::decode_body_from_decompressed::<TransactionSigned, Header>(
                &roundtrip_body_data,
            )
            .expect("Failed to original decode body");

        assert_eq!(
            original_decoded_body.transactions.len(),
            roundtrip_decoded_body.transactions.len(),
            "Transaction count should match after roundtrip between original and roundtrip"
        );

        assert_eq!(
            original_decoded_body.ommers.len(),
            roundtrip_decoded_body.ommers.len(),
            "Ommers count should match after roundtrip"
        );

        // Decode receipts
        let original_receipts_decoded = original_block.receipts.decode::<Vec<ReceiptEnvelope>>()?;
        let roundtrip_receipts_decoded =
            roundtrip_block.receipts.decode::<Vec<ReceiptEnvelope>>()?;

        assert_eq!(
            original_receipts_decoded, roundtrip_receipts_decoded,
            "Block {block_number} decoded receipts should be identical after roundtrip"
        );
        assert_eq!(
            original_receipts_data, roundtrip_receipts_data,
            "Block {block_number} receipts data should be identical after roundtrip"
        );

        // Check withdrawals presence/absence matches
        assert_eq!(
            original_decoded_body.withdrawals.is_some(),
            roundtrip_decoded_body.withdrawals.is_some(),
            "Withdrawals presence should match after roundtrip"
        );

        println!("Block {block_number} roundtrip verified successfully");

        println!("Testing full re-encoding/re-compression cycle for block {block_number}");

        // Re-encode and re-compress the header
        let recompressed_header = CompressedHeader::from_header(&original_header)?;
        let recompressed_header_data = recompressed_header.decompress()?;
        assert_eq!(
            original_header_data, recompressed_header_data,
            "Re-compressed header data should match original after full cycle"
        );

        // Re-encode and re-compress the body
        let recompressed_body = CompressedBody::from_body(&original_decoded_body)?;
        let recompressed_body_data = recompressed_body.decompress()?;

        let recompressed_decoded_body: BlockBody<TransactionSigned> =
            CompressedBody::decode_body_from_decompressed::<TransactionSigned, Header>(
                &recompressed_body_data,
            )
            .expect("Failed to decode re-compressed body");

        assert_eq!(
            original_decoded_body.transactions.len(),
            recompressed_decoded_body.transactions.len(),
            "Transaction count should match after re-compression"
        );

        // Re-encore and re-compress the receipts
        let recompressed_receipts =
            CompressedReceipts::from_encodable(&roundtrip_receipts_decoded)?;
        let recompressed_receipts_data = recompressed_receipts.decompress()?;

        assert_eq!(
            original_receipts_data.len(),
            recompressed_receipts_data.len(),
            "Receipts length should match after re-compression"
        );

        let recompressed_block = BlockTuple::new(
            recompressed_header,
            recompressed_body,
            recompressed_receipts,
            TotalDifficulty::new(original_block.total_difficulty.value),
        );

        let mut recompressed_buffer = Vec::new();
        {
            let blocks = vec![recompressed_block];

            let new_group = Era1Group::new(
                blocks,
                original_file.group.accumulator.clone(),
                original_file.group.block_index.clone(),
            );

            let new_file =
                Era1File::new(new_group, Era1Id::new(network, original_file.id.start_block, 1));

            let mut writer = Era1Writer::new(&mut recompressed_buffer);
            writer.write_file(&new_file)?;
        }

        let reader = Era1Reader::new(Cursor::new(&recompressed_buffer));
        let recompressed_file = reader.read(network.to_string())?;

        let recompressed_first_block = &recompressed_file.group.blocks[0];
        let recompressed_header = recompressed_first_block.header.decode_header()?;

        assert_eq!(
            original_header.number, recompressed_header.number,
            "Block number should match after complete re-encoding cycle"
        );

        println!(
            "Block {block_number} full re-encoding/re-compression cycle verified successfully 🫡"
        );
    }

    println!("File {filename} roundtrip successful");
    Ok(())
}

#[test_case::test_case("mainnet-00000-5ec1ffb8.era1"; "era1_roundtrip_mainnet_0")]
#[test_case::test_case("mainnet-00151-e322efe1.era1"; "era1_roundtrip_mainnet_151")]
#[test_case::test_case("mainnet-01367-d7efc68f.era1"; "era1_roundtrip_mainnet_1367")]
#[test_case::test_case("mainnet-01895-3f81607c.era1"; "era1_roundtrip_mainnet_1895")]
#[tokio::test(flavor = "multi_thread")]
#[ignore = "download intensive"]
async fn test_roundtrip_compression_encoding_mainnet(filename: &str) -> eyre::Result<()> {
    let downloader = EraTestDownloader::new().await?;
    test_era1_file_roundtrip(&downloader, filename, MAINNET).await
}

#[test_case::test_case("sepolia-00000-643a00f7.era1"; "era1_roundtrip_sepolia_0")]
#[test_case::test_case("sepolia-00074-0e81003c.era1"; "era1_roundtrip_sepolia_74")]
#[test_case::test_case("sepolia-00173-b6924da5.era1"; "era1_roundtrip_sepolia_173")]
#[test_case::test_case("sepolia-00182-a4f0a8a1.era1"; "era1_roundtrip_sepolia_182")]
#[tokio::test(flavor = "multi_thread")]
#[ignore = "download intensive"]
async fn test_roundtrip_compression_encoding_sepolia(filename: &str) -> eyre::Result<()> {
    let downloader = EraTestDownloader::new().await?;

    test_era1_file_roundtrip(&downloader, filename, SEPOLIA).await?;

    Ok(())
}
