// Core Format Processors - ZIP, TAR, GZIP, TAR.GZ Full Implementation

use crate::archive::{
    ArchiveProcessor, ArchiveFormat, ArchiveMetadata, ArchiveEntry, ExtractResult,
    processor::{validate_extraction_path, check_decompression_ratio, check_file_size_limit},
};
use crate::archive::config::ArchiveConfig;
use anyhow::{Result, anyhow};
use std::path::{Path, PathBuf};
use std::fs::{File, create_dir_all};
use std::io::{Read, Write, BufReader, BufWriter};
use std::time::SystemTime;
use log::{info, warn, debug};

// ==================== ZIP Processor ====================

/// ZIP Processor - Full Implementation using zip crate
pub struct ZipProcessor {
    archive: Option<zip::ZipArchive<std::fs::File>>,
    path: PathBuf,
    config: ArchiveConfig,
}

impl ZipProcessor {
    pub fn new() -> Self {
        Self {
            archive: None,
            path: PathBuf::new(),
            config: ArchiveConfig::default(),
        }
    }
    
    pub fn with_config(config: ArchiveConfig) -> Self {
        Self {
            archive: None,
            path: PathBuf::new(),
            config,
        }
    }
}

impl ArchiveProcessor for ZipProcessor {
    fn format(&self) -> ArchiveFormat {
        ArchiveFormat::Zip
    }
    
    fn open(&mut self, path: &Path) -> Result<ArchiveMetadata> {
        info!("Opening ZIP archive: {}", path.display());
        
        let file = File::open(path)?;
        let archive = zip::ZipArchive::new(file)?;
        
        self.archive = Some(archive);
        self.path = path.to_path_buf();
        
        // Extract metadata
        let archive_ref = self.archive.as_ref().unwrap();
        let total_files = archive_ref.len() as u64;
        
        let mut total_size = 0u64;
        let mut compressed_size = 0u64;
        
        for i in 0..archive_ref.len() {
            let file = archive_ref.by_index(i)?;
            total_size += file.size();
            compressed_size += file.compressed_size();
        }
        
        let compression_ratio = if compressed_size > 0 {
            total_size as f64 / compressed_size as f64
        } else {
            0.0
        };
        
        // Check for Zip Bomb
        if compression_ratio > self.config.max_decompression_ratio as f64 {
            warn!("Potential Zip Bomb detected: ratio {:.1}:1", compression_ratio);
            return Err(anyhow!("Zip Bomb detected: compression ratio {:.1} exceeds limit {}", 
                compression_ratio, self.config.max_decompression_ratio));
        }
        
        Ok(ArchiveMetadata {
            format: ArchiveFormat::Zip,
            total_files,
            total_size,
            compressed_size,
            compression_ratio,
            is_encrypted: false,  // TODO: Check encryption
            is_multi_volume: false,
            created_time: Some(SystemTime::now()),
        })
    }
    
    fn list_entries(&self) -> Result<Vec<ArchiveEntry>> {
        let archive = self.archive.as_ref()
            .ok_or_else(|| anyhow!("Archive not opened"))?;
        
        let mut entries = Vec::new();
        
        for i in 0..archive.len() {
            let file = archive.by_index(i)?;
            
            let entry = ArchiveEntry {
                path: PathBuf::from(file.name()),
                size: file.size(),
                compressed_size: file.compressed_size(),
                is_dir: file.name().ends_with('/'),
                is_file: !file.name().ends_with('/'),
                is_encrypted: false,
                modified: SystemTime::UNIX_EPOCH,  // TODO: Get actual time
                permissions: Some(0o644),
                checksum: None,
            };
            
            entries.push(entry);
        }
        
        info!("Listed {} entries in ZIP archive", entries.len());
        Ok(entries)
    }
    
    fn extract_file(&self, entry_path: &Path, output: &mut Vec<u8>) -> Result<u64> {
        let archive = self.archive.as_ref()
            .ok_or_else(|| anyhow!("Archive not opened"))?;
        
        let entry_name = entry_path.to_str()
            .ok_or_else(|| anyhow!("Invalid entry path"))?;
        
        let mut file = archive.by_name(entry_name)?;
        
        // Check file size limit
        check_file_size_limit(file.size(), self.config.max_file_size_mb * 1024 * 1024)?;
        
        output.clear();
        output.reserve(file.size() as usize);
        
        file.read_to_end(output)?;
        
        info!("Extracted file: {} ({} bytes)", entry_name, output.len());
        Ok(output.len() as u64)
    }
    
