use std::io::{SeekFrom, Write};

use log::warn;

use crate::compression::CompressionMethod;
use crate::error::Error;
use crate::extra_fields::{ExtraField, GenericExtraField, Zip64ExtraField};
use crate::{cp437, general_purpose_flags, Encoding, Signature};
use androscalpel_serializer::{ReadSeek, Result, Serializable};

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LocalFileHeader {
    // signature: Signature(0x04034b50)
    pub version_needed_to_extract: u16,
    pub general_purpose_flags: u16,
    pub compression_method: CompressionMethod,
    pub last_mod_file_time: u16,
    pub last_mod_file_data: u16,
    pub crc_32: u32,
    pub compressed_size: u32,
    pub uncompressed_size: u32,
    // file_name_length: u16,
    // extra_field_length: u16,
    pub file_name: Vec<u8>,
    pub extra_field: Vec<ExtraField>,
    /// Remaining bytes in the extra_fields that could not be parsed as ExtraField
    pub malformed_extra_field: Vec<u8>,
    pub decryption_header: Option<DecryptionHeader>,
}

impl Serializable for LocalFileHeader {
    fn serialize(&self, output: &mut dyn Write) -> Result<()> {
        Self::SIGNATURE.serialize(output)?;
        self.version_needed_to_extract.serialize(output)?;
        self.general_purpose_flags.serialize(output)?;
        self.compression_method.serialize(output)?;
        self.last_mod_file_time.serialize(output)?;
        self.last_mod_file_data.serialize(output)?;
        self.crc_32.serialize(output)?;
        self.compressed_size.serialize(output)?;
        self.uncompressed_size.serialize(output)?;
        (self.file_name.len() as u16).serialize(output)?;
        (self
            .extra_field
            .iter()
            .map(|f| f.size() as u16)
            .sum::<u16>()
            + self.malformed_extra_field.len() as u16)
            .serialize(output)?;
        for c in &self.file_name {
            c.serialize(output)?;
        }
        for c in &self.extra_field {
            c.serialize(output)?;
        }
        for c in &self.malformed_extra_field {
            c.serialize(output)?;
        }
        if let Some(header) = &self.decryption_header {
            header.serialize(output)?;
        }
        Ok(())
    }

