"""Tests for src/core/convert.py."""

import io
import subprocess
import zipfile
from pathlib import Path
from unittest.mock import MagicMock, patch

import pikepdf
import pytest
from PIL import Image

from src.core.convert import (
    IMAGE_FORMATS,
    IMAGE_OUTPUT_FORMATS,
    LIBREOFFICE_FORMATS,
    LIBREOFFICE_OUTPUT_FORMATS,
    OUTPUT_FORMATS,
    image_to_pdf,
    libreoffice_to_pdf,
    pdf_to_format,
)


# ---------------------------------------------------------------------------
# Format set sanity checks
# ---------------------------------------------------------------------------

def test_libreoffice_formats_contains_expected():
    assert {".docx", ".doc", ".pptx", ".ppt", ".odt", ".odp"} <= LIBREOFFICE_FORMATS


def test_image_formats_contains_expected():
    assert {".png", ".jpg", ".jpeg", ".tiff"} <= IMAGE_FORMATS


def test_no_overlap_between_format_sets():
    assert LIBREOFFICE_FORMATS.isdisjoint(IMAGE_FORMATS)


def test_output_formats_contains_expected():
    assert {".pdf", ".docx", ".html", ".png", ".jpg"} <= OUTPUT_FORMATS


def test_libreoffice_output_formats_contains_expected():
    assert {".docx", ".odt", ".odp"} <= LIBREOFFICE_OUTPUT_FORMATS


def test_image_output_formats_contains_expected():
    assert {".png", ".jpg", ".jpeg", ".tiff"} <= IMAGE_OUTPUT_FORMATS


def test_output_format_sets_no_overlap():
    assert LIBREOFFICE_OUTPUT_FORMATS.isdisjoint(IMAGE_OUTPUT_FORMATS)


# ---------------------------------------------------------------------------
# libreoffice_to_pdf — error paths
# ---------------------------------------------------------------------------

def test_libreoffice_not_installed(tmp_path):
    with patch("subprocess.run", side_effect=FileNotFoundError):
        with pytest.raises(RuntimeError, match="LibreOffice is not installed"):
            libreoffice_to_pdf(str(tmp_path / "doc.docx"), str(tmp_path))


def test_libreoffice_nonzero_exit(tmp_path):
    mock = MagicMock()
    mock.returncode = 1
    mock.stderr = "conversion error"
    with patch("subprocess.run", return_value=mock):
        with pytest.raises(RuntimeError, match="LibreOffice failed to convert"):
            libreoffice_to_pdf(str(tmp_path / "doc.docx"), str(tmp_path))


def test_libreoffice_missing_output(tmp_path):
    mock = MagicMock()
    mock.returncode = 0
    with patch("subprocess.run", return_value=mock):
        with pytest.raises(FileNotFoundError, match="output not found"):
            libreoffice_to_pdf(str(tmp_path / "doc.docx"), str(tmp_path))


# ---------------------------------------------------------------------------
# libreoffice_to_pdf — integration (requires LibreOffice)
# ---------------------------------------------------------------------------

@pytest.mark.skipif(
    subprocess.run(["which", "libreoffice"], capture_output=True).returncode != 0,
    reason="LibreOffice not available",
)
def test_libreoffice_converts_docx(tmp_path):
    from docx import Document
    doc = Document()
    doc.add_paragraph("Test document.")
    src = str(tmp_path / "doc.docx")
    doc.save(src)
    pdf_path = libreoffice_to_pdf(src, str(tmp_path))
    assert Path(pdf_path).exists()
    assert Path(pdf_path).stat().st_size > 0


# ---------------------------------------------------------------------------
# image_to_pdf
# ---------------------------------------------------------------------------

def _make_image(path, mode="RGB", size=(200, 150), color=(100, 150, 200)):
    img = Image.new(mode, size, color)
    img.save(str(path))
    return str(path)


def test_image_to_pdf_png(tmp_path):
    src = _make_image(tmp_path / "img.png")
    pdf_path = image_to_pdf(src, str(tmp_path))
    assert Path(pdf_path).exists()
    assert pdf_path.endswith(".pdf")


