use crate::compression_engine::{CompressionType, get_compression_engine};
use crate::services::error::CoreError;
use anyhow::anyhow;
use std::io::{Read, Write};
use std::path::PathBuf;
use std::str::FromStr;

pub struct CompressionService;

/// Service for handling compression and decompression of item content.
///
/// Provides methods to read compressed item files either fully into memory
/// or as streaming readers. Supports various compression types via engines.
/// This service abstracts the underlying compression engines for consistent access.
///
/// # Examples
///
/// ```ignore
/// let service = CompressionService::new();
/// let content = service.get_item_content(path, "gzip")?;
/// ```
/// Provides methods to read compressed item files either fully into memory
/// or as streaming readers. Supports various compression types via engines.
///
/// # Examples
///
/// ```ignore
/// let service = CompressionService::new();
/// let content = service.get_item_content(path, "gzip")?;
/// ```
impl CompressionService {
    /// Creates a new CompressionService instance.
    ///
    /// This is a simple constructor; no initialization is required beyond the static methods.
    ///
    /// # Returns
    ///
    /// * `CompressionService` - A new instance of the service.
    ///
    /// # Examples
    ///
    /// ```
    /// # use keep::services::CompressionService;
    /// let service = CompressionService::new();
    /// ```
    pub fn new() -> Self {
        Self
    }

    /// Reads and decompresses the full content of an item file into memory.
    ///
    /// Loads the entire decompressed content as a byte vector. Suitable for small to medium files.
    ///
    /// # Arguments
    ///
    /// * `item_path` - Path to the compressed item file on disk.
    /// * `compression` - Compression type as string (e.g., "gzip", "lz4"); case-insensitive.
    ///
    /// # Returns
    ///
    /// * `Ok(Vec<u8>)` - The full decompressed content bytes.
    /// * `Err(CoreError)` - On failure (see errors).
    ///
    /// # Errors
    ///
    /// * `CoreError::Compression(String)` - If the compression type string is invalid.
    /// * `CoreError::Other(anyhow::Error)` - If the file cannot be opened, the engine fails, or reading encounters an I/O error.
    ///
    /// # Examples
    ///
    /// ```ignore
    /// let content = service.get_item_content(item_path, "lz4")?;
    /// assert_eq!(content.len(), expected_size);
    /// ```
    pub fn get_item_content(
        &self,
        item_path: PathBuf,
        compression: &str,
    ) -> Result<Vec<u8>, CoreError> {
        let compression_type = CompressionType::from_str(compression)
            .map_err(|e| CoreError::Compression(e.to_string()))?;
        let engine = get_compression_engine(compression_type)
            .map_err(|e| CoreError::Other(anyhow!(e.to_string())))?;

        let mut reader = engine.open(item_path.clone()).map_err(|e| {
            CoreError::Other(anyhow!("Failed to open item file {:?}: {}", item_path, e))
        })?;
        let mut content = Vec::new();
        reader.read_to_end(&mut content)?;
        Ok(content)
    }

    /// Opens a streaming reader for decompressing item content.
    ///
    /// Returns a boxed reader that implements `Read + Send` for streaming decompression.
    /// This enables true streaming without loading the entire file into memory.
    ///
    /// # Arguments
    ///
    /// * `item_path` - Path to the compressed item file on disk.
    /// * `compression` - Compression type as string (e.g., "gzip", "lz4"); case-insensitive.
    ///
    /// # Returns
    ///
    /// * `Ok(Box<dyn Read + Send>)` - A boxed reader that can be used for streaming decompressed data.
    /// * `Err(CoreError)` - On failure (see errors).
    ///
    /// # Errors
    ///
    /// * `CoreError::Compression(String)` - If the compression type string is invalid.
    /// * `CoreError::Other(anyhow::Error)` - If the file cannot be opened, the engine fails, or reading encounters an I/O error.
    ///
    /// # Examples
    ///
    /// ```ignore
    /// let mut reader = service.stream_item_content(item_path, "gzip")?;
    /// let mut buf = [0; 1024];
    /// let n = reader.read(&mut buf)?;
    /// ```
    pub fn stream_item_content(
        &self,
        item_path: PathBuf,
        compression: &str,
    ) -> Result<Box<dyn Read + Send>, CoreError> {
        let compression_type = CompressionType::from_str(compression)
            .map_err(|e| CoreError::Compression(e.to_string()))?;
        let engine = get_compression_engine(compression_type)
            .map_err(|e| CoreError::Other(anyhow!(e.to_string())))?;

        let reader = engine.open(item_path.clone()).map_err(|e| {
            CoreError::Other(anyhow!("Failed to open item file {:?}: {}", item_path, e))
        })?;
        Ok(reader)
    }

    /// Creates a decompressing reader wrapping the given reader.
    ///
    /// Returns a boxed reader that decompresses on the fly based on the compression type.
    /// Useful for decompressing network streams or other non-file sources.
    ///
    /// # Arguments
    ///
    /// * `reader` - The underlying compressed reader.
    /// * `compression` - Compression type string (e.g., "gzip", "lz4").
    ///
    /// # Returns
    ///
    /// A boxed decompressing reader. Unknown/none types pass through unchanged.
    pub fn decompressing_reader(
        reader: Box<dyn Read>,
        compression: &CompressionType,
    ) -> Box<dyn Read> {
        match compression {
            CompressionType::GZip => {
                use flate2::read::GzDecoder;
                Box::new(GzDecoder::new(reader))
            }
            CompressionType::LZ4 => {
                use lz4_flex::frame::FrameDecoder;
                Box::new(FrameDecoder::new(reader))
            }
            #[cfg(feature = "zstd")]
            CompressionType::ZStd => {
                use zstd::stream::read::Decoder;
                Box::new(Decoder::new(reader).expect("Failed to create zstd decoder"))
            }
            _ => reader,
        }
    }

    /// Creates a compressing writer wrapping the given writer.
    ///
    /// Returns a boxed writer that compresses on the fly based on the compression type.
    /// Useful for compressing data to network streams or pipes.
    ///
    /// # Arguments
    ///
    /// * `writer` - The underlying destination writer.
    /// * `compression` - Compression type string (e.g., "gzip", "lz4").
    ///
    /// # Returns
    ///
    /// A boxed compressing writer. Unknown/none types pass through unchanged.
    pub fn compressing_writer(
        writer: Box<dyn Write>,
        compression: &CompressionType,
    ) -> Box<dyn Write> {
        match compression {
            CompressionType::GZip => {
                use flate2::Compression;
                use flate2::write::GzEncoder;
                Box::new(GzEncoder::new(writer, Compression::default()))
            }
            CompressionType::LZ4 => Box::new(lz4_flex::frame::FrameEncoder::new(writer)),
            #[cfg(feature = "zstd")]
            CompressionType::ZStd => {
                use zstd::stream::write::Encoder;
                Box::new(
                    Encoder::new(writer, 3)
                        .expect("Failed to create zstd encoder")
                        .auto_finish(),
                )
            }
            _ => writer,
        }
    }
}

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