"""Structural validation for OOXML and ODF packages.

Validates ZIP structure, required parts, and XML well-formedness.
Used by both the post-generation validation hook and test helpers.

See ADR 020, item 7.
"""

from __future__ import annotations

import logging
import zipfile
from io import BytesIO

from lxml import etree

from tokenmoulds.validation.formats import Format
from tokenmoulds.validation.model import ValidationReport

logger = logging.getLogger(__name__)


def validate_package_structure(data: bytes, fmt: str) -> ValidationReport:
    """Run structural checks on a generated package.

    Returns a :class:`ValidationReport` with any issues found.
    """
    report = ValidationReport()

    if len(data) < 4 or data[:4] != b"PK\x03\x04":
        report.add_error("INVALID_ZIP", "Not a valid ZIP (PK header missing)")
        return report

    try:
        with zipfile.ZipFile(BytesIO(data), "r") as zf:
            names = zf.namelist()

            if fmt in Format.ODF_FORMATS:
                _check_odf_parts(names, report)
            else:
                _check_ooxml_parts(names, report)

            _check_xml_wellformedness(zf, names, report)
    except zipfile.BadZipFile:
        report.add_error("BAD_ZIP", "Corrupt ZIP archive")

    return report


def _check_ooxml_parts(names: list[str], report: ValidationReport) -> None:
    if "[Content_Types].xml" not in names:
        report.add_error(
            "MISSING_CONTENT_TYPES",
            "OOXML package missing [Content_Types].xml",
        )
    if "_rels/.rels" not in names:
        report.add_error(
            "MISSING_RELS",
            "OOXML package missing _rels/.rels",
        )


def _check_odf_parts(names: list[str], report: ValidationReport) -> None:
    if "mimetype" not in names:
        report.add_error("MISSING_MIMETYPE", "ODF package missing mimetype")
    if "content.xml" not in names:
        report.add_error("MISSING_CONTENT", "ODF package missing content.xml")


def _check_xml_wellformedness(
    zf: zipfile.ZipFile,
    names: list[str],
    report: ValidationReport,
) -> None:
    for name in names:
        if name.endswith(".xml"):
            try:
                etree.fromstring(zf.read(name))
            except etree.XMLSyntaxError as exc:
                report.add_error(
                    "MALFORMED_XML",
                    f"Malformed XML in {name}: {exc}",
                    location=name,
                )