    fn extract_all(&self, output_dir: &Path) -> Result<ExtractResult> {
        let archive = self.archive.as_ref()
            .ok_or_else(|| anyhow!("Archive not opened"))?;
        
        create_dir_all(output_dir)?;
        
        let mut result = ExtractResult::new();
        result.total_files = archive.len() as u64;
        
        for i in 0..archive.len() {
            let mut file = archive.by_index(i)?;
            let entry_name = file.name();
            let outpath = output_dir.join(entry_name);
            
            // Zip Slip protection
            match validate_extraction_path(&PathBuf::from(entry_name), output_dir) {
                Ok(safe_path) => {
                    if entry_name.ends_with('/') {
                        // Directory
                        create_dir_all(&safe_path)?;
                        debug!("Created directory: {}", entry_name);
                    } else {
                        // File
                        check_file_size_limit(file.size(), self.config.max_file_size_mb * 1024 * 1024)?;
                        
                        if let Some(parent) = safe_path.parent() {
                            create_dir_all(parent)?;
                        }
                        
                        let mut outfile = BufWriter::new(File::create(&safe_path)?);
                        std::io::copy(&mut file, &mut outfile)?;
                        
                        result.success_files += 1;
                        result.total_bytes += file.size();
                        debug!("Extracted: {} ({} bytes)", entry_name, file.size());
                    }
                },
                Err(e) => {
                    warn!("Zip Slip detected: {} - {}", entry_name, e);
                    result.failed_files.push(PathBuf::from(entry_name));
                    result.warnings.push(format!("Zip Slip: {}", entry_name));
                }
            }
        }
        
        info!("Extracted {} files ({} bytes) to {}", 
            result.success_files, result.total_bytes, output_dir.display());
        
        Ok(result)
    }
    
    fn can_process(format: ArchiveFormat) -> bool {
        format == ArchiveFormat::Zip
    }
}

// ==================== TAR Processor ====================

/// TAR Processor - Full Implementation using tar crate
pub struct TarProcessor {
    path: PathBuf,
    entries: Vec<ArchiveEntry>,
    config: ArchiveConfig,
}

impl TarProcessor {
    pub fn new() -> Self {
        Self {
            path: PathBuf::new(),
            entries: Vec::new(),
            config: ArchiveConfig::default(),
        }
    }
    
    pub fn with_config(config: ArchiveConfig) -> Self {
        Self {
            path: PathBuf::new(),
            entries: Vec::new(),
            config,
        }
    }
}

impl ArchiveProcessor for TarProcessor {
    fn format(&self) -> ArchiveFormat {
        ArchiveFormat::Tar
    }
    
    fn open(&mut self, path: &Path) -> Result<ArchiveMetadata> {
        info!("Opening TAR archive: {}", path.display());
        
        self.path = path.to_path_buf();
        self.entries.clear();
        
        let file = File::open(path)?;
        let mut archive = tar::Archive::new(file);
        
        let mut total_size = 0u64;
        
        // Iterate entries to collect metadata
        for entry in archive.entries()? {
            let entry = entry?;
            let path = entry.path()?.to_path_buf();
            let size = entry.size();
            
            total_size += size;
            
            self.entries.push(ArchiveEntry {
                path,
                size,
                compressed_size: size,  // TAR has no compression
                is_dir: entry.header().entry_type().is_dir(),
                is_file: entry.header().entry_type().is_file(),
                is_encrypted: false,
                modified: SystemTime::UNIX_EPOCH,
                permissions: Some(entry.header().mode()?),
                checksum: None,
            });
        }
        
        let total_files = self.entries.len() as u64;
        
        Ok(ArchiveMetadata {
            format: ArchiveFormat::Tar,
            total_files,
            total_size,
            compressed_size: total_size,  // TAR has no compression
            compression_ratio: 1.0,  // No compression
            is_encrypted: false,
            is_multi_volume: false,
            created_time: Some(SystemTime::now()),
        })
    }
    
    fn list_entries(&self) -> Result<Vec<ArchiveEntry>> {
        Ok(self.entries.clone())
    }
    
    fn extract_file(&self, entry_path: &Path, output: &mut Vec<u8>) -> Result<u64> {
        // TAR doesn't support random access, need to unpack entire archive
        // This is a limitation - for single file extraction, we unpack everything
        warn!("TAR format doesn't support random access - extracting entire archive");
        
        let temp_dir = tempfile::tempdir()?;
        self.extract_all(temp_dir.path())?;
        
        let file_path = temp_dir.path().join(entry_path);
        let mut file = File::open(&file_path)?;
        output.clear();
        file.read_to_end(output)?;
        
        Ok(output.len() as u64)
    }
    
