"""Read enough text out of a document to tell what it actually is.

Filenames and hand-typed titles have proved unreliable — a file called
"Board Presentation" turned out to be a spreadsheet — so classification looks
inside. Office formats are ZIP containers of XML, so they can be read without
adding a dependency for each one.
"""
import logging
import re
import zipfile

# pypdf narrates every oddity it forgives in these scanned files; the warnings
# are noise here because a partly-read document is still useful for classifying.
logging.getLogger("pypdf").setLevel(logging.ERROR)

TAG = re.compile(r"<[^>]+>")
WS = re.compile(r"\s+")
MAX_CHARS = 6000


def _clean(text):
    return WS.sub(" ", TAG.sub(" ", text or "")).strip()


def _from_pdf(handle):
    try:
        from pypdf import PdfReader
    except ImportError:
        return ""
    try:
        reader = PdfReader(handle)
        out = []
        for page in reader.pages[:3]:
            out.append(page.extract_text() or "")
            if sum(len(p) for p in out) > MAX_CHARS:
                break
        return _clean(" ".join(out))
    except Exception:
        return ""


def _from_zip_xml(handle, members, sharedstrings=None):
    try:
        with zipfile.ZipFile(handle) as archive:
            names = archive.namelist()
            wanted = [n for n in names if any(re.fullmatch(m, n) for m in members)]
            if sharedstrings:
                wanted += [n for n in names if n == sharedstrings]
            out = []
            for name in wanted[:12]:
                try:
                    out.append(archive.read(name).decode("utf-8", "ignore"))
                except Exception:
                    continue
                if sum(len(p) for p in out) > MAX_CHARS * 3:
                    break
            return _clean(" ".join(out))[:MAX_CHARS]
    except Exception:
        return ""


def extract_text(file_field):
    """Up to a few thousand characters of readable text, or "" if unreadable."""
    name = (file_field.name or "").lower()
    try:
        handle = file_field.open("rb")
    except Exception:
        return ""
    try:
        if name.endswith(".pdf"):
            return _from_pdf(handle)[:MAX_CHARS]
        if name.endswith((".docx", ".odt")):
            return _from_zip_xml(handle, [r"word/document\.xml", r"content\.xml"])
        if name.endswith(".xlsx"):
            # Cell labels first (sharedStrings), then sheet and defined names —
            # formula references alone read as noise.
            text = _from_zip_xml(handle, [r"xl/sharedStrings\.xml"])
            if len(text) < 120:
                handle.seek(0)
                text += " " + _from_zip_xml(handle, [r"xl/workbook\.xml"])
            return text[:MAX_CHARS]
        if name.endswith(".pptx"):
            return _from_zip_xml(handle, [r"ppt/slides/slide\d+\.xml"])
        if name.endswith((".txt", ".csv")):
            return _clean(handle.read(MAX_CHARS).decode("utf-8", "ignore"))
        return ""
    finally:
        try:
            handle.close()
        except Exception:
            pass
