from __future__ import annotations

import os
import shutil
import subprocess
import tempfile
from pathlib import Path

from tkpptx import (
    BackendUnavailableError,
    ExportError,
    ExportOptions,
    UnsupportedOperationError,
    resolve_page_range,
)


class LibreOfficeBackend:
    name = "libreoffice"

    def __init__(self) -> None:
        self.executable = self._find_executable()

    @staticmethod
    def _find_executable() -> str | None:
        configured = os.environ.get("LIBREOFFICE_EXE")
        if configured and Path(configured).exists():
            return str(Path(configured).resolve())

        candidates = ["soffice", "libreoffice"]
        if os.name == "nt":
            candidates = [
                "soffice.exe",
                r"C:\Program Files\LibreOffice\program\soffice.exe",
                r"C:\Program Files (x86)\LibreOffice\program\soffice.exe",
            ]

        for candidate in candidates:
            if Path(candidate).is_absolute() and Path(candidate).exists():
                return str(Path(candidate))
            found = shutil.which(candidate)
            if found:
                return found
        return None

    def is_available(self) -> bool:
        return self.executable is not None

    def _run(self, command: list[str]) -> subprocess.CompletedProcess[str]:
        if not self.executable:
            raise BackendUnavailableError(
                "LibreOffice was not found. Add 'soffice' to PATH or set "
                "LIBREOFFICE_EXE to its full path."
            )

        completed = subprocess.run(
            [self.executable, *command],
            text=True,
            capture_output=True,
            check=False,
        )
        if completed.returncode != 0:
            details = "\n".join(
                part for part in (completed.stdout, completed.stderr) if part
            ).strip()
            raise ExportError(
                f"LibreOffice failed with exit code "
                f"{completed.returncode}.\n{details}"
            )
        return completed

    def _convert_to_pdf(self, input_path: Path, output_dir: Path) -> Path:
        output_dir.mkdir(parents=True, exist_ok=True)
        expected = output_dir / f"{input_path.stem}.pdf"
        if expected.exists():
            expected.unlink()

        self._run(
            [
                "--headless",
                "--convert-to",
                "pdf:impress_pdf_Export",
                "--outdir",
                str(output_dir),
                str(input_path),
            ]
        )

        if expected.exists():
            return expected

        pdfs = sorted(output_dir.glob("*.pdf"))
        if len(pdfs) == 1:
            return pdfs[0]

        raise ExportError(
            f"LibreOffice did not create the expected PDF in {output_dir}."
        )

    def export_pdf(
        self, input_path: Path, output_path: Path, options: ExportOptions
    ) -> Path:
        with tempfile.TemporaryDirectory(prefix="tkpptx_lo_") as tmp:
            generated = self._convert_to_pdf(input_path, Path(tmp))
            shutil.copy2(generated, output_path)

        if not output_path.exists():
            raise ExportError(
                f"LibreOffice PDF export did not create: {output_path}"
            )
        return output_path

    def export_images(
        self, input_path: Path, output_dir: Path, options: ExportOptions
    ) -> list[Path]:
        try:
            import fitz
        except ImportError as exc:
            raise BackendUnavailableError(
                "LibreOffice image export requires PyMuPDF. "
                "Install it with: pip install pymupdf"
            ) from exc

        with tempfile.TemporaryDirectory(prefix="tkpptx_lo_") as tmp:
            generated_pdf = self._convert_to_pdf(input_path, Path(tmp))
            document = fitz.open(generated_pdf)
            try:
                slide_count = document.page_count
                start = options.start
                end = options.end if options.end is not None else slide_count

                if start < 1 or end < start or end > slide_count:
                    raise ValueError(
                        f"Invalid slide range {start}-{end}; "
                        f"presentation has {slide_count} slides."
                    )

                outputs: list[Path] = []
                zoom = max(0.1, options.dpi / 72.0)
                matrix = fitz.Matrix(zoom, zoom)

                for page_number in range(start, end + 1):
                    target = output_dir / f"slide_{page_number:03d}.png"
                    if target.exists() and options.overwrite:
                        target.unlink()

                    page = document.load_page(page_number - 1)
                    pixmap = page.get_pixmap(matrix=matrix, alpha=False)
                    pixmap.save(target)
                    outputs.append(target)
            finally:
                document.close()

        return outputs

    def export_html(
        self, input_path: Path, output_path: Path, options: ExportOptions
    ) -> Path:
        raise UnsupportedOperationError(
            "The LibreOffice backend does not provide stable presentation HTML "
            "export. Use the OfficeCLI backend."
        )


def get_backend() -> LibreOfficeBackend:
    return LibreOfficeBackend()
