//! Compression module implementing Rules 3, 4, and 6 (DataSource abstraction)
//!
//! # Evidence Base
//!
//! - **Rule 3**: Parallel bgzip decompression (6.5× speedup, Entry 029)
//! - **Rule 4**: Smart mmap for files ≥50 MB (2.5× additional, Entry 032)
//! - **Rule 6**: DataSource abstraction for network streaming (Entry 028)
//!
//! # Combined Performance
//!
//! - Small files (<50 MB): 6.5× (parallel bgzip only)
//! - Large files (≥50 MB): 16.3× (6.5 × 2.5, layered optimization)

use crate::error::{BiometalError, Result};
use crate::io::DataSink;
use flate2::read::GzDecoder;
use flate2::write::GzEncoder;
use flate2::Compression;
use memmap2::Mmap;
use rayon::prelude::*;
use std::fs::File;
use std::io::{self, BufRead, BufReader, BufWriter, Read, Write};
use std::path::{Path, PathBuf};

/// Memory-mapped file threshold (50 MB)
///
/// # Evidence
///
/// Entry 032 (scale validation across 0.54-544 MB):
/// - Files <50 MB: 0.66-0.99× (overhead dominates, don't use mmap)
/// - Files ≥50 MB: 2.30-2.55× speedup (APFS prefetching benefit)
pub const MMAP_THRESHOLD: u64 = 50 * 1024 * 1024; // 50 MB

/// Number of bgzip blocks to decompress in parallel
///
/// # Evidence & Memory Budget
///
/// Entry 029: Parallel bgzip achieves 6.5× speedup using rayon.
///
/// Memory budget (8 blocks in parallel):
/// - Compressed: 8 × ~64 KB = ~512 KB
/// - Decompressed: 8 × ~65 KB = ~520 KB (BGZF spec: max 64 KB uncompressed per block)
/// - Total: ~1 MB (bounded, regardless of file size)
///
/// # BGZF Specification Guarantee
///
/// The BGZF (Blocked GNU Zip Format) specification guarantees that each block
/// decompresses to a maximum of 64 KB uncompressed. This ensures our memory bounds
/// are respected even for malformed files. If a block violates the spec and
/// decompresses larger, the memory is still bounded to PARALLEL_BLOCK_COUNT × actual_size
/// and gets cleared on the next chunk.
///
/// This delivers Rule 3 (parallel speedup) while maintaining Rule 5 (constant memory).
pub const PARALLEL_BLOCK_COUNT: usize = 8;

/// Data source abstraction for local and network streaming
///
/// # Architecture
///
/// This enum is designed Day 1 (Rule 6 requirement) but implements variants incrementally:
/// - **Week 1-2**: Local file support (with Rules 3+4 optimization)
/// - **Week 3-4**: HTTP and SRA streaming implementation
///
/// # Evidence
///
/// Entry 028: I/O bottleneck dominates 264-352× compared to compute time.
/// Network streaming is CRITICAL, not optional.
#[derive(Debug, Clone)]
pub enum DataSource {
    /// Local file path
    Local(PathBuf),

    /// HTTP/HTTPS URL (Week 3-4 implementation)
    #[cfg(feature = "network")]
    Http(String),

    /// SRA accession (Week 3-4 implementation)
    #[cfg(feature = "network")]
    Sra(String),
}

impl DataSource {
    /// Create a local file data source
    pub fn from_path<P: AsRef<Path>>(path: P) -> Self {
        DataSource::Local(path.as_ref().to_path_buf())
    }

    /// Open the data source and return a buffered reader
    ///
    /// # Implementation Status
    ///
    /// - ✅ Local: Full implementation (Rules 3+4)
    /// - ⏳ Http/Sra: Stub (Week 3-4)
    pub fn open(&self) -> Result<Box<dyn BufRead + Send>> {
        match self {
            DataSource::Local(path) => open_local_file(path),

            #[cfg(feature = "network")]
            DataSource::Http(url) => {
                use crate::io::network::HttpReader;
                let reader = HttpReader::new(url)?;
                Ok(Box::new(std::io::BufReader::new(reader)))
            }

            #[cfg(feature = "network")]
            DataSource::Sra(accession) => {
                use crate::io::network::HttpReader;
                use crate::io::sra::sra_to_url;

                // Convert SRA accession to HTTP URL
                let url = sra_to_url(accession)?;
                let reader = HttpReader::new(&url)?;
                Ok(Box::new(std::io::BufReader::new(reader)))
            }
        }
    }
}

/// Open a local file with smart I/O method selection (Rule 4)
///
/// # Evidence
///
/// Entry 032 (threshold-based mmap):
/// - Small files (<50 MB): Use standard I/O (faster)
/// - Large files (≥50 MB): Use mmap + madvise (2.5× speedup on macOS)
fn open_local_file(path: &Path) -> Result<Box<dyn BufRead + Send>> {
    let metadata = std::fs::metadata(path)?;
    let file_size = metadata.len();

    if file_size >= MMAP_THRESHOLD {
        // Large file: Use memory-mapped I/O (Rule 4, 2.5× speedup)
        open_mmap_file(path)
    } else {
        // Small file: Use standard I/O (avoids mmap overhead)
        let file = File::open(path)?;
        Ok(Box::new(BufReader::new(file)))
    }
}

/// Open file with memory mapping and platform-specific optimization hints
///
/// # Platform Support
///
/// - ✅ macOS: Uses madvise(MADV_SEQUENTIAL | MADV_WILLNEED) for APFS optimization
/// - ⏳ Linux: Future validation (Week 3-4)
/// - ❌ Windows: Falls back to standard I/O
#[cfg(target_os = "macos")]
fn open_mmap_file(path: &Path) -> Result<Box<dyn BufRead + Send>> {
    use libc::{madvise, MADV_SEQUENTIAL, MADV_WILLNEED};

    let file = File::open(path)?;
    let mmap = unsafe { Mmap::map(&file)? };

    // Give kernel sequential access hints for APFS optimization
    unsafe {
        madvise(
            mmap.as_ptr() as *mut _,
            mmap.len(),
            MADV_SEQUENTIAL | MADV_WILLNEED,
        );
    }

    // Wrap mmap in a cursor that implements BufRead
    Ok(Box::new(std::io::Cursor::new(mmap)))
}

#[cfg(not(target_os = "macos"))]
fn open_mmap_file(path: &Path) -> Result<Box<dyn BufRead + Send>> {
    // For non-macOS platforms, use standard mmap without madvise hints
    let file = File::open(path)?;
    let mmap = unsafe { Mmap::map(&file)? };
    Ok(Box::new(std::io::Cursor::new(mmap)))
}

/// Bgzip block structure
///
/// Bgzip files consist of independent compressed blocks that can be
/// decompressed in parallel (Rule 3).
#[derive(Debug, Clone)]
struct BgzipBlock {
    /// Compressed block data
    data: Vec<u8>,
    /// Block size in bytes (reserved for future proper block parsing)
    #[allow(dead_code)]
    size: usize,
}

