"""
Document to PDF converter — vendored from pdfit (github.com/spiderdev27/pdfit).

Converts DOCX, images, HTML, MD, TXT, CSV, etc. to PDF so they can be sent to Gemini
(which only supports PDF + images natively). Uses Pillow, pypandoc, weasyprint, LibreOffice.
"""

from __future__ import annotations

import csv
import os
import subprocess
import tempfile
from pathlib import Path
from typing import Optional

# MIME to extension mapping for format detection
MIME_TO_EXT = {
    "application/pdf": ".pdf",
    "image/png": ".png",
    "image/jpeg": ".jpg",
    "image/jpg": ".jpg",
    "image/webp": ".webp",
    "image/gif": ".gif",
    "image/bmp": ".bmp",
    "image/tiff": ".tiff",
    "image/tif": ".tif",
    "application/vnd.openxmlformats-officedocument.wordprocessingml.document": ".docx",
    "application/msword": ".doc",
    "application/vnd.openxmlformats-officedocument.presentationml.presentation": ".pptx",
    "application/vnd.ms-powerpoint": ".ppt",
    "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": ".xlsx",
    "application/vnd.ms-excel": ".xls",
    "application/vnd.oasis.opendocument.text": ".odt",
    "application/vnd.oasis.opendocument.spreadsheet": ".ods",
    "application/vnd.oasis.opendocument.presentation": ".odp",
    "text/html": ".html",
    "text/markdown": ".md",
    "text/plain": ".txt",
    "text/csv": ".csv",
    "application/rtf": ".rtf",
    "application/epub+zip": ".epub",
}

IMAGE_EXTENSIONS = {".png", ".jpg", ".jpeg", ".gif", ".bmp", ".tiff", ".tif", ".webp"}
GEMINI_DIRECT_EXTENSIONS = {".pdf", ".png", ".jpg", ".jpeg", ".webp", ".gif"}
GEMINI_DIRECT_MIMES = {
    "application/pdf",
    "image/png",
    "image/jpeg",
    "image/jpg",
    "image/webp",
    "image/gif",
}


def _escape(s: str) -> str:
    return s.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;").replace('"', "&quot;")


def ext_from_mime(mime: str) -> str:
    """Get file extension from MIME type (e.g. 'image/png' -> '.png')."""
    m = (mime or "").strip().lower().split(";")[0]
    return MIME_TO_EXT.get(m, "")


def convert_to_pdf(
    input_bytes: bytes,
    ext: Optional[str] = None,
    mime_type: Optional[str] = None,
) -> bytes:
    """
    Convert document bytes to PDF.

    Args:
        input_bytes: Raw file bytes
        ext: File extension (e.g. .docx). Inferred from mime_type if not provided.
        mime_type: MIME type for format detection when ext is None.

    Returns:
        PDF bytes.

    Raises:
        ValueError: If format is unsupported or conversion fails.
    """
    ext = ext or ext_from_mime(mime_type or "")
    if not ext:
        raise ValueError("Could not determine format. Provide ext or mime_type.")
    ext = ext.lower() if ext.startswith(".") else f".{ext.lower()}"

    if ext == ".pdf":
        return input_bytes

    with tempfile.TemporaryDirectory() as tmp:
        inp = Path(tmp) / f"input{ext}"
        out = Path(tmp) / "output.pdf"
        inp.write_bytes(input_bytes)

        if ext in IMAGE_EXTENSIONS:
            _convert_image(inp, out)
        elif ext in [".html", ".htm"]:
            _convert_html(inp, out)
        elif ext in [".md", ".markdown"]:
            _convert_markdown(inp, out)
        elif ext == ".txt":
            _convert_txt(inp, out)
        elif ext == ".csv":
            _convert_csv(inp, out)
        elif ext in [".docx", ".doc", ".rtf"]:
            _convert_document(inp, out, ext)
        elif ext in [".pptx", ".ppt", ".xlsx", ".xls", ".odt", ".ods", ".odp"]:
            _convert_office(inp, out)
        elif ext == ".epub":
            _convert_epub(inp, out)
        else:
            if _try_pypandoc(inp, out) or _try_libreoffice(inp, out):
                pass
            else:
                raise ValueError(f"Unsupported format: {ext}")

        if not out.exists():
            raise ValueError(f"Conversion produced no output for {ext}")
        return out.read_bytes()


def needs_conversion(mime_type: str, ext: Optional[str] = None) -> bool:
    """True if the format must be converted to PDF before sending to Gemini."""
    m = (mime_type or "").strip().lower().split(";")[0]
    if m in GEMINI_DIRECT_MIMES:
        return False
    e = (ext or ext_from_mime(mime_type or "")).lower()
    if not e.startswith("."):
        e = f".{e}"
    return e not in GEMINI_DIRECT_EXTENSIONS


def _convert_image(input_path: Path, output_path: Path) -> None:
    try:
        from PIL import Image
    except ImportError:
        raise ValueError("Pillow is required for image conversion. pip install Pillow")
    img = Image.open(input_path)
    if img.mode in ("RGBA", "P"):
        img = img.convert("RGB")
    img.save(output_path, "PDF", resolution=100.0)