    fn extract_all(&self, output_dir: &Path) -> Result<ExtractResult> {
        create_dir_all(output_dir)?;
        
        let file = File::open(&self.path)?;
        let mut archive = tar::Archive::new(file);
        
        let mut result = ExtractResult::new();
        result.total_files = self.entries.len() as u64;
        
        for entry in archive.entries()? {
            let entry = entry?;
            let entry_path = entry.path()?.to_path_buf();
            
            // Zip Slip protection
            match validate_extraction_path(&entry_path, output_dir) {
                Ok(safe_path) => {
                    check_file_size_limit(entry.size(), self.config.max_file_size_mb * 1024 * 1024)?;
                    
                    entry.unpack(&safe_path)?;
                    
                    result.success_files += 1;
                    result.total_bytes += entry.size();
                },
                Err(e) => {
                    warn!("Zip Slip detected: {} - {}", entry_path.display(), e);
                    result.failed_files.push(entry_path);
                    result.warnings.push(format!("Zip Slip: {}", entry_path.display()));
                }
            }
        }
        
        info!("Extracted {} TAR entries to {}", result.success_files, output_dir.display());
        Ok(result)
    }
    
    fn can_process(format: ArchiveFormat) -> bool {
        format == ArchiveFormat::Tar
    }
}

// ==================== GZIP Processor ====================

/// GZIP Processor - Full Implementation using flate2 crate
pub struct GzipProcessor {
    path: PathBuf,
    decompressed_size: u64,
    config: ArchiveConfig,
}

impl GzipProcessor {
    pub fn new() -> Self {
        Self {
            path: PathBuf::new(),
            decompressed_size: 0,
            config: ArchiveConfig::default(),
        }
    }
    
    pub fn with_config(config: ArchiveConfig) -> Self {
        Self {
            path: PathBuf::new(),
            decompressed_size: 0,
            config,
        }
    }
}

impl ArchiveProcessor for GzipProcessor {
    fn format(&self) -> ArchiveFormat {
        ArchiveFormat::Gzip
    }
    
    fn open(&mut self, path: &Path) -> Result<ArchiveMetadata> {
        info!("Opening GZIP archive: {}", path.display());
        
        self.path = path.to_path_buf();
        
        let file = File::open(path)?;
        let compressed_size = file.metadata()?.len();
        
        let mut decoder = flate2::read::GzDecoder::new(file);
        let mut buffer = Vec::new();
        decoder.read_to_end(&mut buffer)?;
        
        self.decompressed_size = buffer.len() as u64;
        
        // Check Zip Bomb
        check_decompression_ratio(compressed_size, self.decompressed_size, self.config.max_decompression_ratio)?;
        
        Ok(ArchiveMetadata {
            format: ArchiveFormat::Gzip,
            total_files: 1,  // GZIP is single file
            total_size: self.decompressed_size,
            compressed_size,
            compression_ratio: if compressed_size > 0 {
                self.decompressed_size as f64 / compressed_size as f64
            } else {
                0.0
            },
            is_encrypted: false,
            is_multi_volume: false,
            created_time: Some(SystemTime::now()),
        })
    }
    
    fn list_entries(&self) -> Result<Vec<ArchiveEntry>> {
        // GZIP is single file - infer name from archive name
        let name = self.path.file_name()
            .and_then(|n| n.to_str())
            .unwrap_or("unknown")
            .replace(".gz", "")
            .replace(".gzip", "");
        
        Ok(vec![ArchiveEntry::file(
            PathBuf::from(name),
            self.decompressed_size,
            0,  // GZIP doesn't preserve compressed size per file
        )])
    }
    
    fn extract_file(&self, entry_path: &Path, output: &mut Vec<u8>) -> Result<u64> {
        // GZIP is single file - just decompress it
        let file = File::open(&self.path)?;
        let mut decoder = flate2::read::GzDecoder::new(file);
        
        output.clear();
        decoder.read_to_end(output)?;
        
        check_file_size_limit(output.len() as u64, self.config.max_file_size_mb * 1024 * 1024)?;
        
        info!("Decompressed GZIP file: {} bytes", output.len());
        Ok(output.len() as u64)
    }
    