/// Parse bgzip blocks from compressed data
///
/// # Bgzip Format
///
/// Bgzip is a variant of gzip that uses fixed-size blocks (typically 64KB uncompressed).
/// Each block is an independent gzip stream, enabling parallel decompression.
///
/// # Block Structure
///
/// Each bgzip block:
/// - Bytes 0-1: Gzip magic (31, 139)
/// - Bytes 2-9: Standard gzip header fields
/// - Bytes 10-11: XLEN (extra field length)
/// - Bytes 12+: Extra subfields, including BSIZE
///   - SI1=66 ('B'), SI2=67 ('C') for bgzip
///   - SLEN=2 (2-byte BSIZE field)
///   - BSIZE (little-endian u16): total block size - 1
///
/// # Evidence
///
/// Entry 029: Parallel bgzip decompression achieves 6.5× speedup using rayon.
fn parse_bgzip_blocks(data: &[u8]) -> Result<Vec<BgzipBlock>> {
    let mut blocks = Vec::new();
    let mut pos = 0;

    while pos < data.len() {
        // Need at least 18 bytes for minimal gzip header
        if pos + 18 > data.len() {
            if pos == data.len() {
                break; // Clean EOF
            }
            return Err(BiometalError::Compression(format!(
                "Incomplete gzip header at offset {} (only {} bytes remaining)",
                pos,
                data.len() - pos
            )));
        }

        // Check gzip magic bytes (ID1=31, ID2=139)
        if data[pos] != 31 || data[pos + 1] != 139 {
            return Err(BiometalError::Compression(format!(
                "Invalid gzip magic bytes at offset {}: expected [31, 139], got [{}, {}]",
                pos, data[pos], data[pos + 1]
            )));
        }

        // Check for extra field flag (FLG & 0x04)
        let flg = data[pos + 3];
        if flg & 0x04 == 0 {
            // No extra field - this might be regular gzip, not bgzip
            // For safety, treat entire remaining data as one block
            blocks.push(BgzipBlock {
                data: data[pos..].to_vec(),
                size: data.len() - pos,
            });
            break;
        }

        // Read XLEN (extra field length) at bytes 10-11 (little-endian)
        let xlen = u16::from_le_bytes([data[pos + 10], data[pos + 11]]) as usize;

        // Extra field starts at byte 12
        let extra_start = pos + 12;
        let extra_end = extra_start + xlen;

        if extra_end > data.len() {
            return Err(BiometalError::Compression(format!(
                "Extra field extends beyond data at offset {}", pos
            )));
        }

        // Parse extra subfields to find BSIZE
        let mut bsize: Option<u16> = None;
        let mut extra_pos = extra_start;

        while extra_pos + 4 <= extra_end {
            let si1 = data[extra_pos];
            let si2 = data[extra_pos + 1];
            let slen = u16::from_le_bytes([data[extra_pos + 2], data[extra_pos + 3]]) as usize;

            // Check for bgzip subfield (SI1='B'=66, SI2='C'=67)
            if si1 == 66 && si2 == 67 && slen == 2 {
                if extra_pos + 6 > extra_end {
                    return Err(BiometalError::Compression(format!(
                        "BSIZE field incomplete at offset {}", pos
                    )));
                }
                bsize = Some(u16::from_le_bytes([
                    data[extra_pos + 4],
                    data[extra_pos + 5],
                ]));
                break;
            }

            extra_pos += 4 + slen;
        }

        // Calculate block size
        let block_size = match bsize {
            Some(bs) => (bs as usize) + 1, // BSIZE is block_size - 1
            None => {
                // No BSIZE field found - might be regular gzip
                // Treat remaining data as one block
                blocks.push(BgzipBlock {
                    data: data[pos..].to_vec(),
                    size: data.len() - pos,
                });
                break;
            }
        };

        // Validate block size
        if pos + block_size > data.len() {
            return Err(BiometalError::Compression(format!(
                "Block size {} at offset {} exceeds remaining data ({} bytes)",
                block_size,
                pos,
                data.len() - pos
            )));
        }

        // Extract block
        blocks.push(BgzipBlock {
            data: data[pos..pos + block_size].to_vec(),
            size: block_size,
        });

        pos += block_size;
    }

    if blocks.is_empty() {
        return Err(BiometalError::Compression(
            "No valid bgzip blocks found".to_string(),
        ));
    }

    Ok(blocks)
}

/// Decompress a single bgzip block
fn decompress_block(block: &BgzipBlock) -> io::Result<Vec<u8>> {
    let mut decoder = GzDecoder::new(&block.data[..]);
    let mut decompressed = Vec::new();
    decoder.read_to_end(&mut decompressed)?;
    Ok(decompressed)
}

/// Bounded parallel bgzip reader (Rules 3 + 5)
///
/// # Architecture
///
/// Processes blocks in chunks of PARALLEL_BLOCK_COUNT (8):
/// 1. Read 8 blocks from input stream
/// 2. Decompress 8 blocks in parallel using rayon (Rule 3: 6.5× speedup)
/// 3. Buffer concatenated results
/// 4. Yield data incrementally
/// 5. Repeat
///
/// # Memory Footprint (Rule 5)
///
/// Bounded regardless of file size:
/// - Compressed blocks: 8 × ~64 KB = ~512 KB
/// - Decompressed blocks: 8 × ~65 KB = ~520 KB
/// - Total: ~1 MB (constant, even for 5TB files)
///
/// # Evidence
///
/// - Rule 3 (Entry 029): Parallel bgzip achieves 6.5× speedup
/// - Rule 5 (Entry 026): Constant memory streaming (~5 MB)
/// - Combined: Delivers both parallelism AND constant memory
struct BoundedParallelBgzipReader<R: BufRead> {
    inner: R,
    /// Buffer for decompressed data ready to read
    output_buffer: Vec<u8>,
    /// Current read position in output_buffer
    output_pos: usize,
    /// Whether we've reached EOF
    eof: bool,
}

impl<R: BufRead> BoundedParallelBgzipReader<R> {
    fn new(inner: R) -> Self {
        Self {
            inner,
            output_buffer: Vec::new(),
            output_pos: 0,
            eof: false,
        }
    }

