use std::fs;
use std::io::{Read, Seek, SeekFrom, Write};
use std::path::{Path, PathBuf};

use rand::Rng;

use crate::compression;
use crate::crypto;
use crate::format::{self, Header, TocEntry, HEADER_SIZE};
use crate::key::KEY;

/// Processed file data collected during Pass 1 of pack.
struct ProcessedFile {
    name: String,
    original_size: u32,
    compressed_size: u32,
    encrypted_size: u32,
    iv: [u8; 16],
    hmac: [u8; 32],
    sha256: [u8; 32],
    compression_flag: u8,
    ciphertext: Vec<u8>,
    padding_after: u16,
    padding_bytes: Vec<u8>,
}

/// Read and de-obfuscate archive header and TOC entries.
///
/// Handles XOR header bootstrapping (FORMAT.md Section 10 steps 1-3)
/// and TOC decryption (Section 10 step 4) automatically.
/// Used by both unpack() and inspect().
fn read_archive_metadata(file: &mut fs::File) -> anyhow::Result<(Header, Vec<TocEntry>)> {
    // Step 1-3: Read header with XOR bootstrapping
    let header = format::read_header_auto(file)?;

    // Step 4: Read TOC (possibly encrypted)
    file.seek(SeekFrom::Start(header.toc_offset as u64))?;
    let mut toc_raw = vec![0u8; header.toc_size as usize];
    file.read_exact(&mut toc_raw)?;

    let entries = if header.flags & 0x02 != 0 {
        // TOC is encrypted: decrypt with toc_iv, then parse
        let toc_plaintext = crypto::decrypt_data(&toc_raw, &KEY, &header.toc_iv)?;
        format::read_toc_from_buf(&toc_plaintext, header.file_count)?
    } else {
        // TOC is plaintext: parse directly
        format::read_toc_from_buf(&toc_raw, header.file_count)?
    };

    Ok((header, entries))
}

