"""Shared OOXML deep validation via ``openxml-audit``.

Provides a small adapter that converts ``openxml-audit`` results into the
TokenMoulds ``ValidationReport`` model. The adapter is optional: when
``openxml-audit`` is unavailable or the format is unsupported, callers get a
successful skipped outcome rather than an import-time failure.
"""

from __future__ import annotations

from dataclasses import dataclass
from pathlib import Path
from tempfile import NamedTemporaryFile
from typing import Any

from tokenmoulds.validation.formats import Format
from tokenmoulds.validation.model import Severity, ValidationIssue, ValidationReport

_FORMAT_ALIASES = {
    "ppt": Format.POWERPOINT,
    "pptx": Format.POWERPOINT,
    "potx": Format.POWERPOINT_TEMPLATE,
    "word": Format.WORD,
    "docx": Format.WORD,
    "dotx": Format.WORD_TEMPLATE,
    "xl": Format.EXCEL,
    "xlsx": Format.EXCEL,
    "xltx": Format.EXCEL_TEMPLATE,
}

_SUPPORTED_FORMATS = frozenset(
    {
        Format.WORD,
        Format.WORD_TEMPLATE,
        Format.POWERPOINT,
        Format.POWERPOINT_TEMPLATE,
        Format.EXCEL,
        Format.EXCEL_TEMPLATE,
    }
)

_SUFFIX_BY_FORMAT = {
    Format.WORD: ".docx",
    Format.WORD_TEMPLATE: ".dotx",
    Format.POWERPOINT: ".pptx",
    Format.POWERPOINT_TEMPLATE: ".potx",
    Format.EXCEL: ".xlsx",
    Format.EXCEL_TEMPLATE: ".xltx",
}


@dataclass(frozen=True)
class OOXMLDeepValidationOutcome:
    """Result of running optional deep OOXML validation."""

    report: ValidationReport
    executed: bool
    skipped_reason: str | None = None


def normalize_ooxml_format(fmt: str) -> str:
    """Normalise external format aliases to TokenMoulds format constants."""
    return _FORMAT_ALIASES.get(fmt, fmt)


def supports_ooxml_deep_validation(fmt: str) -> bool:
    """Return ``True`` when ``openxml-audit`` can validate the format."""
    return normalize_ooxml_format(fmt) in _SUPPORTED_FORMATS


def validate_ooxml_file(
    path: str | Path,
    fmt: str,
    *,
    include_binary: bool = False,
) -> OOXMLDeepValidationOutcome:
    """Validate an OOXML archive on disk with ``openxml-audit``."""
    normalized_fmt = normalize_ooxml_format(fmt)
    if not supports_ooxml_deep_validation(normalized_fmt):
        return _skipped_outcome(
            f"openxml-audit does not support deep validation for format: {fmt}"
        )

    imports = _import_openxml_audit()
    if imports is None:
        return _skipped_outcome("openxml-audit is not installed")

    file_format, validator_cls, binary_error_type = imports
    report = ValidationReport()

    try:
        validator = validator_cls(file_format=file_format.OFFICE_2019)
        result = validator.validate(str(path))
    except Exception as exc:  # pragma: no cover - catastrophic library failure
        report.add_error(
            "OPENXML_AUDIT_EXCEPTION",
            f"openxml-audit validation failed: {exc}",
        )
        return OOXMLDeepValidationOutcome(report=report, executed=True)

    for error in result.errors:
        if not include_binary and _is_binary_error(error, binary_error_type):
            continue

        report.add_issue(
            ValidationIssue(
                code=_error_code(error),
                message=getattr(error, "description", str(error)),
                severity=_map_severity(getattr(error, "severity", None)),
                location=_build_location(error),
            )
        )

    return OOXMLDeepValidationOutcome(report=report, executed=True)


def validate_ooxml_bytes(
    data: bytes,
    fmt: str,
    *,
    include_binary: bool = False,
) -> OOXMLDeepValidationOutcome:
    """Validate OOXML package bytes with ``openxml-audit``."""
    normalized_fmt = normalize_ooxml_format(fmt)
    if not supports_ooxml_deep_validation(normalized_fmt):
        return _skipped_outcome(
            f"openxml-audit does not support deep validation for format: {fmt}"
        )

    suffix = _SUFFIX_BY_FORMAT.get(normalized_fmt, ".zip")
    with NamedTemporaryFile(suffix=suffix, delete=False) as tmp:
        tmp.write(data)
        tmp_path = Path(tmp.name)

    try:
        return validate_ooxml_file(
            tmp_path,
            normalized_fmt,
            include_binary=include_binary,
        )
    finally:
        tmp_path.unlink(missing_ok=True)


def _import_openxml_audit() -> tuple[Any, Any, Any] | None:
    try:
        from openxml_audit import FileFormat, OpenXmlValidator
        from openxml_audit.errors import ValidationErrorType
    except ImportError:
        return None
    return FileFormat, OpenXmlValidator, ValidationErrorType


def _skipped_outcome(reason: str) -> OOXMLDeepValidationOutcome:
    report = ValidationReport()
    report.add_info("OPENXML_AUDIT_SKIPPED", reason)
    return OOXMLDeepValidationOutcome(
        report=report,
        executed=False,
        skipped_reason=reason,
    )


def _map_severity(severity: Any) -> Severity:
    severity_name = getattr(severity, "name", "").upper()
    if severity_name == "WARNING":
        return Severity.WARNING
    if severity_name == "INFO":
        return Severity.INFO
    return Severity.ERROR


def _is_binary_error(error: Any, binary_error_type: Any) -> bool:
    error_type = getattr(error, "error_type", None)
    if error_type is None:
        return False

    if error_type == binary_error_type.BINARY:
        return True

    error_type_name = getattr(error_type, "name", "").upper()
    return error_type_name == "BINARY"


def _error_code(error: Any) -> str:
    error_id = getattr(error, "id", "")
    if error_id:
        return str(error_id)

    error_type = getattr(error, "error_type", None)
    error_type_name = getattr(error_type, "name", "")
    if error_type_name:
        return f"OPENXML_AUDIT_{error_type_name}"

    return "OPENXML_AUDIT_ERROR"


def _build_location(error: Any) -> str | None:
    part_uri = getattr(error, "part_uri", "") or None
    xpath = getattr(error, "path", "") or None
    if xpath:
        return f"{part_uri}:{xpath}" if part_uri else str(xpath)
    return part_uri


__all__ = [
    "OOXMLDeepValidationOutcome",
    "normalize_ooxml_format",
    "supports_ooxml_deep_validation",
    "validate_ooxml_bytes",
    "validate_ooxml_file",
]