    /// Read one bgzip block from stream
    fn read_one_block(&mut self) -> io::Result<Option<BgzipBlock>> {
        // Read block header (10 bytes standard gzip + 2 bytes XLEN)
        let mut header = [0u8; 12];
        match self.inner.read_exact(&mut header) {
            Ok(_) => {}
            Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => {
                return Ok(None); // Clean EOF
            }
            Err(e) => return Err(e),
        }

        // Check gzip magic
        if header[0] != 31 || header[1] != 139 {
            return Err(io::Error::new(
                io::ErrorKind::InvalidData,
                format!("Invalid gzip magic: [{}, {}]", header[0], header[1]),
            ));
        }

        // Check for extra field (FLG & 0x04)
        let flg = header[3];
        if flg & 0x04 == 0 {
            // Regular gzip, not bgzip - read entire remaining stream
            let mut compressed = header.to_vec();
            self.inner.read_to_end(&mut compressed)?;
            let size = compressed.len();
            return Ok(Some(BgzipBlock {
                data: compressed,
                size,
            }));
        }

        // Read XLEN (extra field length)
        let xlen = u16::from_le_bytes([header[10], header[11]]) as usize;

        // Read extra field
        let mut extra = vec![0u8; xlen];
        self.inner.read_exact(&mut extra)?;

        // Parse BSIZE from extra field
        let mut bsize: Option<u16> = None;
        let mut pos = 0;

        while pos + 4 <= xlen {
            let si1 = extra[pos];
            let si2 = extra[pos + 1];
            let slen = u16::from_le_bytes([extra[pos + 2], extra[pos + 3]]) as usize;

            if si1 == 66 && si2 == 67 && slen == 2 {
                // Found BSIZE
                if pos + 6 > xlen {
                    return Err(io::Error::new(
                        io::ErrorKind::InvalidData,
                        "Incomplete BSIZE field",
                    ));
                }
                bsize = Some(u16::from_le_bytes([extra[pos + 4], extra[pos + 5]]));
                break;
            }

            pos += 4 + slen;
        }

        let block_size = match bsize {
            Some(bs) => (bs as usize) + 1,
            None => {
                // No BSIZE - treat as regular gzip
                let mut compressed = header.to_vec();
                compressed.extend_from_slice(&extra);
                self.inner.read_to_end(&mut compressed)?;
                let size = compressed.len();
                return Ok(Some(BgzipBlock {
                    data: compressed,
                    size,
                }));
            }
        };

        // Calculate remaining bytes to read (block_size - header - extra)
        let already_read = 12 + xlen;
        if block_size < already_read {
            return Err(io::Error::new(
                io::ErrorKind::InvalidData,
                format!("Invalid block size: {} < {}", block_size, already_read),
            ));
        }
        let remaining = block_size - already_read;

        // Build complete block
        let mut block_data = Vec::with_capacity(block_size);
        block_data.extend_from_slice(&header);
        block_data.extend_from_slice(&extra);

        // Read remaining block data
        let mut rest = vec![0u8; remaining];
        self.inner.read_exact(&mut rest)?;
        block_data.extend_from_slice(&rest);

        Ok(Some(BgzipBlock {
            data: block_data,
            size: block_size,
        }))
    }

    /// Read and decompress next chunk of blocks in parallel
    fn read_next_chunk(&mut self) -> io::Result<()> {
        if self.eof {
            return Ok(());
        }

        // Read up to PARALLEL_BLOCK_COUNT blocks
        let mut blocks = Vec::with_capacity(PARALLEL_BLOCK_COUNT);

        for _ in 0..PARALLEL_BLOCK_COUNT {
            match self.read_one_block()? {
                Some(block) => blocks.push(block),
                None => {
                    self.eof = true;
                    break;
                }
            }
        }

        if blocks.is_empty() {
            return Ok(());
        }

        // Decompress blocks in parallel (Rule 3: 6.5× speedup)
        let decompressed_blocks: Vec<_> = blocks
            .par_iter()
            .map(decompress_block)
            .collect::<io::Result<Vec<_>>>()?;

        // Concatenate decompressed blocks into output buffer
        self.output_buffer.clear();
        for block_data in decompressed_blocks {
            self.output_buffer.extend_from_slice(&block_data);
        }
        self.output_pos = 0;

        Ok(())
    }
}

impl<R: BufRead> Read for BoundedParallelBgzipReader<R> {
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
        // If output buffer is exhausted, read and decompress next chunk
        if self.output_pos >= self.output_buffer.len() {
            if self.eof {
                return Ok(0); // EOF
            }

            self.read_next_chunk()?;

            // Check if we got any data
            if self.output_buffer.is_empty() {
                return Ok(0); // EOF
            }
        }

        // Copy from output buffer to caller's buffer
        let available = self.output_buffer.len() - self.output_pos;
        let to_copy = available.min(buf.len());

        buf[..to_copy].copy_from_slice(
            &self.output_buffer[self.output_pos..self.output_pos + to_copy],
        );
        self.output_pos += to_copy;

        Ok(to_copy)
    }
}

/// Decompress bgzip file in parallel (Rule 3)
///
/// # Evidence
///
/// Entry 029: CPU parallel prototype achieves 6.5× speedup.
/// - Implementation: Rayon-based parallelism
/// - Platform: All platforms (Mac, Linux, Windows, ARM, x86_64)
/// - Cost: Zero platform dependencies (pure Rust + Rayon)
///
/// # Performance
///
/// - Uses all available CPU cores
/// - Each block decompressed independently
/// - Automatic work distribution via rayon
///
/// # Example
///
/// ```no_run
/// use biometal::io::compression::decompress_bgzip_parallel;
///
/// # fn main() -> std::io::Result<()> {
/// let compressed_data = std::fs::read("file.fq.gz")?;
/// let decompressed = decompress_bgzip_parallel(&compressed_data)?;
/// # Ok(())
/// # }
/// ```
pub fn decompress_bgzip_parallel(data: &[u8]) -> io::Result<Vec<u8>> {
    // Parse bgzip block boundaries
    let blocks = parse_bgzip_blocks(data)
        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;

    // Decompress blocks in parallel (uses all CPU cores)
    let decompressed_blocks: Vec<_> = blocks
        .par_iter()
        .map(decompress_block)
        .collect::<io::Result<Vec<_>>>()?;

    // Concatenate decompressed blocks
    Ok(decompressed_blocks.concat())
}

/// Compressed file reader with full optimization stack (Rules 3+4)
///
/// # Evidence-Based Optimization
///
/// This reader combines:
/// - **Rule 3**: Parallel bgzip decompression (6.5× speedup)
/// - **Rule 4**: Threshold-based mmap (2.5× additional for ≥50 MB)
/// - **Combined**: 16.3× speedup for large files
///
/// # Example
///
/// ```no_run
/// use biometal::io::compression::{DataSource, CompressedReader};
///
/// # fn main() -> biometal::Result<()> {
/// let source = DataSource::from_path("large.fq.gz");
/// let reader = CompressedReader::new(source)?;
///
/// // Reader implements BufRead, use with FASTQ parser
/// # Ok(())
/// # }
/// ```
pub struct CompressedReader {
    /// Inner buffered reader (from decompressed data)
    inner: Box<dyn BufRead + Send>,
}

impl CompressedReader {
    /// Create a new compressed reader from a data source
    ///
    /// # Optimization Stack (Rules 3-6)
    ///
    /// 1. Opens data source (Rule 6 abstraction)
    /// 2. Applies threshold-based mmap if local file ≥50 MB (Rule 4)
    /// 3. Decompresses blocks in parallel chunks (Rule 3: 6.5× speedup)
    /// 4. Maintains constant memory (Rule 5: ~1 MB bounded)
    ///
    /// # Memory Usage (Rule 5)
    ///
    /// Bounded regardless of file size:
    /// - 8 compressed blocks: ~512 KB
    /// - 8 decompressed blocks: ~520 KB
    /// - Total: ~1 MB (constant, even for 5TB files)
    ///
    /// # Performance (Rule 3)
    ///
    /// - Decompresses 8 blocks in parallel using rayon
    /// - Expected: 6.5× speedup (validated in Entry 029)
    /// - Maintains constant memory while achieving parallel speedup
    pub fn new(source: DataSource) -> Result<Self> {
        // Open source with smart I/O (Rules 4+6)
        let mut reader = source.open()?;

        // Peek at first two bytes to detect compression
        let first_bytes = {
            let peeked = reader.fill_buf()?;
            if peeked.len() >= 2 {
                [peeked[0], peeked[1]]
            } else if peeked.len() == 1 {
                [peeked[0], 0]
            } else {
                [0, 0]
            }
        };

        // Check for gzip magic bytes (31, 139)
        let is_gzipped = first_bytes[0] == 31 && first_bytes[1] == 139;

        if is_gzipped {
            // Wrap in bounded parallel bgzip reader (Rules 3+5 combined)
            // - Decompresses 8 blocks in parallel (Rule 3: 6.5× speedup)
            // - Bounded memory: ~1 MB (Rule 5: constant regardless of file size)
            let parallel_reader = BoundedParallelBgzipReader::new(reader);

            // Wrap in buffered reader for efficient line-by-line reading
            Ok(Self {
                inner: Box::new(BufReader::new(parallel_reader)),
            })
        } else {
            // Uncompressed data - pass through directly
            Ok(Self {
                inner: reader,
            })
        }
    }