    fn extract_all(&self, output_dir: &Path) -> Result<ExtractResult> {
        create_dir_all(output_dir)?;
        
        let entries = self.list_entries()?;
        let entry = entries.first()
            .ok_or_else(|| anyhow!("No entry in GZIP archive"))?;
        
        let outpath = output_dir.join(&entry.path);
        
        // Zip Slip protection
        validate_extraction_path(&entry.path, output_dir)?;
        
        if let Some(parent) = outpath.parent() {
            create_dir_all(parent)?;
        }
        
        let file = File::open(&self.path)?;
        let mut decoder = flate2::read::GzDecoder::new(file);
        let mut outfile = BufWriter::new(File::create(&outpath)?);
        
        std::io::copy(&mut decoder, &mut outfile)?;
        
        let result = ExtractResult {
            total_files: 1,
            total_bytes: self.decompressed_size,
            success_files: 1,
            failed_files: Vec::new(),
            skipped_files: Vec::new(),
            warnings: Vec::new(),
        };
        
        info!("Decompressed GZIP to: {}", outpath.display());
        Ok(result)
    }
    
    fn can_process(format: ArchiveFormat) -> bool {
        format == ArchiveFormat::Gzip
    }
}

// ==================== TAR.GZ Composite Processor ====================

/// TAR.GZ Processor - Composite Format (TAR + GZIP)
pub struct TarGzipProcessor {
    gzip_processor: GzipProcessor,
    config: ArchiveConfig,
}

impl TarGzipProcessor {
    pub fn new() -> Self {
        Self {
            gzip_processor: GzipProcessor::new(),
            config: ArchiveConfig::default(),
        }
    }
    
    pub fn with_config(config: ArchiveConfig) -> Self {
        Self {
            gzip_processor: GzipProcessor::with_config(config.clone()),
            config,
        }
    }
}

impl ArchiveProcessor for TarGzipProcessor {
    fn format(&self) -> ArchiveFormat {
        ArchiveFormat::TarGzip
    }
    
    fn open(&mut self, path: &Path) -> Result<ArchiveMetadata> {
        info!("Opening TAR.GZ archive: {}", path.display());
        
        // Step 1: Decompress GZIP
        let temp_dir = tempfile::tempdir()?;
        self.gzip_processor.open(path)?;
        self.gzip_processor.extract_all(temp_dir.path())?;
        
        // Step 2: Open TAR
        let tar_entries = self.gzip_processor.list_entries()?;
        let tar_file = tar_entries.first()
            .ok_or_else(|| anyhow!("No TAR file in GZIP"))?;
        
        let tar_path = temp_dir.path().join(&tar_file.path);
        
        let mut tar_processor = TarProcessor::with_config(self.config.clone());
        let tar_metadata = tar_processor.open(&tar_path)?;
        
        Ok(ArchiveMetadata {
            format: ArchiveFormat::TarGzip,
            total_files: tar_metadata.total_files,
            total_size: tar_metadata.total_size,
            compressed_size: path.metadata()?.len(),
            compression_ratio: if path.metadata()?.len() > 0 {
                tar_metadata.total_size as f64 / path.metadata()?.len() as f64
            } else {
                0.0
            },
            is_encrypted: false,
            is_multi_volume: false,
            created_time: Some(SystemTime::now()),
        })
    }
    
    fn list_entries(&self) -> Result<Vec<ArchiveEntry>> {
        // Need to implement properly - this requires decompressing first
        warn!("TAR.GZ list_entries requires full decompression - consider extract_all instead");
        Ok(Vec::new())
    }
    
    fn extract_file(&self, entry_path: &Path, output: &mut Vec<u8>) -> Result<u64> {
        warn!("TAR.GZ extract_file requires full unpacking - inefficient for single file");
        
        let temp_dir = tempfile::tempdir()?;
        self.extract_all(temp_dir.path())?;
        
        let file_path = temp_dir.path().join(entry_path);
        let mut file = File::open(&file_path)?;
        output.clear();
        file.read_to_end(output)?;
        
        Ok(output.len() as u64)
    }
    
    fn extract_all(&self, output_dir: &Path) -> Result<ExtractResult> {
        info!("Extracting TAR.GZ to: {}", output_dir.display());
        
        // Step 1: Decompress GZIP to temp
        let temp_dir = tempfile::tempdir()?;
        self.gzip_processor.extract_all(temp_dir.path())?;
        
        // Step 2: Extract TAR
        let tar_entries = self.gzip_processor.list_entries()?;
        let tar_file = tar_entries.first()
            .ok_or_else(|| anyhow!("No TAR file found"))?;
        
        let tar_path = temp_dir.path().join(&tar_file.path);
        
        let mut tar_processor = TarProcessor::with_config(self.config.clone());
        tar_processor.open(&tar_path)?;
        tar_processor.extract_all(output_dir)
    }
    
    fn can_process(format: ArchiveFormat) -> bool {
        format == ArchiveFormat::TarGzip
    }
}