def _convert_html(input_path: Path, output_path: Path) -> None:
    try:
        from weasyprint import HTML
    except ImportError:
        raise ValueError("WeasyPrint required for HTML. pip install weasyprint")
    HTML(filename=str(input_path)).write_pdf(output_path)


def _convert_markdown(input_path: Path, output_path: Path) -> None:
    try:
        import pypandoc
        from weasyprint import HTML
    except ImportError:
        raise ValueError("pypandoc and weasyprint required for Markdown")
    md = input_path.read_text(encoding="utf-8", errors="replace")
    html = pypandoc.convert_text(md, "html", format="md")
    with tempfile.NamedTemporaryFile(mode="w", suffix=".html", delete=False, encoding="utf-8") as f:
        f.write(html)
        tmp = f.name
    try:
        HTML(filename=tmp).write_pdf(output_path)
    finally:
        os.unlink(tmp)


def _convert_txt(input_path: Path, output_path: Path) -> None:
    try:
        from weasyprint import HTML
    except ImportError:
        raise ValueError("WeasyPrint required for TXT")
    content = input_path.read_text(encoding="utf-8", errors="replace")
    html = f"<pre>{_escape(content)}</pre>"
    with tempfile.NamedTemporaryFile(mode="w", suffix=".html", delete=False, encoding="utf-8") as f:
        f.write(html)
        tmp = f.name
    try:
        HTML(filename=tmp).write_pdf(output_path)
    finally:
        os.unlink(tmp)


def _convert_csv(input_path: Path, output_path: Path) -> None:
    try:
        from weasyprint import HTML
    except ImportError:
        raise ValueError("WeasyPrint required for CSV")
    rows = list(csv.reader(open(input_path, encoding="utf-8", errors="replace")))
    trs = "".join(
        f"<tr>{''.join(f'<td>{_escape(cell)}</td>' for cell in row)}</tr>" for row in rows
    )
    html = f"<table><tbody>{trs}</tbody></table>"
    with tempfile.NamedTemporaryFile(mode="w", suffix=".html", delete=False, encoding="utf-8") as f:
        f.write(html)
        tmp = f.name
    try:
        HTML(filename=tmp).write_pdf(output_path)
    finally:
        os.unlink(tmp)


def _convert_document(input_path: Path, output_path: Path, ext: str) -> None:
    if not _try_pypandoc(input_path, output_path) and not _try_libreoffice(input_path, output_path):
        raise ValueError(
            f"Could not convert {ext}. Install pypandoc/weasyprint or LibreOffice."
        )


def _convert_office(input_path: Path, output_path: Path) -> None:
    if not _try_libreoffice(input_path, output_path):
        raise ValueError(
            "LibreOffice required for Office formats. Install: sudo apt install libreoffice"
        )


def _convert_epub(input_path: Path, output_path: Path) -> None:
    if _try_pypandoc(input_path, output_path):
        return
    try:
        import ebooklib
        from ebooklib import epub
        from bs4 import BeautifulSoup
        from weasyprint import HTML
    except ImportError:
        raise ValueError("For EPUB: install pypandoc+weasyprint, or ebooklib+beautifulsoup4+weasyprint")
    book = epub.read_epub(str(input_path))
    html_parts = []
    for item in book.get_items():
        try:
            if item and getattr(item, "get_type", lambda: None)() == ebooklib.ITEM_DOCUMENT:
                content = getattr(item, "get_content", lambda: None)()
                if content:
                    soup = BeautifulSoup(content, "html.parser")
                    html_parts.append(str(soup))
        except (AttributeError, TypeError):
            pass
    if not html_parts:
        raise ValueError("Could not extract content from EPUB")
    html = "<html><body>" + "".join(html_parts) + "</body></html>"
    with tempfile.NamedTemporaryFile(mode="w", suffix=".html", delete=False, encoding="utf-8") as f:
        f.write(html)
        tmp = f.name
    try:
        HTML(filename=tmp).write_pdf(output_path)
    finally:
        os.unlink(tmp)


def _try_pypandoc(input_path: Path, output_path: Path) -> bool:
    try:
        import pypandoc
        pypandoc.convert_file(
            str(input_path),
            "pdf",
            outputfile=str(output_path),
            extra_args=["--pdf-engine=weasyprint"],
        )
        return output_path.exists()
    except Exception:
        return False


def _try_libreoffice(input_path: Path, output_path: Path) -> bool:
    for cmd in ["libreoffice", "soffice"]:
        try:
            out_dir = str(output_path.parent)
            result = subprocess.run(
                [cmd, "--headless", "--convert-to", "pdf", str(input_path), "--outdir", out_dir],
                capture_output=True,
                text=True,
                timeout=60,
            )
            if result.returncode == 0:
                expected = output_path.parent / (input_path.stem + ".pdf")
                if expected.exists():
                    if expected != output_path:
                        expected.rename(output_path)
                    return True
        except (FileNotFoundError, subprocess.TimeoutExpired):
            continue
    return False