    /// Get the inner buffered reader
    pub fn into_inner(self) -> Box<dyn BufRead + Send> {
        self.inner
    }
}

impl Read for CompressedReader {
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
        self.inner.read(buf)
    }
}

impl BufRead for CompressedReader {
    fn fill_buf(&mut self) -> io::Result<&[u8]> {
        self.inner.fill_buf()
    }

    fn consume(&mut self, amt: usize) {
        self.inner.consume(amt)
    }
}

// ============================================================================
// WRITING OPERATIONS (Phase 2: Writing Infrastructure)
// ============================================================================

/// Maximum uncompressed size for a single bgzip block
///
/// # BGZF Specification
///
/// The BGZF format requires that each block decompresses to at most 64 KB.
/// We use 60 KB to leave headroom for the compressed size to stay under 64 KB.
const BGZIP_BLOCK_SIZE: usize = 60 * 1024; // 60 KB

/// Parallel bgzip writer (Rule 3)
///
/// # Architecture
///
/// Mirrors BoundedParallelBgzipReader but for writing:
/// 1. Buffer input data until we have PARALLEL_BLOCK_COUNT blocks (8 × 60 KB = 480 KB)
/// 2. Compress 8 blocks in parallel using rayon (Rule 3: 6.5× speedup)
/// 3. Write compressed blocks sequentially with BGZF headers
/// 4. Maintain bounded memory (~1 MB total)
///
/// # BGZF Format
///
/// Each block is a complete gzip stream with special BGZF extra field:
/// - Extra subfield SI1='B' (66), SI2='C' (67)
/// - SLEN=2 (2-byte BSIZE field)
/// - BSIZE = total compressed block size - 1
///
/// # Evidence
///
/// Category 1 (Mirror): Inherits 6.5× speedup from parallel decompression (Entry 029).
/// Expected performance validated through symmetric architecture.
///
/// # Memory Guarantees (Rule 5)
///
/// - Uncompressed buffer: 8 × 60 KB = 480 KB
/// - Compressed buffer: 8 × ~60 KB = 480 KB
/// - Total: ~1 MB bounded, regardless of output size
pub(crate) struct BgzipWriter {
    /// Underlying writer for compressed output
    writer: Box<dyn Write>,

    /// Buffer for uncompressed blocks waiting to be compressed
    uncompressed_blocks: Vec<Vec<u8>>,

    /// Current uncompressed block being filled
    current_block: Vec<u8>,

    /// Total bytes written (for stats)
    bytes_written: usize,
}

impl BgzipWriter {
    /// Create a new bgzip writer
    fn new(writer: Box<dyn Write>) -> Self {
        Self {
            writer,
            uncompressed_blocks: Vec::with_capacity(PARALLEL_BLOCK_COUNT),
            current_block: Vec::with_capacity(BGZIP_BLOCK_SIZE),
            bytes_written: 0,
        }
    }

    /// Compress a single block to BGZF format
    ///
    /// # BGZF Block Structure
    ///
    /// Standard gzip header (10 bytes):
    /// - ID1=31, ID2=139 (gzip magic)
    /// - CM=8 (deflate)
    /// - FLG=4 (FEXTRA flag set)
    /// - MTIME=0 (no timestamp)
    /// - XFL=0 (default compression)
    /// - OS=255 (unknown)
    /// - XLEN=6 (extra field length)
    ///
    /// Extra field (8 bytes):
    /// - SI1=66 ('B'), SI2=67 ('C')
    /// - SLEN=2
    /// - BSIZE (little-endian u16): block_size - 1
    ///
    /// Compressed data + CRC32 + ISIZE
    fn compress_block(data: &[u8]) -> io::Result<Vec<u8>> {
        use flate2::Compression;

        // BGZF requires special header modifications
        // The flate2 GzEncoder creates a standard gzip header, but we need BGZF format
        // We'll need to manually construct the BGZF header

        // Create BGZF-formatted block from scratch
        let mut block = Vec::new();

        // Compress with deflate (raw, no gzip wrapper)
        use flate2::write::DeflateEncoder;
        let mut deflate = DeflateEncoder::new(Vec::new(), Compression::default());
        deflate.write_all(data)?;
        let deflated = deflate.finish()?;

        // Calculate CRC32 and ISIZE
        let crc = crc32fast::hash(data);
        let isize = data.len() as u32;

        // Build BGZF block
        // Header (10 bytes)
        block.push(31);  // ID1
        block.push(139); // ID2
        block.push(8);   // CM (deflate)
        block.push(4);   // FLG (FEXTRA)
        block.extend_from_slice(&[0, 0, 0, 0]); // MTIME
        block.push(0);   // XFL
        block.push(255); // OS (unknown)

        // Extra field
        block.extend_from_slice(&6u16.to_le_bytes()); // XLEN=6
        block.push(66);  // SI1='B'
        block.push(67);  // SI2='C'
        block.extend_from_slice(&2u16.to_le_bytes()); // SLEN=2

        // BSIZE placeholder (will update after we know total size)
        let bsize_pos = block.len();
        block.extend_from_slice(&0u16.to_le_bytes()); // BSIZE (placeholder)

        // Compressed data
        block.extend_from_slice(&deflated);

        // CRC32 and ISIZE
        block.extend_from_slice(&crc.to_le_bytes());
        block.extend_from_slice(&isize.to_le_bytes());

        // Update BSIZE field (total block size - 1)
        let total_size = block.len();
        let bsize = (total_size - 1) as u16;
        block[bsize_pos..bsize_pos + 2].copy_from_slice(&bsize.to_le_bytes());

        Ok(block)
    }

    /// Flush all pending blocks in parallel
    fn flush_blocks(&mut self) -> io::Result<()> {
        if self.uncompressed_blocks.is_empty() {
            return Ok(());
        }

        // Compress blocks in parallel (Rule 3: 6.5× speedup)
        let compressed_blocks: Vec<_> = self.uncompressed_blocks
            .par_iter()
            .map(|block| Self::compress_block(block))
            .collect::<io::Result<Vec<_>>>()?;

        // Write compressed blocks sequentially
        for block in compressed_blocks {
            self.writer.write_all(&block)?;
        }

        self.uncompressed_blocks.clear();
        Ok(())
    }

    /// Write data to the bgzip writer
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
        let mut remaining = buf;

        while !remaining.is_empty() {
            let space_in_block = BGZIP_BLOCK_SIZE - self.current_block.len();
            let to_copy = remaining.len().min(space_in_block);

            self.current_block.extend_from_slice(&remaining[..to_copy]);
            remaining = &remaining[to_copy..];

            // If current block is full, add to pending blocks
            if self.current_block.len() >= BGZIP_BLOCK_SIZE {
                let block = std::mem::replace(
                    &mut self.current_block,
                    Vec::with_capacity(BGZIP_BLOCK_SIZE),
                );
                self.uncompressed_blocks.push(block);

                // If we have enough blocks, compress them in parallel
                if self.uncompressed_blocks.len() >= PARALLEL_BLOCK_COUNT {
                    self.flush_blocks()?;
                }
            }
        }