    fn deserialize(input: &mut dyn ReadSeek) -> Result<Self> {
        let signature = Signature::deserialize(input)?;
        assert_eq!(signature, Self::SIGNATURE); // TODO
        let version_needed_to_extract = u16::deserialize(input)?;
        let general_purpose_flags = u16::deserialize(input)?;
        let compression_method = CompressionMethod::deserialize(input)?;
        let last_mod_file_time = u16::deserialize(input)?;
        let last_mod_file_data = u16::deserialize(input)?;
        let crc_32 = u32::deserialize(input)?;
        let compressed_size = u32::deserialize(input)?;
        let uncompressed_size = u32::deserialize(input)?;
        let file_name_length = u16::deserialize(input)?;
        let extra_field_length = u16::deserialize(input)?;
        let mut file_name = vec![];
        for _ in 0..file_name_length {
            file_name.push(u8::deserialize(input)?);
        }
        let mut header = Self {
            version_needed_to_extract,
            general_purpose_flags,
            compression_method,
            last_mod_file_time,
            last_mod_file_data,
            crc_32,
            compressed_size,
            uncompressed_size,
            file_name,
            extra_field: vec![],
            malformed_extra_field: vec![],
            decryption_header: None,
        };
        //let end_of_extra_field = input.stream_position().unwrap() + extra_field_length as u64;
        let extra_field_off = input.stream_position().unwrap();
        let mut extra_size_read = 0;
        while extra_size_read < extra_field_length as usize {
            let field_off = input.stream_position().unwrap();
            let field = ExtraField::deserialize(input);

            if let Err(err) = field {
                warn!(
                    "Failed to parsed extra field in {}: {err:?}",
                    header.get_name(),
                );
                input.seek(SeekFrom::Start(field_off)).unwrap();
                break;
            } else {
                let field = field.unwrap();
                extra_size_read += field.size();
                header.extra_field.push(field);
            }
        }
        let mut failed_last_extra_field = false;
        if extra_size_read > extra_field_length as usize {
            //println!("Failed to parse last extra field in {}", header.get_name());
            failed_last_extra_field = true;
            let size = header.extra_field.pop().unwrap().size();
            input.seek(SeekFrom::Current(-(size as i64))).unwrap();
        }
        let mut extra_size_read = input.stream_position().unwrap() - extra_field_off;
        while extra_size_read < extra_field_length as u64 {
            header.malformed_extra_field.push(u8::deserialize(input)?);
            extra_size_read += 1;
        }
        // If it is not padding from zipalign
        if failed_last_extra_field
            && header.malformed_extra_field != vec![0]
            && header.malformed_extra_field != vec![0, 0]
            && header.malformed_extra_field != vec![0, 0, 0]
        {
            warn!("Failed to parse last extra field in {}", header.get_name());
        }
        //input.seek(SeekFrom::Start(end_of_extra_field)).unwrap();
        for field in &mut header.extra_field {
            if let ExtraField::Generic(GenericExtraField {
                id: Zip64ExtraField::ID,
                data,
            }) = field
            {
                let original_size = uncompressed_size == u32::MAX;
                let compressed_size = compressed_size == u32::MAX;
                let offset_header = false;
                let disk_number = false;
                let zip64_filed = Zip64ExtraField::from_generic(
                    &GenericExtraField {
                        id: Zip64ExtraField::ID,
                        data: data.clone(),
                    },
                    original_size,
                    compressed_size,
                    offset_header,
                    disk_number,
                )
                .unwrap();
                *field = ExtraField::Zip64(zip64_filed);
            }
        }
        if (header.general_purpose_flags & general_purpose_flags::MASK_ENCRYPTED != 0)
            && (header.general_purpose_flags & general_purpose_flags::MASK_STRONG_ENCRYPTION != 0)
        {
            header.decryption_header = Some(DecryptionHeader::deserialize(input)?);
        }

        Ok(header)
    }

    fn size(&self) -> usize {
        Self::MIN_SIZE
            + self.file_name.len()
            + self.extra_field.iter().map(|f| f.size()).sum::<usize>()
            + self.malformed_extra_field.len()
            + self
                .decryption_header
                .as_ref()
                .map(|h| h.size())
                .unwrap_or(0)
    }
}

impl LocalFileHeader {
    const SIGNATURE: Signature = Signature(0x04034b50);
    const MIN_SIZE: usize = 4 + 5 * 2 + 3 * 4 + 2 * 2;

    const MASK_UTF8_FILENAME: u16 = 1 << 11;

    pub fn get_name_encoding(&self) -> Encoding {
        if self.general_purpose_flags & Self::MASK_UTF8_FILENAME != 0 {
            Encoding::UTF8
        } else {
            Encoding::CP437
        }
    }

    pub fn get_name(&self) -> String {
        match self.get_name_encoding() {
            Encoding::UTF8 => std::str::from_utf8(&self.file_name).unwrap().into(),
            Encoding::CP437 => cp437::cp437_to_string(&self.file_name),
        }
    }

    pub fn set_name(&mut self, name: &str) {
        let file_name = match cp437::string_to_cp437(name) {
            Ok(name) => {
                self.general_purpose_flags &= !general_purpose_flags::MASK_UTF8_FILENAME;
                name
            }
            Err(Error::NotCp437) => {
                self.general_purpose_flags |= general_purpose_flags::MASK_UTF8_FILENAME;
                name.as_bytes().into()
            }
        };
        self.file_name = file_name;
    }