// ==================== Stub Processors for Phase 2 ====================

/// ZSTD Processor Stub (Phase 2/3)
pub struct ZstdProcessor;

impl ArchiveProcessor for ZstdProcessor {
    fn format(&self) -> ArchiveFormat { ArchiveFormat::Zstd }
    fn open(&mut self, _path: &Path) -> Result<ArchiveMetadata> {
        Err(anyhow!("ZSTD processor not yet implemented"))
    }
    fn list_entries(&self) -> Result<Vec<ArchiveEntry>> { Ok(Vec::new()) }
    fn extract_file(&self, _entry: &Path, _output: &mut Vec<u8>) -> Result<u64> { Ok(0) }
    fn extract_all(&self, _dir: &Path) -> Result<ExtractResult> { Ok(ExtractResult::new()) }
    fn can_process(format: ArchiveFormat) -> bool { format == ArchiveFormat::Zstd }
    fn new() -> Self { Self }
}

/// BZIP2 Processor Stub (Phase 2/3)
pub struct Bzip2Processor;

impl ArchiveProcessor for Bzip2Processor {
    fn format(&self) -> ArchiveFormat { ArchiveFormat::Bzip2 }
    fn open(&mut self, _path: &Path) -> Result<ArchiveMetadata> {
        Err(anyhow!("BZIP2 processor not yet implemented"))
    }
    fn list_entries(&self) -> Result<Vec<ArchiveEntry>> { Ok(Vec::new()) }
    fn extract_file(&self, _entry: &Path, _output: &mut Vec<u8>) -> Result<u64> { Ok(0) }
    fn extract_all(&self, _dir: &Path) -> Result<ExtractResult> { Ok(ExtractResult::new()) }
    fn can_process(format: ArchiveFormat) -> bool { format == ArchiveFormat::Bzip2 }
    fn new() -> Self { Self }
}

/// LZ4 Processor Stub (Phase 2/3)
pub struct Lz4Processor;

impl ArchiveProcessor for Lz4Processor {
    fn format(&self) -> ArchiveFormat { ArchiveFormat::Lz4 }
    fn open(&mut self, _path: &Path) -> Result<ArchiveMetadata> {
        Err(anyhow!("LZ4 processor not yet implemented"))
    }
    fn list_entries(&self) -> Result<Vec<ArchiveEntry>> { Ok(Vec::new()) }
    fn extract_file(&self, _entry: &Path, _output: &mut Vec<u8>) -> Result<u64> { Ok(0) }
    fn extract_all(&self, _dir: &Path) -> Result<ExtractResult> { Ok(ExtractResult::new()) }
    fn can_process(format: ArchiveFormat) -> bool { format == ArchiveFormat::Lz4 }
    fn new() -> Self { Self }
}

/// TAR.BZ2 Composite Processor Stub (Phase 2/3)
pub struct TarBzip2Processor;

impl ArchiveProcessor for TarBzip2Processor {
    fn format(&self) -> ArchiveFormat { ArchiveFormat::TarBzip2 }
    fn open(&mut self, _path: &Path) -> Result<ArchiveMetadata> {
        Err(anyhow!("TAR.BZ2 processor not yet implemented"))
    }
    fn list_entries(&self) -> Result<Vec<ArchiveEntry>> { Ok(Vec::new()) }
    fn extract_file(&self, _entry: &Path, _output: &mut Vec<u8>) -> Result<u64> { Ok(0) }
    fn extract_all(&self, _dir: &Path) -> Result<ExtractResult> { Ok(ExtractResult::new()) }
    fn can_process(format: ArchiveFormat) -> bool { format == ArchiveFormat::TarBzip2 }
    fn new() -> Self { Self }
}

/// TAR.ZST Composite Processor Stub (Phase 2/3)
pub struct TarZstdProcessor;

impl ArchiveProcessor for TarZstdProcessor {
    fn format(&self) -> ArchiveFormat { ArchiveFormat::TarZstd }
    fn open(&mut self, _path: &Path) -> Result<ArchiveMetadata> {
        Err(anyhow!("TAR.ZST processor not yet implemented"))
    }
    fn list_entries(&self) -> Result<Vec<ArchiveEntry>> { Ok(Vec::new()) }
    fn extract_file(&self, _entry: &Path, _output: &mut Vec<u8>) -> Result<u64> { Ok(0) }
    fn extract_all(&self, _dir: &Path) -> Result<ExtractResult> { Ok(ExtractResult::new()) }
    fn can_process(format: ArchiveFormat) -> bool { format == ArchiveFormat::TarZstd }
    fn new() -> Self { Self }
}