use std::collections::HashMap;
use std::io::{Read, Seek};

use crate::Error;

pub const MAIN_DOCUMENT_PART: &str = "word/document.xml";

pub type FileRegistry = HashMap<String, Vec<u8>>;

pub fn unpack_docx(input: impl Read + Seek) -> Result<FileRegistry, Error> {
    let mut archive = zip::ZipArchive::new(input)?;
    let mut files = HashMap::with_capacity(archive.len());

    for index in 0..archive.len() {
        let mut entry = archive.by_index(index)?;

        if entry.is_dir() {
            continue;
        }

        let mut bytes = Vec::new();
        entry.read_to_end(&mut bytes)?;
        files.insert(entry.name().to_string(), bytes);
    }

    Ok(files)
}

pub fn get_part<'a>(files: &'a FileRegistry, path: &str) -> Option<&'a [u8]> {
    files.get(path).map(Vec::as_slice)
}

pub fn require_part<'a>(files: &'a FileRegistry, path: &str) -> Result<&'a [u8], Error> {
    get_part(files, path).ok_or_else(|| Error::MissingPart(path.to_string()))
}

#[cfg(test)]
mod tests {
    use std::io::{Cursor, Write};

    use zip::write::SimpleFileOptions;

    use super::{get_part, require_part, unpack_docx, MAIN_DOCUMENT_PART};
    use crate::Error;

    #[test]
    fn unpack_docx_reads_all_files() {
        let bytes = build_zip(&[
            (
                MAIN_DOCUMENT_PART,
                br#"<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"/>"#,
            ),
            ("docProps/core.xml", br#"<cp:coreProperties/>"#),
        ]);

        let files = unpack_docx(Cursor::new(bytes)).unwrap();

        assert_eq!(files.len(), 2);
        assert!(get_part(&files, MAIN_DOCUMENT_PART).is_some());
        assert!(get_part(&files, "docProps/core.xml").is_some());
    }

    #[test]
    fn require_part_returns_missing_part_error() {
        let bytes = build_zip(&[("docProps/core.xml", br#"<cp:coreProperties/>"#)]);
        let files = unpack_docx(Cursor::new(bytes)).unwrap();

        let error = require_part(&files, MAIN_DOCUMENT_PART).unwrap_err();

        assert!(matches!(error, Error::MissingPart(path) if path == MAIN_DOCUMENT_PART));
    }

    fn build_zip(files: &[(&str, &[u8])]) -> Vec<u8> {
        let mut buffer = Cursor::new(Vec::new());
        let mut writer = zip::ZipWriter::new(&mut buffer);
        let options = SimpleFileOptions::default();

        for (path, contents) in files {
            writer.start_file(path, options).unwrap();
            writer.write_all(contents).unwrap();
        }

        writer.finish().unwrap();
        buffer.into_inner()
    }
}