        self.bytes_written += buf.len();
        Ok(buf.len())
    }

    /// Flush any buffered data
    fn flush(&mut self) -> io::Result<()> {
        self.writer.flush()
    }

    /// Finish writing and flush all remaining data
    fn finish(mut self) -> io::Result<()> {
        // Add current block to pending if it has data
        if !self.current_block.is_empty() {
            self.uncompressed_blocks.push(self.current_block);
            self.current_block = Vec::new();
        }

        // Flush all remaining blocks
        self.flush_blocks()?;

        // Write BGZF EOF marker (empty block)
        // This is a standard BGZF convention - exact 28 bytes
        let eof_marker = vec![
            31, 139, 8, 4, 0, 0, 0, 0, 0, 255, // Header (10 bytes)
            6, 0, 66, 67, 2, 0, 27, 0,          // Extra field with BSIZE=27 (8 bytes)
            3, 0,                                // Empty deflate block (2 bytes)
            0, 0, 0, 0,                          // CRC32 (4 bytes)
            0, 0, 0, 0,                          // ISIZE=0 (4 bytes)
        ];
        self.writer.write_all(&eof_marker)?;

        self.writer.flush()
    }
}

/// Compressed writer with automatic compression support
///
/// This is the write counterpart to `CompressedReader`, enabling biometal
/// to write data to various formats with the same streaming guarantees.
///
/// # Architecture
///
/// Mirrors `CompressedReader`:
/// - `CompressedReader::Plain` ↔ `CompressedWriter::Plain`
/// - `CompressedReader::Gzip` ↔ `CompressedWriter::Gzip` (Phase 1.2)
/// - Future: `CompressedWriter::Bgzip` for parallel compression
///
/// # Memory Guarantees (Rule 5)
///
/// - Write buffer: ~8 KB (BufWriter default)
/// - Gzip compression: ~64 KB additional (compression buffer)
/// - Total: ~72 KB constant regardless of output size
///
/// # Example
///
/// ```no_run
/// use biometal::io::{DataSink, compression::CompressedWriter};
/// use std::io::Write;
///
/// # fn main() -> std::io::Result<()> {
/// let sink = DataSink::from_path("output.fq.gz");
/// let mut writer = CompressedWriter::new(sink)?;
///
/// writer.write_all(b"Hello, biometal!\n")?;
/// writer.finish()?;  // Important: finalizes compression
/// # Ok(())
/// # }
/// ```
#[allow(private_interfaces)]
pub enum CompressedWriter {
    /// Uncompressed writer with buffering
    Plain(Option<BufWriter<Box<dyn Write>>>),

    /// Gzip compressed writer (single-threaded)
    ///
    /// Uses flate2 with default compression level (6).
    /// Compatible with all gzip tools.
    Gzip(Option<GzEncoder<BufWriter<Box<dyn Write>>>>),

    /// Bgzip compressed writer (parallel, Rule 3)
    ///
    /// Uses rayon for parallel block compression (6.5× speedup).
    /// BGZF-compatible format for bioinformatics tools.
    Bgzip(Option<BgzipWriter>),
}

impl CompressedWriter {
    /// Create a new writer from a data sink
    ///
    /// Automatically detects compression format from file extension:
    /// - `.gz` → gzip compression (single-threaded, compatible)
    /// - `.bgz` → bgzip compression (Phase 3.3, parallel)
    /// - other → uncompressed
    ///
    /// # Evidence (Category 1: Mirror)
    ///
    /// Compression mirrors decompression (Rule 3, Entry 029).
    /// Expected speedup inherited from parallel decompression (6.5×).
    ///
    /// # Example
    ///
    /// ```no_run
    /// use biometal::io::{DataSink, compression::CompressedWriter};
    ///
    /// # fn main() -> std::io::Result<()> {
    /// // Gzip compression (auto-detected from .gz extension)
    /// let sink = DataSink::from_path("output.fq.gz");
    /// let writer = CompressedWriter::new(sink)?;
    /// # Ok(())
    /// # }
    /// ```
    pub fn new(sink: DataSink) -> io::Result<Self> {
        match sink {
            DataSink::Local(path) => {
                let file = File::create(&path)?;

                // Detect compression from file extension
                let ext = path.extension()
                    .and_then(|s| s.to_str())
                    .unwrap_or("");

                match ext {
                    "gz" => {
                        // Gzip compression (single-threaded, compatible)
                        Self::new_gzip(Box::new(file))
                    }
                    "bgz" => {
                        // Bgzip compression (parallel, Rule 3: 6.5× speedup)
                        Self::new_bgzip(Box::new(file))
                    }
                    _ => {
                        // Uncompressed
                        Self::new_plain(Box::new(file))
                    }
                }
            }
            DataSink::Stdout => {
                // Stdout is always uncompressed (no extension to detect)
                Self::new_plain(Box::new(io::stdout()))
            }
        }
    }

    /// Create a plain (uncompressed) writer
    pub fn new_plain(writer: Box<dyn Write>) -> io::Result<Self> {
        Ok(Self::Plain(Some(BufWriter::new(writer))))
    }

    /// Create a gzip compressed writer
    ///
    /// Uses default compression level (6), which provides a good balance
    /// between compression ratio and speed.
    ///
    /// # Evidence (Category 1: Mirror)
    ///
    /// Gzip compression mirrors decompression. No new validation needed
    /// as this is the inverse of proven decompression operation.
    ///
    /// # Example
    ///
    /// ```no_run
    /// use biometal::io::compression::CompressedWriter;
    /// use std::fs::File;
    ///
    /// # fn main() -> std::io::Result<()> {
    /// let file = File::create("output.fq.gz")?;
    /// let writer = CompressedWriter::new_gzip(Box::new(file))?;
    /// # Ok(())
    /// # }
    /// ```
    pub fn new_gzip(writer: Box<dyn Write>) -> io::Result<Self> {
        let encoder = GzEncoder::new(
            BufWriter::new(writer),
            Compression::default(), // Level 6
        );
        Ok(Self::Gzip(Some(encoder)))
    }

    /// Create a bgzip compressed writer (parallel)
    ///
    /// Uses parallel block compression for 6.5× speedup (Rule 3).
    /// Creates BGZF-compatible output for bioinformatics tools.
    ///
    /// # Evidence (Category 1: Mirror)
    ///
    /// Parallel bgzip compression mirrors decompression (Entry 029).
    /// Expected 6.5× speedup inherited from validated parallel decompression.
    ///
    /// # Memory Guarantees (Rule 5)
    ///
    /// - Uncompressed buffer: 8 blocks × 60 KB = 480 KB
    /// - Compressed buffer: 8 blocks × ~60 KB = 480 KB
    /// - Total: ~1 MB bounded
    ///
    /// # Example
    ///
    /// ```no_run
    /// use biometal::io::compression::CompressedWriter;
    /// use std::fs::File;
    ///
    /// # fn main() -> std::io::Result<()> {
    /// let file = File::create("output.fq.bgz")?;
    /// let mut writer = CompressedWriter::new_bgzip(Box::new(file))?;
    /// # Ok(())
    /// # }
    /// ```
    pub fn new_bgzip(writer: Box<dyn Write>) -> io::Result<Self> {
        Ok(Self::Bgzip(Some(BgzipWriter::new(writer))))
    }

