"""PPT online preview helpers.

Browsers cannot reliably render .pptx files inline, so the app converts a
deck to a cached PDF and embeds that PDF in the observability preview page.
"""

from __future__ import annotations

import hashlib
import os
import shutil
import subprocess
import tempfile
from dataclasses import dataclass
from pathlib import Path


@dataclass(frozen=True)
class PPTPreviewResult:
    ok: bool
    pdf_path: str | None = None
    cache_hit: bool = False
    converter: str | None = None
    error: str | None = None


@dataclass(frozen=True)
class PPTPreviewCacheInfo:
    pdf_path: str | None = None
    cache_exists: bool = False
    cache_size_kb: float | None = None
    cache_mtime_ts: float | None = None


def find_libreoffice_binary() -> str | None:
    return shutil.which("libreoffice") or shutil.which("soffice")


def _preview_cache_path(pptx_path: Path, cache_dir: Path) -> Path:
    stat = pptx_path.stat()
    cache_key = hashlib.sha256(
        f"{pptx_path.resolve()}:{stat.st_size}:{int(stat.st_mtime)}".encode("utf-8")
    ).hexdigest()[:16]
    safe_stem = "".join(ch if ch.isalnum() or ch in ("-", "_") else "_" for ch in pptx_path.stem)[:96]
    return cache_dir / f"{safe_stem}_{cache_key}.pdf"


def get_ppt_preview_cache_info(
    pptx_path: str | os.PathLike[str],
    *,
    cache_dir: str | os.PathLike[str] | None = None,
) -> PPTPreviewCacheInfo:
    """只讀取預期 PDF 快取狀態，不啟動 LibreOffice 轉檔。"""
    source = Path(pptx_path)
    if not source.is_file() or source.suffix.lower() != ".pptx":
        return PPTPreviewCacheInfo()

    target_dir = Path(cache_dir or os.getenv("PPT_PREVIEW_CACHE_DIR", "/app/data/ppt_previews"))
    target_pdf = _preview_cache_path(source, target_dir)
    if not target_pdf.is_file() or target_pdf.stat().st_size <= 0:
        return PPTPreviewCacheInfo(pdf_path=str(target_pdf), cache_exists=False)

    stat = target_pdf.stat()
    return PPTPreviewCacheInfo(
        pdf_path=str(target_pdf),
        cache_exists=True,
        cache_size_kb=round(stat.st_size / 1024, 1),
        cache_mtime_ts=stat.st_mtime,
    )


def build_ppt_preview(
    pptx_path: str | os.PathLike[str],
    *,
    cache_dir: str | os.PathLike[str] | None = None,
    timeout_sec: int = 90,
) -> PPTPreviewResult:
    source = Path(pptx_path)
    if not source.is_file():
        return PPTPreviewResult(ok=False, error="pptx not found")
    if source.suffix.lower() != ".pptx":
        return PPTPreviewResult(ok=False, error="unsupported file type")

    converter = find_libreoffice_binary()
    if not converter:
        return PPTPreviewResult(
            ok=False,
            error="LibreOffice is not installed in the app container.",
        )

    target_dir = Path(cache_dir or os.getenv("PPT_PREVIEW_CACHE_DIR", "/app/data/ppt_previews"))
    target_dir.mkdir(parents=True, exist_ok=True)
    target_pdf = _preview_cache_path(source, target_dir)
    if target_pdf.is_file() and target_pdf.stat().st_size > 0:
        return PPTPreviewResult(ok=True, pdf_path=str(target_pdf), cache_hit=True, converter=converter)

    with tempfile.TemporaryDirectory(prefix="ppt_preview_") as tmp:
        tmpdir = Path(tmp)
        try:
            proc = subprocess.run(
                [
                    converter,
                    "--headless",
                    "--convert-to",
                    "pdf",
                    "--outdir",
                    str(tmpdir),
                    str(source),
                ],
                capture_output=True,
                timeout=timeout_sec,
                check=False,
            )
        except subprocess.TimeoutExpired:
            return PPTPreviewResult(ok=False, converter=converter, error="LibreOffice conversion timed out.")
        except Exception as exc:
            return PPTPreviewResult(ok=False, converter=converter, error=f"{type(exc).__name__}: {str(exc)[:200]}")

        if proc.returncode != 0:
            stderr = proc.stderr.decode("utf-8", errors="ignore").strip()
            return PPTPreviewResult(
                ok=False,
                converter=converter,
                error=f"LibreOffice conversion failed: {stderr[:240] or proc.returncode}",
            )

        generated = tmpdir / f"{source.stem}.pdf"
        if not generated.is_file():
            candidates = sorted(tmpdir.glob("*.pdf"))
            generated = candidates[0] if candidates else generated
        if not generated.is_file() or generated.stat().st_size <= 0:
            return PPTPreviewResult(ok=False, converter=converter, error="LibreOffice did not produce a PDF.")

        shutil.move(str(generated), str(target_pdf))
        return PPTPreviewResult(ok=True, pdf_path=str(target_pdf), cache_hit=False, converter=converter)