/// Pack files into an encrypted archive.
///
/// Two-pass algorithm with full obfuscation:
///   Pass 1: Read, hash, compress, encrypt each file; generate decoy padding.
///   Pass 2: Encrypt TOC, compute offsets, XOR header, write archive.
pub fn pack(files: &[PathBuf], output: &Path, no_compress: &[String]) -> anyhow::Result<()> {
    anyhow::ensure!(!files.is_empty(), "No input files specified");

    let mut rng = rand::rng();

    // --- Pass 1: Process all files ---
    let mut processed: Vec<ProcessedFile> = Vec::with_capacity(files.len());

    for file_path in files {
        let data = fs::read(file_path)?;

        // Validate file size <= u32::MAX
        anyhow::ensure!(
            data.len() <= u32::MAX as usize,
            "File too large: {} ({} bytes exceeds 4 GB limit)",
            file_path.display(),
            data.len()
        );

        // Use just the filename (not the full path) as the archive entry name
        let name = file_path
            .file_name()
            .ok_or_else(|| anyhow::anyhow!("Invalid file path: {}", file_path.display()))?
            .to_str()
            .ok_or_else(|| anyhow::anyhow!("Non-UTF-8 filename: {}", file_path.display()))?
            .to_string();

        // Step 1: SHA-256 of original data
        let sha256 = crypto::sha256_hash(&data);

        // Step 2: Determine compression and compress if needed
        let should_compress = compression::should_compress(&name, no_compress);
        let (compressed_data, compression_flag) = if should_compress {
            let compressed = compression::compress(&data)?;
            (compressed, 1u8)
        } else {
            (data.clone(), 0u8)
        };

        let original_size = data.len() as u32;
        let compressed_size = compressed_data.len() as u32;

        // Step 3: Generate random IV
        let iv = crypto::generate_iv();

        // Step 4: Encrypt
        let ciphertext = crypto::encrypt_data(&compressed_data, &KEY, &iv);
        let encrypted_size = ciphertext.len() as u32;

        // Step 5: Compute HMAC over IV || ciphertext
        let hmac = crypto::compute_hmac(&KEY, &iv, &ciphertext);

        // Step 6: Generate decoy padding (FORMAT.md Section 9.3)
        let padding_after: u16 = rng.random_range(64..=4096);
        let mut padding_bytes = vec![0u8; padding_after as usize];
        rand::Fill::fill(&mut padding_bytes[..], &mut rng);

        processed.push(ProcessedFile {
            name,
            original_size,
            compressed_size,
            encrypted_size,
            iv,
            hmac,
            sha256,
            compression_flag,
            ciphertext,
            padding_after,
            padding_bytes,
        });
    }

    // --- Pass 2: Compute offsets and write archive ---

    // Determine flags byte: bit 0 if any file is compressed, bits 1-3 for obfuscation
    let any_compressed = processed.iter().any(|pf| pf.compression_flag == 1);
    let mut flags: u8 = if any_compressed { 0x01 } else { 0x00 };
    // Enable all three obfuscation features
    flags |= 0x02; // bit 1: TOC encrypted
    flags |= 0x04; // bit 2: XOR header
    flags |= 0x08; // bit 3: decoy padding

    // Build TOC entries (with placeholder data_offset=0, will be set after toc_size known)
    let toc_entries: Vec<TocEntry> = processed
        .iter()
        .map(|pf| TocEntry {
            name: pf.name.clone(),
            original_size: pf.original_size,
            compressed_size: pf.compressed_size,
            encrypted_size: pf.encrypted_size,
            data_offset: 0, // placeholder
            iv: pf.iv,
            hmac: pf.hmac,
            sha256: pf.sha256,
            compression_flag: pf.compression_flag,
            padding_after: pf.padding_after,
        })
        .collect();

    // Serialize TOC to get plaintext size, then encrypt to get final toc_size
    let toc_plaintext = format::serialize_toc(&toc_entries)?;

    // Generate TOC IV and encrypt
    let toc_iv = crypto::generate_iv();
    let encrypted_toc = crypto::encrypt_data(&toc_plaintext, &KEY, &toc_iv);
    let encrypted_toc_size = encrypted_toc.len() as u32;

    let toc_offset = HEADER_SIZE;

    // Compute data offsets (accounting for encrypted TOC size and padding)
    let data_block_start = toc_offset + encrypted_toc_size;
    let mut data_offsets: Vec<u32> = Vec::with_capacity(processed.len());
    let mut current_offset = data_block_start;
    for pf in &processed {
        data_offsets.push(current_offset);
        current_offset += pf.encrypted_size + pf.padding_after as u32;
    }

    // Now re-serialize TOC with correct data_offsets
    let final_toc_entries: Vec<TocEntry> = processed
        .iter()
        .enumerate()
        .map(|(i, pf)| TocEntry {
            name: pf.name.clone(),
            original_size: pf.original_size,
            compressed_size: pf.compressed_size,
            encrypted_size: pf.encrypted_size,
            data_offset: data_offsets[i],
            iv: pf.iv,
            hmac: pf.hmac,
            sha256: pf.sha256,
            compression_flag: pf.compression_flag,
            padding_after: pf.padding_after,
        })
        .collect();

    let final_toc_plaintext = format::serialize_toc(&final_toc_entries)?;
    let final_encrypted_toc = crypto::encrypt_data(&final_toc_plaintext, &KEY, &toc_iv);
    let final_encrypted_toc_size = final_encrypted_toc.len() as u32;

    // Sanity check: encrypted TOC size should not change (same plaintext length)
    assert_eq!(
        encrypted_toc_size, final_encrypted_toc_size,
        "TOC encrypted size changed unexpectedly"
    );

    // Create header
    let header = Header {
        version: format::VERSION,
        flags,
        file_count: processed.len() as u16,
        toc_offset,
        toc_size: final_encrypted_toc_size,
        toc_iv,
        reserved: [0u8; 8],
    };

    // Serialize header to buffer and XOR
    let mut header_buf = format::write_header_to_buf(&header);
    format::xor_header_buf(&mut header_buf);

    // Open output file
    let mut out_file = fs::File::create(output)?;

    // Write XOR'd header
    out_file.write_all(&header_buf)?;

    // Write encrypted TOC
    out_file.write_all(&final_encrypted_toc)?;

    // Write data blocks with interleaved decoy padding
    for pf in &processed {
        out_file.write_all(&pf.ciphertext)?;
        out_file.write_all(&pf.padding_bytes)?;
    }

    let total_bytes = current_offset;
    println!(
        "Packed {} files into {} ({} bytes)",
        processed.len(),
        output.display(),
        total_bytes
    );

    Ok(())
}