    /// Flush all buffered data to the underlying writer
    ///
    /// For compressed formats, this flushes the compression buffer
    /// but does not finalize the stream. Use `finish()` to properly
    /// close compressed files.
    pub fn flush(&mut self) -> io::Result<()> {
        match self {
            Self::Plain(Some(w)) => w.flush(),
            Self::Gzip(Some(w)) => w.flush(),
            Self::Bgzip(Some(w)) => w.flush(),
            _ => Ok(()), // Already finished
        }
    }

    /// Finish writing and consume the writer
    ///
    /// This ensures all buffered data is flushed and, for compressed formats,
    /// writes proper EOF markers.
    ///
    /// # Important
    ///
    /// You should always call `finish()` explicitly rather than relying on `Drop`,
    /// as `finish()` can return errors that need to be handled.
    ///
    /// # Example
    ///
    /// ```no_run
    /// use biometal::io::{DataSink, compression::CompressedWriter};
    /// use std::io::Write;
    ///
    /// # fn main() -> std::io::Result<()> {
    /// let sink = DataSink::from_path("output.fq.gz");
    /// let mut writer = CompressedWriter::new(sink)?;
    /// writer.write_all(b"data")?;
    /// writer.finish()?;  // Finalizes gzip stream
    /// # Ok(())
    /// # }
    /// ```
    pub fn finish(mut self) -> io::Result<()> {
        match &mut self {
            Self::Plain(w) => {
                if let Some(mut writer) = w.take() {
                    writer.flush()
                } else {
                    Ok(()) // Already finished
                }
            }
            Self::Gzip(w) => {
                if let Some(encoder) = w.take() {
                    // finish() consumes the encoder and properly finalizes the gzip stream
                    let _ = encoder.finish()?;
                    Ok(())
                } else {
                    Ok(()) // Already finished
                }
            }
            Self::Bgzip(w) => {
                if let Some(writer) = w.take() {
                    // finish() consumes the writer and writes BGZF EOF marker
                    writer.finish()
                } else {
                    Ok(()) // Already finished
                }
            }
        }
    }
}

impl Write for CompressedWriter {
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
        match self {
            Self::Plain(Some(w)) => w.write(buf),
            Self::Gzip(Some(w)) => w.write(buf),
            Self::Bgzip(Some(w)) => w.write(buf),
            _ => Err(io::Error::new(
                io::ErrorKind::Other,
                "Cannot write to finished writer",
            )),
        }
    }

    fn flush(&mut self) -> io::Result<()> {
        CompressedWriter::flush(self)
    }
}

impl Drop for CompressedWriter {
    fn drop(&mut self) {
        // Best-effort flush on drop
        // Users should call finish() explicitly to handle errors
        let _ = self.flush();
    }
}

