use anyhow::{anyhow, Result};
use byteorder::{LittleEndian, ReadBytesExt};
use flate2::read::ZlibDecoder;
use std::collections::HashMap;
use std::fs::File;
use std::io::{BufReader, Read, Seek, SeekFrom};
use std::path::Path;

const UOP_MAGIC: u32 = 0x0050594D;

#[derive(Debug, Clone)]
pub struct UopEntry {
    pub data_offset: u64,
    pub header_length: u32,
    pub compressed_length: u32,
    pub decompressed_length: u32,
    pub hash: u64,
    pub compression: u16,
}

/// Read the UOP file and return a map from hash → UopEntry.
pub fn read_uop_index(path: &Path) -> Result<HashMap<u64, UopEntry>> {
    let file = File::open(path)?;
    let mut reader = BufReader::new(file);

    // Header
    let magic = reader.read_u32::<LittleEndian>()?;
    if magic != UOP_MAGIC {
        return Err(anyhow!("Not a valid UOP file: {:?}", path));
    }

    let _version = reader.read_u32::<LittleEndian>()?;
    let _signature = reader.read_u32::<LittleEndian>()?;
    let mut next_block = reader.read_u64::<LittleEndian>()?;
    let _max_files = reader.read_u32::<LittleEndian>()?;

    // Skip tag (36 bytes)
    let mut tag_buf = [0u8; 36];
    reader.read_exact(&mut tag_buf)?;

    let mut map = HashMap::new();

    while next_block != 0 {
        reader.seek(SeekFrom::Start(next_block))?;
        next_block = reader.read_u64::<LittleEndian>()?;
        let file_count = reader.read_u32::<LittleEndian>()?;

        for _ in 0..file_count {
            let data_offset = reader.read_u64::<LittleEndian>()?;
            let header_length = reader.read_u32::<LittleEndian>()?;
            let compressed_length = reader.read_u32::<LittleEndian>()?;
            let decompressed_length = reader.read_u32::<LittleEndian>()?;
            let hash = reader.read_u64::<LittleEndian>()?;
            let _crc = reader.read_u32::<LittleEndian>()?;
            let compression = reader.read_u16::<LittleEndian>()?;

            if data_offset == 0 {
                continue;
            }

            map.insert(
                hash,
                UopEntry {
                    data_offset,
                    header_length,
                    compressed_length,
                    decompressed_length,
                    hash,
                    compression,
                },
            );
        }
    }

    Ok(map)
}

/// Read decompressed data for a UOP entry.
pub fn read_uop_entry(path: &Path, entry: &UopEntry) -> Result<Vec<u8>> {
    let mut file = File::open(path)?;
    let offset = entry.data_offset + entry.header_length as u64;
    file.seek(SeekFrom::Start(offset))?;

    let mut compressed = vec![0u8; entry.compressed_length as usize];
    file.read_exact(&mut compressed)?;

    if entry.compression == 1 {
        let mut decoder = ZlibDecoder::new(&compressed[..]);
        let mut decompressed = Vec::with_capacity(entry.decompressed_length as usize);
        decoder.read_to_end(&mut decompressed)?;
        Ok(decompressed)
    } else {
        Ok(compressed)
    }
}

/// Hash function for UOP filenames (per ClassicUO UOFileUop.cs).
pub fn uop_hash(s: &str) -> u64 {
    let s = s.to_lowercase();
    let bytes = s.as_bytes();

    let (mut eax, mut ebx, mut ecx, mut edx, mut esi, mut edi) = (
        0u32,
        bytes.len() as u32,
        0u32,
        0u32,
        0u32,
        0u32,
    );

    ecx = 0x9E3779B9u32;
    edi = ecx;
    esi = ecx;

    let mut i = 0usize;
    while i + 12 <= bytes.len() {
        edi = edi.wrapping_add(
            (bytes[i] as u32)
                | ((bytes[i + 1] as u32) << 8)
                | ((bytes[i + 2] as u32) << 16)
                | ((bytes[i + 3] as u32) << 24),
        );
        esi = esi.wrapping_add(
            (bytes[i + 4] as u32)
                | ((bytes[i + 5] as u32) << 8)
                | ((bytes[i + 6] as u32) << 16)
                | ((bytes[i + 7] as u32) << 24),
        );
        edx = edx
            .wrapping_add(
                (bytes[i + 8] as u32)
                    | ((bytes[i + 9] as u32) << 8)
                    | ((bytes[i + 10] as u32) << 16)
                    | ((bytes[i + 11] as u32) << 24),
            )
            .wrapping_add(ebx);
        edx ^= (esi >> 28).wrapping_add(edi << 4);
        edx = edx.wrapping_add(esi);
        esi ^= (edx >> 26).wrapping_add(edx << 6);
        esi = esi.wrapping_add(edi);
        edi ^= (esi >> 24).wrapping_add(esi << 8);
        edi = edi.wrapping_add(edx);
        edx ^= (edi >> 16).wrapping_add(edi << 16);
        edx = edx.wrapping_add(esi);
        esi ^= (edx >> 13).wrapping_add(edx << 19);
        esi = esi.wrapping_add(edi);
        edi ^= (esi >> 28).wrapping_add(esi << 4);
        edi = edi.wrapping_add(edx);
        i += 12;
    }

    let remaining = bytes.len() - i;
    if remaining > 0 {
        eax = 0;
        if remaining >= 11 { eax = eax.wrapping_add((bytes[i + 10] as u32) << 24); }
        if remaining >= 10 { eax = eax.wrapping_add((bytes[i + 9] as u32) << 16); }
        if remaining >= 9  { eax = eax.wrapping_add((bytes[i + 8] as u32) << 8); }
        if remaining >= 8  { edi = edi.wrapping_add((bytes[i + 7] as u32) << 24); }
        if remaining >= 7  { edi = edi.wrapping_add((bytes[i + 6] as u32) << 16); }
        if remaining >= 6  { edi = edi.wrapping_add((bytes[i + 5] as u32) << 8); }
        if remaining >= 5  { edi = edi.wrapping_add(bytes[i + 4] as u32); }
        if remaining >= 4  { esi = esi.wrapping_add((bytes[i + 3] as u32) << 24); }
        if remaining >= 3  { esi = esi.wrapping_add((bytes[i + 2] as u32) << 16); }
        if remaining >= 2  { esi = esi.wrapping_add((bytes[i + 1] as u32) << 8); }
        if remaining >= 1  { esi = esi.wrapping_add(bytes[i] as u32); }
        esi = esi.wrapping_add(eax);

        edi ^= (esi >> 28).wrapping_add(esi << 4);
        edi = edi.wrapping_add(esi);
        esi ^= (edi >> 26).wrapping_add(edi << 6);
        esi = esi.wrapping_add(edi);
        edi ^= (esi >> 24).wrapping_add(esi << 8);
        edi = edi.wrapping_add(esi);
        esi ^= (edi >> 16).wrapping_add(edi << 16);
        esi = esi.wrapping_add(edi);
        edi ^= (esi >> 13).wrapping_add(esi << 19);
        edi = edi.wrapping_add(esi);
        esi ^= (edi >> 28).wrapping_add(edi << 4);
        esi = esi.wrapping_add(edi);
    }

    let _ = eax;
    let _ = ecx;
    let _ = edx;

    ((edi as u64) << 32) | (esi as u64)
}