    pub fn get_uncompressed_size(&self) -> u64 {
        if self.uncompressed_size != u32::MAX {
            self.uncompressed_size as u64
        } else if let Some(ExtraField::Zip64(Zip64ExtraField {
            original_size: Some(original_size),
            ..
        })) = self
            .extra_field
            .iter()
            .find(|f| matches!(f, ExtraField::Zip64(_)))
        {
            *original_size
        } else {
            self.uncompressed_size as u64
        }
    }

    pub fn set_uncompressed_size(&mut self, uncompressed_size: u64) {
        if let Some(ExtraField::Zip64(Zip64ExtraField {
            original_size: Some(original_size),
            ..
        })) = self
            .extra_field
            .iter_mut()
            .find(|f| matches!(f, ExtraField::Zip64(_)))
        {
            *original_size = uncompressed_size;
            self.uncompressed_size = u32::MAX;
        } else if uncompressed_size > u32::MAX as u64 {
            self.extra_field.push(ExtraField::Zip64(Zip64ExtraField {
                original_size: Some(uncompressed_size),
                compressed_size: Some(self.compressed_size as u64),
                disk_number: None,
                offset_header: None,
            }));
            self.uncompressed_size = u32::MAX;
            self.compressed_size = u32::MAX;
        } else {
            self.uncompressed_size = uncompressed_size as u32;
        }
    }

    pub fn get_compressed_size(&self) -> u64 {
        if self.compressed_size != u32::MAX {
            self.compressed_size as u64
        } else if let Some(ExtraField::Zip64(Zip64ExtraField {
            compressed_size: Some(compressed_size),
            ..
        })) = self
            .extra_field
            .iter()
            .find(|f| matches!(f, ExtraField::Zip64(_)))
        {
            *compressed_size
        } else {
            self.compressed_size as u64
        }
    }

    pub fn set_compressed_size(&mut self, compressed_size: u64) {
        if let Some(ExtraField::Zip64(Zip64ExtraField {
            compressed_size: Some(compressed_size_),
            ..
        })) = self
            .extra_field
            .iter_mut()
            .find(|f| matches!(f, ExtraField::Zip64(_)))
        {
            *compressed_size_ = compressed_size;
            self.compressed_size = u32::MAX;
        } else if compressed_size > u32::MAX as u64 {
            self.extra_field.push(ExtraField::Zip64(Zip64ExtraField {
                original_size: Some(self.uncompressed_size as u64),
                compressed_size: Some(compressed_size),
                disk_number: None,
                offset_header: None,
            }));
            self.uncompressed_size = u32::MAX;
            self.compressed_size = u32::MAX;
        } else {
            self.compressed_size = compressed_size as u32;
        }
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DecryptionHeader {
    // pub ivsize: u16,
    pub iv_data: Vec<u8>,
    // pub remaining_size: u32,
    // TODO: parse, not needed for now
    pub other_data: Vec<u8>,
}

impl Serializable for DecryptionHeader {
    fn serialize(&self, output: &mut dyn Write) -> Result<()> {
        (self.iv_data.len() as u16).serialize(output)?;
        for c in &self.iv_data {
            c.serialize(output)?;
        }
        (self.other_data.len() as u32).serialize(output)?;
        for c in &self.other_data {
            c.serialize(output)?;
        }
        Ok(())
    }

    fn deserialize(input: &mut dyn ReadSeek) -> Result<Self> {
        let iv_size = u16::deserialize(input)?;
        let mut iv_data = vec![];
        for _ in 0..iv_size {
            iv_data.push(u8::deserialize(input)?);
        }
        let remaining_size = u32::deserialize(input)?;
        let mut other_data = vec![];
        for _ in 0..remaining_size {
            other_data.push(u8::deserialize(input)?);
        }
        Ok(Self {
            iv_data,
            other_data,
        })
    }

    fn size(&self) -> usize {
        2 + self.iv_data.len() + 4 + self.other_data.len()
    }
}