// ============================================================================
// TESTS
// ============================================================================

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

    // ========================================================================
    // Writing Tests (Phase 1.1)
    // ========================================================================

    #[test]
    fn test_compressed_writer_plain_basic() {
        use std::fs;
        use tempfile::NamedTempFile;

        let temp_file = NamedTempFile::new().unwrap();
        let path = temp_file.path().to_path_buf();

        {
            let sink = DataSink::from_path(&path);
            let mut writer = CompressedWriter::new(sink).unwrap();

            writer.write_all(b"Hello, biometal!\n").unwrap();
            writer.write_all(b"Line 2\n").unwrap();
            writer.finish().unwrap();
        }

        // Read back and verify
        let content = fs::read_to_string(&path).unwrap();
        assert_eq!(content, "Hello, biometal!\nLine 2\n");
    }

    #[test]
    fn test_compressed_writer_stdout() {
        // Just verify it doesn't panic
        let sink = DataSink::stdout();
        let writer = CompressedWriter::new(sink).unwrap();
        // Don't actually write to stdout in tests
        drop(writer);
    }

    #[test]
    fn test_compressed_writer_large_data() {
        use std::fs;
        use tempfile::NamedTempFile;

        let temp_file = NamedTempFile::new().unwrap();
        let path = temp_file.path().to_path_buf();

        {
            let sink = DataSink::from_path(&path);
            let mut writer = CompressedWriter::new(sink).unwrap();

            // Write 10K lines
            for i in 0..10_000 {
                writeln!(writer, "Line {}", i).unwrap();
            }
            writer.finish().unwrap();
        }

        // Read back and verify line count
        let content = fs::read_to_string(&path).unwrap();
        let line_count = content.lines().count();
        assert_eq!(line_count, 10_000);
    }

    #[test]
    fn test_compressed_writer_flush() {
        use tempfile::NamedTempFile;

        let temp_file = NamedTempFile::new().unwrap();
        let path = temp_file.path().to_path_buf();

        let sink = DataSink::from_path(&path);
        let mut writer = CompressedWriter::new(sink).unwrap();

        writer.write_all(b"Test\n").unwrap();
        writer.flush().unwrap(); // Should not panic
        writer.write_all(b"More\n").unwrap();
        writer.finish().unwrap();
    }

    // ========================================================================
    // Gzip Compression Tests (Phase 3.2)
    // ========================================================================

    #[test]
    fn test_gzip_round_trip_basic() {
        use std::fs;
        use tempfile::NamedTempFile;

        // Create a .gz file
        let temp_file = NamedTempFile::with_suffix(".gz").unwrap();
        let path = temp_file.path().to_path_buf();

        let test_data = b"Hello, gzip compression!\nLine 2\nLine 3\n";

        // Write with gzip compression
        {
            let sink = DataSink::from_path(&path);
            let mut writer = CompressedWriter::new(sink).unwrap();
            writer.write_all(test_data).unwrap();
            writer.finish().unwrap();
        }

        // Verify file is actually compressed (should be smaller or similar for small data)
        let compressed_size = fs::metadata(&path).unwrap().len();
        println!("Original: {} bytes, Compressed: {} bytes", test_data.len(), compressed_size);

        // Read back with CompressedReader
        {
            let source = DataSource::from_path(&path);
            let mut reader = CompressedReader::new(source).unwrap();
            let mut decompressed = Vec::new();
            reader.read_to_end(&mut decompressed).unwrap();

            assert_eq!(decompressed, test_data);
        }
    }

    #[test]
    fn test_gzip_round_trip_large() {
        use std::fs;
        use tempfile::NamedTempFile;

        // Create a .gz file
        let temp_file = NamedTempFile::with_suffix(".gz").unwrap();
        let path = temp_file.path().to_path_buf();

        // Generate large test data (100 KB of repeated pattern)
        let pattern = b"ATGCATGCATGCATGC\n";
        let mut test_data = Vec::new();
        for _ in 0..6000 {
            test_data.extend_from_slice(pattern);
        }
        let original_size = test_data.len();

        // Write with gzip compression
        {
            let sink = DataSink::from_path(&path);
            let mut writer = CompressedWriter::new(sink).unwrap();
            writer.write_all(&test_data).unwrap();
            writer.finish().unwrap();
        }

        // Verify compression achieved size reduction
        let compressed_size = fs::metadata(&path).unwrap().len();
        println!("Original: {} bytes, Compressed: {} bytes ({:.1}% reduction)",
                 original_size, compressed_size,
                 (1.0 - (compressed_size as f64 / original_size as f64)) * 100.0);

        // Repeated pattern should compress very well
        assert!(compressed_size < (original_size as u64) / 10,
                "Expected >90% compression for repeated pattern");

        // Read back and verify
        {
            let source = DataSource::from_path(&path);
            let mut reader = CompressedReader::new(source).unwrap();
            let mut decompressed = Vec::new();
            reader.read_to_end(&mut decompressed).unwrap();

            assert_eq!(decompressed.len(), test_data.len());
            assert_eq!(decompressed, test_data);
        }
    }

    #[test]
    fn test_gzip_explicit_constructor() {
        use tempfile::NamedTempFile;

        let temp_file = NamedTempFile::with_suffix(".custom").unwrap();
        let path = temp_file.path().to_path_buf();

        let test_data = b"Testing explicit gzip constructor\n";

        // Use explicit new_gzip() instead of auto-detection
        {
            let file = File::create(&path).unwrap();
            let mut writer = CompressedWriter::new_gzip(Box::new(file)).unwrap();
            writer.write_all(test_data).unwrap();
            writer.finish().unwrap();
        }

        // Manually verify it's gzip format by checking magic bytes
        {
            let compressed = std::fs::read(&path).unwrap();
            assert!(compressed.len() >= 2);
            assert_eq!(compressed[0], 31); // gzip magic byte 1
            assert_eq!(compressed[1], 139); // gzip magic byte 2
        }

        // Verify decompression works
        {
            use flate2::read::GzDecoder;
            let file = File::open(&path).unwrap();
            let mut decoder = GzDecoder::new(file);
            let mut decompressed = Vec::new();
            decoder.read_to_end(&mut decompressed).unwrap();
            assert_eq!(decompressed, test_data);
        }
    }

    #[test]
    fn test_gzip_auto_detection_from_extension() {
        use tempfile::TempDir;

        let temp_dir = TempDir::new().unwrap();
        let test_data = b"Auto-detection test\n";

        // .gz extension should trigger gzip
        {
            let path = temp_dir.path().join("test.fq.gz");
            let sink = DataSink::from_path(&path);
            let mut writer = CompressedWriter::new(sink).unwrap();
            writer.write_all(test_data).unwrap();
            writer.finish().unwrap();

            // Verify it's compressed
            let compressed = std::fs::read(&path).unwrap();
            assert_eq!(compressed[0], 31);
            assert_eq!(compressed[1], 139);
        }

        // .bgz extension should also trigger gzip (until Phase 3.3)
        {
            let path = temp_dir.path().join("test.fq.bgz");
            let sink = DataSink::from_path(&path);
            let mut writer = CompressedWriter::new(sink).unwrap();
            writer.write_all(test_data).unwrap();
            writer.finish().unwrap();

            // Verify it's compressed
            let compressed = std::fs::read(&path).unwrap();
            assert_eq!(compressed[0], 31);
            assert_eq!(compressed[1], 139);
        }

        // No extension or .fq should be uncompressed
        {
            let path = temp_dir.path().join("test.fq");
            let sink = DataSink::from_path(&path);
            let mut writer = CompressedWriter::new(sink).unwrap();
            writer.write_all(test_data).unwrap();
            writer.finish().unwrap();

            // Verify it's NOT compressed (should be plain text)
            let content = std::fs::read(&path).unwrap();
            assert_eq!(content, test_data);
        }
    }

    // ========================================================================
    // Bgzip Compression Tests (Phase 3.3)
    // ========================================================================

    #[test]
    fn test_bgzip_round_trip_basic() {
        use tempfile::NamedTempFile;

        // Create a .bgz file
        let temp_file = NamedTempFile::with_suffix(".bgz").unwrap();
        let path = temp_file.path().to_path_buf();

        let test_data = b"Hello, bgzip parallel compression!\nLine 2\nLine 3\n";

        // Write with bgzip compression
        {
            let sink = DataSink::from_path(&path);
            let mut writer = CompressedWriter::new(sink).unwrap();
            writer.write_all(test_data).unwrap();
            writer.finish().unwrap();
        }

        // Read back with CompressedReader
        {
            let source = DataSource::from_path(&path);
            let mut reader = CompressedReader::new(source).unwrap();
            let mut decompressed = Vec::new();
            reader.read_to_end(&mut decompressed).unwrap();

            assert_eq!(decompressed, test_data);
        }
    }

    #[test]
    fn test_bgzip_round_trip_large() {
        use std::fs;
        use tempfile::NamedTempFile;

        // Create a .bgz file
        let temp_file = NamedTempFile::with_suffix(".bgz").unwrap();
        let path = temp_file.path().to_path_buf();

        // Generate large test data (500 KB to trigger multiple parallel blocks)
        let pattern = b"ATGCATGCATGCATGC\n";
        let mut test_data = Vec::new();
        for _ in 0..30_000 {
            test_data.extend_from_slice(pattern);
        }
        let original_size = test_data.len();

        println!("Test data size: {} bytes", original_size);
        assert!(original_size > BGZIP_BLOCK_SIZE * PARALLEL_BLOCK_COUNT,
                "Test data should span multiple parallel blocks");

        // Write with bgzip compression
        {
            let sink = DataSink::from_path(&path);
            let mut writer = CompressedWriter::new(sink).unwrap();
            writer.write_all(&test_data).unwrap();
            writer.finish().unwrap();
        }

        // Verify compression achieved size reduction
        let compressed_size = fs::metadata(&path).unwrap().len();
        println!("Original: {} bytes, Compressed: {} bytes ({:.1}% reduction)",
                 original_size, compressed_size,
                 (1.0 - (compressed_size as f64 / original_size as f64)) * 100.0);

        // Repeated pattern should compress very well
        assert!(compressed_size < (original_size as u64) / 10,
                "Expected >90% compression for repeated pattern");

        // Read back and verify
        {
            let source = DataSource::from_path(&path);
            let mut reader = CompressedReader::new(source).unwrap();
            let mut decompressed = Vec::new();
            reader.read_to_end(&mut decompressed).unwrap();

            assert_eq!(decompressed.len(), test_data.len());
            assert_eq!(decompressed, test_data);
        }
    }

    #[test]
    fn test_bgzip_block_format() {
        use tempfile::NamedTempFile;

        let temp_file = NamedTempFile::with_suffix(".bgz").unwrap();
        let path = temp_file.path().to_path_buf();

        let test_data = b"BGZF format validation test\n";

        // Write with bgzip
        {
            let sink = DataSink::from_path(&path);
            let mut writer = CompressedWriter::new(sink).unwrap();
            writer.write_all(test_data).unwrap();
            writer.finish().unwrap();
        }

        // Verify BGZF format
        {
            let compressed = std::fs::read(&path).unwrap();

            // Should start with gzip magic
            assert_eq!(compressed[0], 31);
            assert_eq!(compressed[1], 139);

            // Should have FLG with FEXTRA bit set
            assert_eq!(compressed[3] & 0x04, 0x04);

            // Check for BGZF extra field (SI1='B', SI2='C')
            // Extra field starts at byte 12
            let xlen = u16::from_le_bytes([compressed[10], compressed[11]]) as usize;
            assert_eq!(xlen, 6); // BGZF uses XLEN=6

            // SI1='B' (66), SI2='C' (67)
            assert_eq!(compressed[12], 66);
            assert_eq!(compressed[13], 67);

            // SLEN=2
            let slen = u16::from_le_bytes([compressed[14], compressed[15]]);
            assert_eq!(slen, 2);

            println!("BGZF format validated: magic bytes, FEXTRA flag, and BC subfield present");
        }
    }

    #[test]
    fn test_bgzip_explicit_constructor() {
        use tempfile::NamedTempFile;

        let temp_file = NamedTempFile::with_suffix(".custom").unwrap();
        let path = temp_file.path().to_path_buf();

        let test_data = b"Testing explicit bgzip constructor\n";

        // Use explicit new_bgzip() instead of auto-detection
        {
            let file = File::create(&path).unwrap();
            let mut writer = CompressedWriter::new_bgzip(Box::new(file)).unwrap();
            writer.write_all(test_data).unwrap();
            writer.finish().unwrap();
        }

        // Verify it's BGZF format
        {
            let compressed = std::fs::read(&path).unwrap();
            assert_eq!(compressed[0], 31);  // gzip magic
            assert_eq!(compressed[1], 139);
            assert_eq!(compressed[12], 66); // SI1='B'
            assert_eq!(compressed[13], 67); // SI2='C'
        }

        // Verify decompression works
        {
            let source = DataSource::from_path(&path);
            let mut reader = CompressedReader::new(source).unwrap();
            let mut decompressed = Vec::new();
            reader.read_to_end(&mut decompressed).unwrap();
            assert_eq!(decompressed, test_data);
        }
    }

    #[test]
    fn test_bgzip_parallel_blocks() {
        use tempfile::NamedTempFile;

        let temp_file = NamedTempFile::with_suffix(".bgz").unwrap();
        let path = temp_file.path().to_path_buf();

        // Create data spanning exactly PARALLEL_BLOCK_COUNT blocks
        let block_data = vec![b'X'; BGZIP_BLOCK_SIZE];
        let mut test_data = Vec::new();
        for _ in 0..PARALLEL_BLOCK_COUNT {
            test_data.extend_from_slice(&block_data);
        }

        // Write with bgzip
        {
            let sink = DataSink::from_path(&path);
            let mut writer = CompressedWriter::new(sink).unwrap();
            writer.write_all(&test_data).unwrap();
            writer.finish().unwrap();
        }

        // Verify we can read it back
        {
            let source = DataSource::from_path(&path);
            let mut reader = CompressedReader::new(source).unwrap();
            let mut decompressed = Vec::new();
            reader.read_to_end(&mut decompressed).unwrap();

            assert_eq!(decompressed.len(), test_data.len());
            assert_eq!(decompressed, test_data);
        }
    }

    // ========================================================================
    // Original Tests
    // ========================================================================

    #[test]
    fn test_mmap_threshold_constant() {
        // Verify evidence-based threshold (Entry 032)
        assert_eq!(MMAP_THRESHOLD, 50 * 1024 * 1024);
    }

    #[test]
    fn test_datasource_local_creation() {
        let source = DataSource::from_path("/tmp/test.fq");
        match source {
            DataSource::Local(path) => {
                assert_eq!(path, PathBuf::from("/tmp/test.fq"));
            }
            #[allow(unreachable_patterns)]
            _ => panic!("Expected Local variant"),
        }
    }

    // Network streaming tests moved to io/network.rs module

    #[test]
    fn test_bgzip_block_parsing() {
        // Test that we correctly parse multiple bgzip blocks from a real file
        // Using the tiny dataset which should have multiple blocks
        let home = std::env::var("HOME").unwrap_or_else(|_| "/Users/scotthandley".to_string());
        let path = PathBuf::from(&home)
            .join("Code/apple-silicon-bio-bench/datasets/tiny_100_150bp.fq.gz");

        if !path.exists() {
            println!("Skipping test: file not found at {:?}", path);
            return;
        }

        // Read the compressed file
        let compressed_data = std::fs::read(&path).expect("Failed to read test file");

        // Parse blocks
        let blocks = parse_bgzip_blocks(&compressed_data).expect("Failed to parse bgzip blocks");

        // Verify we found multiple blocks (bgzip files typically have many blocks)
        println!("Found {} bgzip blocks in {:?}", blocks.len(), path);
        println!(
            "Block sizes: {:?}",
            blocks.iter().map(|b| b.size).collect::<Vec<_>>()
        );

        // Bgzip files should have multiple blocks for parallel decompression
        // Even small files typically have 1-10 blocks
        assert!(
            !blocks.is_empty(),
            "Expected at least 1 block, found {}",
            blocks.len()
        );

        // Verify total size matches original file
        let total_size: usize = blocks.iter().map(|b| b.size).sum();
        assert_eq!(
            total_size,
            compressed_data.len(),
            "Block sizes don't sum to file size"
        );

        // Verify each block has valid gzip header
        for (i, block) in blocks.iter().enumerate() {
            assert!(
                block.data.len() >= 18,
                "Block {} too small: {} bytes",
                i,
                block.data.len()
            );
            assert_eq!(
                block.data[0], 31,
                "Block {} has invalid magic byte 0: {}",
                i, block.data[0]
            );
            assert_eq!(
                block.data[1], 139,
                "Block {} has invalid magic byte 1: {}",
                i, block.data[1]
            );
        }
    }

    #[test]
    fn test_parallel_sequential_equivalence() {
        // Core correctness guarantee: parallel decompression must produce
        // byte-for-byte identical output to sequential decompression

        let home = std::env::var("HOME").unwrap_or_else(|_| "/Users/scotthandley".to_string());

        // Test with multiple dataset sizes to ensure correctness across all scales
        let test_files = vec![
            "Code/apple-silicon-bio-bench/datasets/tiny_100_150bp.fq.gz",
            "Code/apple-silicon-bio-bench/datasets/small_1k_150bp.fq.gz",
            "Code/apple-silicon-bio-bench/datasets/medium_10k_150bp.fq.gz",
        ];

        for rel_path in test_files {
            let path = PathBuf::from(&home).join(rel_path);

            if !path.exists() {
                println!("Skipping test: file not found at {:?}", path);
                continue;
            }

            println!("Testing parallel vs sequential for: {:?}", path);

            // Decompress with bounded parallel (our implementation)
            let source = DataSource::from_path(&path);
            let parallel_reader = BoundedParallelBgzipReader::new(
                source.open().expect("Failed to open source")
            );
            let mut parallel_output = Vec::new();
            BufReader::new(parallel_reader)
                .read_to_end(&mut parallel_output)
                .expect("Failed to read parallel output");

            // Decompress with sequential (standard GzDecoder)
            let file = File::open(&path).expect("Failed to open file");
            let mut sequential_reader = GzDecoder::new(file);
            let mut sequential_output = Vec::new();
            sequential_reader
                .read_to_end(&mut sequential_output)
                .expect("Failed to read sequential output");

            // Must be byte-for-byte identical
            assert_eq!(
                parallel_output.len(),
                sequential_output.len(),
                "Output lengths differ for {:?}",
                path
            );

            assert_eq!(
                parallel_output,
                sequential_output,
                "Parallel and sequential outputs differ for {:?}",
                path
            );

            println!("  ✓ Outputs match ({} bytes)", parallel_output.len());
        }
    }
}
