// Format Detector - Automatic Detection Based on Magic Numbers

use std::fs::File;
use std::io::Read;
use std::path::Path;
use anyhow::Result;

use crate::archive::processor::ArchiveFormat;

/// Format Detector based on Magic Numbers
pub struct FormatDetector {
    magic_table: Vec<(Vec<u8>, ArchiveFormat, usize)>,
}

impl FormatDetector {
    pub fn new() -> Self {
        let magic_table = vec![
            // ZIP: 50 4B 03 04 or 50 4B 05 06 (empty) or 50 4B 07 08 (spanned)
            (vec![0x50, 0x4B, 0x03, 0x04], ArchiveFormat::Zip, 4),
            (vec![0x50, 0x4B, 0x05, 0x06], ArchiveFormat::Zip, 4),
            
            // GZIP: 1F 8B
            (vec![0x1F, 0x8B], ArchiveFormat::Gzip, 2),
        ];
        
        Self { magic_table }
    }
    
    /// Detect file format based on Magic Number
    pub fn detect(&self, path: &Path) -> Result<ArchiveFormat> {
        let mut file = File::open(path)?;
        let mut buffer = vec![0u8; 512];
        
        let bytes_read = file.read(&mut buffer)?;
        if bytes_read < 2 {
            return Ok(ArchiveFormat::Unknown);
        }
        
        // Match Magic Numbers
        for (magic, format, offset) in &self.magic_table {
            if buffer.len() >= *offset && buffer[0..magic.len()] == *magic {
                return Ok(*format);
            }
        }
        
        // Special detection: TAR format (check ustar magic at offset 257)
        if buffer.len() >= 262 {
            if &buffer[257..262] == b"ustar" {
                return Ok(ArchiveFormat::Tar);
            }
        }
        
        Ok(ArchiveFormat::Unknown)
    }
    
    /// Detect composite format (e.g., TAR.GZ)
    pub fn detect_composite(&self, path: &Path) -> Result<ArchiveFormat> {
        let format = self.detect(path)?;
        
        // If GZIP, check if it's TAR.GZ (by extension for now)
        if format == ArchiveFormat::Gzip {
            let ext = path.extension()
                .and_then(|e| e.to_str())
                .unwrap_or("")
                .to_lowercase();
            
            if ext == "tgz" || ext == "gz" {
                // Check if filename contains .tar
                let filename = path.file_name()
                    .and_then(|n| n.to_str())
                    .unwrap_or("");
                
                if filename.contains(".tar") {
                    return Ok(ArchiveFormat::TarGzip);
                }
            }
        }
        
        Ok(format)
    }
}

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

#[cfg(test)]
mod tests {
    use super::*;
    use tempfile::TempDir;
    use std::io::Write;
    
    #[test]
    fn test_detect_zip() {
        let temp_dir = TempDir::new().unwrap();
        let zip_path = temp_dir.path().join("test.zip");
        
        // Create minimal ZIP file header
        let mut file = File::create(&zip_path).unwrap();
        file.write_all(&[0x50, 0x4B, 0x03, 0x04]).unwrap();
        
        let detector = FormatDetector::new();
        let format = detector.detect(&zip_path).unwrap();
        
        assert_eq!(format, ArchiveFormat::Zip);
    }
    
    #[test]
    fn test_detect_gzip() {
        let temp_dir = TempDir::new().unwrap();
        let gz_path = temp_dir.path().join("test.gz");
        
        // Create minimal GZIP file header
        let mut file = File::create(&gz_path).unwrap();
        file.write_all(&[0x1F, 0x8B]).unwrap();
        
        let detector = FormatDetector::new();
        let format = detector.detect(&gz_path).unwrap();
        
        assert_eq!(format, ArchiveFormat::Gzip);
    }
    
    #[test]
    fn test_detect_unknown() {
        let temp_dir = TempDir::new().unwrap();
        let unknown_path = temp_dir.path().join("test.bin");
        
        // Create unknown file
        let mut file = File::create(&unknown_path).unwrap();
        file.write_all(b"unknown data").unwrap();
        
        let detector = FormatDetector::new();
        let format = detector.detect(&unknown_path).unwrap();
        
        assert_eq!(format, ArchiveFormat::Unknown);
    }
}