#!/usr/bin/env python3
"""
Tool to pack a directory into a .docx, .pptx, or .xlsx file with XML formatting undone.

Example usage:
    python pack.py <input_directory> <office_file> [--force]
"""

import argparse
import os
import shutil
import subprocess
import sys
import tempfile
import zipfile
from pathlib import Path

try:
    import defusedxml.minidom as secure_minidom
except ImportError:  # pragma: no cover - fallback for minimal environments
    import xml.dom.minidom as secure_minidom


def resolve_soffice_binary():
    candidates = ["soffice", "soffice.com", "soffice.exe"]
    if sys.platform == "win32":
        candidates = ["soffice.com", "soffice.exe", "soffice"]

    for name in candidates:
        resolved = shutil.which(name)
        if resolved:
            return resolved

    if sys.platform == "win32":
        for env_key in ("ProgramFiles", "ProgramFiles(x86)"):
            value = os.environ.get(env_key)
            if not value:
                continue
            root = Path(value)
            for rel in ("LibreOffice/program/soffice.com", "LibreOffice/program/soffice.exe"):
                candidate = root / rel
                if candidate.exists():
                    return str(candidate)
    return None


def main():
    parser = argparse.ArgumentParser(description="Pack a directory into an Office file")
    parser.add_argument("input_directory", help="Unpacked Office document directory")
    parser.add_argument("output_file", help="Output Office file (.docx/.pptx/.xlsx)")
    parser.add_argument("--force", action="store_true", help="Skip validation")
    args = parser.parse_args()

    try:
        success = pack_document(
            args.input_directory, args.output_file, validate=not args.force
        )

        if args.force:
            print("Warning: Skipped validation, file may be corrupt", file=sys.stderr)
        elif not success:
            print("Contents would produce a corrupt file.", file=sys.stderr)
            print("Please validate XML before repacking.", file=sys.stderr)
            print("Use --force to skip validation and pack anyway.", file=sys.stderr)
            sys.exit(1)

    except ValueError as exc:
        sys.exit(f"Error: {exc}")


def pack_document(input_dir, output_file, validate=False):
    input_dir = Path(input_dir)
    output_file = Path(output_file)

    if not input_dir.is_dir():
        raise ValueError(f"{input_dir} is not a directory")
    if output_file.suffix.lower() not in {".docx", ".pptx", ".xlsx"}:
        raise ValueError(f"{output_file} must be a .docx, .pptx, or .xlsx file")

    with tempfile.TemporaryDirectory() as temp_dir:
        temp_content_dir = Path(temp_dir) / "content"
        shutil.copytree(input_dir, temp_content_dir)

        for pattern in ["*.xml", "*.rels"]:
            for xml_file in temp_content_dir.rglob(pattern):
                condense_xml(xml_file)

        output_file.parent.mkdir(parents=True, exist_ok=True)
        with zipfile.ZipFile(output_file, "w", zipfile.ZIP_DEFLATED) as archive:
            for file_path in temp_content_dir.rglob("*"):
                if file_path.is_file():
                    archive.write(file_path, file_path.relative_to(temp_content_dir))

        if validate and not validate_document(output_file):
            if output_file.exists():
                output_file.unlink()
            return False

    return True


def validate_document(doc_path):
    match doc_path.suffix.lower():
        case ".docx":
            filter_name = "html:HTML"
        case ".pptx":
            filter_name = "html:impress_html_Export"
        case ".xlsx":
            filter_name = "html:HTML (StarCalc)"
        case _:
            return False

    soffice_binary = resolve_soffice_binary()
    if not soffice_binary:
        print("Warning: soffice not found. Skipping validation.", file=sys.stderr)
        return True

    with tempfile.TemporaryDirectory() as temp_dir:
        try:
            result = subprocess.run(
                [
                    soffice_binary,
                    "--headless",
                    "--convert-to",
                    filter_name,
                    "--outdir",
                    temp_dir,
                    str(doc_path),
                ],
                capture_output=True,
                timeout=30,
                text=True,
                check=False,
            )
            if not (Path(temp_dir) / f"{doc_path.stem}.html").exists():
                error_msg = result.stderr.strip() or result.stdout.strip() or "Document validation failed"
                print(f"Validation error: {error_msg}", file=sys.stderr)
                return False
            return True
        except subprocess.TimeoutExpired:
            print("Validation error: Timeout during conversion", file=sys.stderr)
            return False
        except Exception as exc:
            print(f"Validation error: {exc}", file=sys.stderr)
            return False


def condense_xml(xml_file):
    with open(xml_file, "r", encoding="utf-8") as file_obj:
        dom = secure_minidom.parse(file_obj)

    for element in dom.getElementsByTagName("*"):
        if element.tagName.endswith(":t"):
            continue

        for child in list(element.childNodes):
            is_blank_text = (
                child.nodeType == child.TEXT_NODE
                and child.nodeValue
                and child.nodeValue.strip() == ""
            )
            if is_blank_text or child.nodeType == child.COMMENT_NODE:
                element.removeChild(child)

    with open(xml_file, "wb") as file_obj:
        file_obj.write(dom.toxml(encoding="UTF-8"))


if __name__ == "__main__":
    main()
