"""
Чтение DOCX (Word 2007+, формат Office Open XML / ZIP).

Поддерживает извлечение текста через `python-docx` (основной текст, таблицы,
колонтитулы), резервный разбор ZIP + XML, а также парсер вложений `<w:altChunk>`
/ MHTML (`word/afchunk.mht`) для нестандартных документов.
"""
from __future__ import annotations

import email
from email.policy import default
import re
import zipfile
from pathlib import Path
from typing import List, Optional

from app.readers.base import BaseReader, ReaderOutcome
from app.readers.doc_formats import read_rtf

_WORD_NS = "{http://schemas.openxmlformats.org/wordprocessingml/2006/main}"
_TEXT_TAG = f"{_WORD_NS}t"
_BREAK_TAGS = {f"{_WORD_NS}p", f"{_WORD_NS}br", f"{_WORD_NS}tab", f"{_WORD_NS}cr"}


class DocxReader(BaseReader):
    name = "docx"

    def extract_text(self, path: Path) -> ReaderOutcome:
        if not self.is_file_accessible(path):
            return ReaderOutcome(locked=True, error="Файл заблокирован")

        text = self._read_with_python_docx(path)
        if text:
            return ReaderOutcome(text=text, method_used="python-docx")

        text = self._read_manual_xml(path)
        if text:
            return ReaderOutcome(text=text, method_used="manual-xml-fallback")

        text = self._read_altchunk(path)
        if text:
            return ReaderOutcome(text=text, method_used="altchunk-mht-parser")

        return ReaderOutcome(error="Не удалось извлечь текст из DOCX (файл повреждён?)")

    # ------------------------------------------------------------------ #
    @staticmethod
    def _read_with_python_docx(path: Path) -> Optional[str]:
        try:
            import docx  # python-docx
        except ImportError:
            return None
        try:
            document = docx.Document(str(path))
            parts: List[str] = []

            for para in document.paragraphs:
                if para.text:
                    parts.append(para.text)

            for table in document.tables:
                for row in table.rows:
                    for cell in row.cells:
                        if cell.text:
                            parts.append(cell.text)

            for section in document.sections:
                for hf in (section.header, section.footer):
                    try:
                        for para in hf.paragraphs:
                            if para.text:
                                parts.append(para.text)
                    except Exception:
                        pass

            result = "\n".join(parts).strip()
            return result if len(result) >= 1 else None
        except Exception:
            return None

    # ------------------------------------------------------------------ #
    @staticmethod
    def _read_manual_xml(path: Path) -> Optional[str]:
        """Резервный разбор: склеиваем текстовые узлы <w:t> в порядке документа,
        вставляя перевод строки только на границах параграфов/табов/разрывов —
        текст внутри абзаца не разрывается пробелом между runs.

        Ищет все word/document*.xml (некоторые редакторы создают
        word/document2.xml при разбиении содержимого)."""
        try:
            import xml.etree.ElementTree as ET

            with zipfile.ZipFile(path) as zf:
                names = sorted(
                    n for n in zf.namelist()
                    if n.lower().replace("\\", "/").startswith("word/document") and n.lower().endswith(".xml")
                )
                if not names:
                    return None
                xml_bytes = zf.read(names[0])

            root = ET.fromstring(xml_bytes)
            parts: List[str] = []
            for elem in root.iter():
                if elem.tag == _TEXT_TAG:
                    if elem.text:
                        parts.append(elem.text)
                elif elem.tag in _BREAK_TAGS:
                    parts.append("\n")

            result = "".join(parts)
            result = re.sub(r"[ \t]+", " ", result)
            result = re.sub(r"\n{3,}", "\n\n", result).strip()
            return result if len(result) >= 1 else None
        except Exception:
            return None

    # ------------------------------------------------------------------ #
    @staticmethod
    def _read_altchunk(path: Path) -> Optional[str]:
        """Парсер для документов, сохранённых как MHTML-вложение (altChunk)."""
        try:
            with zipfile.ZipFile(path) as zf:
                mht_names = [
                    n for n in zf.namelist()
                    if n.lower().startswith("word/") and n.lower().endswith((".mht", ".mhtml"))
                ]
                if not mht_names:
                    return None
                data = zf.read(mht_names[0])
        except Exception:
            return None

        try:
            msg = email.message_from_bytes(data, policy=default)
            parts = []
            for part in msg.walk():
                if part.is_multipart():
                    continue
                content_type = part.get_content_type()
                payload = part.get_payload(decode=True)
                if not payload:
                    continue

                if content_type == "text/html":
                    charset = part.get_content_charset() or "utf-8"
                    html_text = payload.decode(charset, errors="replace")
                    # Простая очистка HTML
                    cleaned = re.sub(r"<style[^>]*>[\s\S]*?</style>", " ", html_text, flags=re.IGNORECASE)
                    cleaned = re.sub(r"<script[^>]*>[\s\S]*?</script>", " ", cleaned, flags=re.IGNORECASE)
                    cleaned = re.sub(r"<[^>]+>", " ", cleaned)
                    parts.append(cleaned)
                elif content_type == "text/plain":
                    charset = part.get_content_charset() or "utf-8"
                    parts.append(payload.decode(charset, errors="replace"))
                elif content_type == "application/rtf":
                    rtf_text = read_rtf(payload)
                    if rtf_text:
                        parts.append(rtf_text)

            result = " ".join(parts)
            result = re.sub(r"\s+", " ", result).strip()
            return result if len(result) >= 5 else None
        except Exception:
            return None