/// Inspect archive metadata without decryption.
///
/// Reads and displays the header and all TOC entries.
/// Handles XOR header de-obfuscation and TOC decryption.
pub fn inspect(archive: &Path) -> anyhow::Result<()> {
    let mut file = fs::File::open(archive)?;

    // Read header and TOC with full de-obfuscation
    let (header, entries) = read_archive_metadata(&mut file)?;

    // Print header info
    let filename = archive
        .file_name()
        .map(|s| s.to_string_lossy().to_string())
        .unwrap_or_else(|| archive.display().to_string());

    println!("Archive: {}", filename);
    println!("Version: {}", header.version);
    println!("Flags: 0x{:02X}", header.flags);
    println!("Files: {}", header.file_count);
    println!("TOC offset: {}", header.toc_offset);
    println!("TOC size: {}", header.toc_size);
    println!();

    // Print each file entry
    let mut total_original: u64 = 0;
    for (i, entry) in entries.iter().enumerate() {
        let compression_str = if entry.compression_flag == 1 {
            "yes"
        } else {
            "no"
        };

        println!("[{}] {}", i, entry.name);
        println!("    Original: {} bytes", entry.original_size);
        println!("    Compressed: {} bytes", entry.compressed_size);
        println!("    Encrypted: {} bytes", entry.encrypted_size);
        println!("    Offset: {}", entry.data_offset);
        println!("    Compression: {}", compression_str);
        println!("    Padding after: {} bytes", entry.padding_after);
        println!(
            "    IV: {}",
            entry.iv.iter().map(|b| format!("{:02x}", b)).collect::<String>()
        );
        println!(
            "    HMAC: {}",
            entry.hmac.iter().map(|b| format!("{:02x}", b)).collect::<String>()
        );
        println!(
            "    SHA-256: {}",
            entry.sha256.iter().map(|b| format!("{:02x}", b)).collect::<String>()
        );

        total_original += entry.original_size as u64;
    }

    println!();
    println!("Total original size: {} bytes", total_original);

    Ok(())
}

/// Unpack an encrypted archive, extracting all files with HMAC and SHA-256 verification.
///
/// Follows FORMAT.md Section 10 decode order:
///   1. Read header with XOR bootstrapping
///   2. Read and decrypt TOC entries
///   3. For each file: seek to data_offset, verify HMAC, decrypt, decompress, verify SHA-256, write
pub fn unpack(archive: &Path, output_dir: &Path) -> anyhow::Result<()> {
    let mut file = fs::File::open(archive)?;

    // Read header and TOC with full de-obfuscation
    let (_header, entries) = read_archive_metadata(&mut file)?;

    // Create output directory
    fs::create_dir_all(output_dir)?;

    let file_count = entries.len();
    let mut error_count: usize = 0;
    let mut success_count: usize = 0;

    for entry in &entries {
        // Sanitize filename: reject directory traversal
        if entry.name.starts_with('/') || entry.name.contains("..") {
            eprintln!(
                "Skipping file with unsafe name: {} (directory traversal attempt)",
                entry.name
            );
            error_count += 1;
            continue;
        }

        // Seek to data_offset and read ciphertext
        file.seek(SeekFrom::Start(entry.data_offset as u64))?;
        let mut ciphertext = vec![0u8; entry.encrypted_size as usize];
        file.read_exact(&mut ciphertext)?;

        // Step 1: Verify HMAC FIRST (encrypt-then-MAC)
        if !crypto::verify_hmac(&KEY, &entry.iv, &ciphertext, &entry.hmac) {
            eprintln!("HMAC verification failed for {}, skipping", entry.name);
            error_count += 1;
            continue;
        }

        // Step 2: Decrypt
        let decrypted = match crypto::decrypt_data(&ciphertext, &KEY, &entry.iv) {
            Ok(data) => data,
            Err(e) => {
                eprintln!("Decryption failed for {}: {}", entry.name, e);
                error_count += 1;
                continue;
            }
        };

        // Step 3: Decompress if compressed
        let decompressed = if entry.compression_flag == 1 {
            match compression::decompress(&decrypted) {
                Ok(data) => data,
                Err(e) => {
                    eprintln!("Decompression failed for {}: {}", entry.name, e);
                    error_count += 1;
                    continue;
                }
            }
        } else {
            decrypted
        };

        // Step 4: Verify SHA-256
        let computed_sha256 = crypto::sha256_hash(&decompressed);
        if computed_sha256 != entry.sha256 {
            eprintln!(
                "SHA-256 mismatch for {} (data may be corrupted)",
                entry.name
            );
            error_count += 1;
            // Still write the file per spec
        }

        // Step 5: Create parent directories if name contains path separators
        let output_path = output_dir.join(&entry.name);
        if let Some(parent) = output_path.parent() {
            fs::create_dir_all(parent)?;
        }

        // Step 6: Write file
        fs::write(&output_path, &decompressed)?;
        println!("Extracted: {} ({} bytes)", entry.name, entry.original_size);
        success_count += 1;
    }

    println!(
        "Extracted {}/{} files",
        success_count, file_count
    );

    if error_count > 0 {
        anyhow::bail!("{} file(s) had verification errors", error_count);
    }

    Ok(())
}