def test_image_to_pdf_jpeg(tmp_path):
    src = _make_image(tmp_path / "img.jpg")
    pdf_path = image_to_pdf(src, str(tmp_path))
    assert Path(pdf_path).exists()


def test_image_to_pdf_rgba(tmp_path):
    """RGBA image should be embedded with a transparency mask without error."""
    src = _make_image(tmp_path / "img.png", mode="RGBA", color=(100, 150, 200, 128))
    pdf_path = image_to_pdf(src, str(tmp_path))
    assert Path(pdf_path).exists()


def test_image_to_pdf_produces_valid_pdf(tmp_path):
    src = _make_image(tmp_path / "img.png")
    pdf_path = image_to_pdf(src, str(tmp_path))
    with pikepdf.open(pdf_path) as pdf:
        assert len(pdf.pages) == 1


def test_no_collision_same_stem_different_dirs(tmp_path):
    """Two images with the same filename from different dirs get separate output PDFs."""
    dir_a = tmp_path / "a"
    dir_b = tmp_path / "b"
    dir_a.mkdir()
    dir_b.mkdir()
    _make_image(dir_a / "photo.png", color=(255, 0, 0))
    _make_image(dir_b / "photo.png", color=(0, 0, 255))

    out_dir = tmp_path / "out"
    out_dir.mkdir()
    pdf_a = image_to_pdf(str(dir_a / "photo.png"), str(out_dir))
    pdf_b = image_to_pdf(str(dir_b / "photo.png"), str(out_dir))

    assert pdf_a != pdf_b
    assert Path(pdf_a).exists()
    assert Path(pdf_b).exists()


# ---------------------------------------------------------------------------
# pdf_to_format
# ---------------------------------------------------------------------------

def _make_pdf(path, n_pages=1):
    """Create a minimal PDF with n_pages blank pages."""
    with pikepdf.new() as pdf:
        for _ in range(n_pages):
            page = pikepdf.Page(pikepdf.Dictionary(
                Type=pikepdf.Name("/Page"),
                MediaBox=[0, 0, 612, 792],
            ))
            pdf.pages.append(page)
        pdf.save(str(path))
    return str(path)


def test_pdf_to_format_unsupported_extension(tmp_path):
    pdf = _make_pdf(tmp_path / "src.pdf")
    with pytest.raises(ValueError, match="Unsupported output format"):
        pdf_to_format(pdf, str(tmp_path / "out.xyz"))


def test_pdf_to_format_single_page_png(tmp_path):
    """Single-page PDF → PNG image file."""
    pdf = _make_pdf(tmp_path / "src.pdf", n_pages=1)
    out = str(tmp_path / "out.png")
    result = pdf_to_format(pdf, out)
    assert result == out
    assert Path(out).exists()
    img = Image.open(out)
    assert img.width > 0


def test_pdf_to_format_multi_page_png_makes_zip(tmp_path):
    """Multi-page PDF → ZIP of numbered PNGs."""
    pdf = _make_pdf(tmp_path / "src.pdf", n_pages=2)
    out = str(tmp_path / "out.png")
    result = pdf_to_format(pdf, out)
    zip_path = str(tmp_path / "out.png.zip")
    assert result == zip_path
    assert Path(zip_path).exists()
    with zipfile.ZipFile(zip_path) as zf:
        names = zf.namelist()
    assert len(names) == 2
    assert "out_001.png" in names
    assert "out_002.png" in names


def test_pdf_to_format_libreoffice_output(tmp_path):
    """PDF → .docx via LibreOffice (mocked)."""
    pdf = _make_pdf(tmp_path / "src.pdf")
    out = str(tmp_path / "out.docx")

    mock_result = MagicMock()
    mock_result.returncode = 0

    def fake_run(cmd, **kwargs):
        # Simulate LibreOffice creating the output file
        import shutil
        out_dir = cmd[cmd.index("--outdir") + 1]
        stem = Path(cmd[-1]).stem
        fake_out = Path(out_dir) / f"{stem}.docx"
        fake_out.write_bytes(b"fake docx")
        return mock_result

    with patch("subprocess.run", side_effect=fake_run):
        result = pdf_to_format(pdf, out)

    assert result == out
    assert Path(out).exists()
