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

import pytest
import pikepdf

from src.core.pdf import assemble, parse_page_spec


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

def _make_pdf(path, pages=1, title=None, author=None):
    """Create a minimal PDF with optional docinfo metadata."""
    pdf = pikepdf.Pdf.new()
    for _ in range(pages):
        pdf.pages.append(pikepdf.Page(pikepdf.Dictionary(Type=pikepdf.Name("/Page"))))
    if title:
        pdf.docinfo["/Title"] = title
    if author:
        pdf.docinfo["/Author"] = author
    pdf.save(str(path))
    return str(path)


# ---------------------------------------------------------------------------
# assemble() — page count
# ---------------------------------------------------------------------------

def test_assemble_page_count(tmp_path):
    src = _make_pdf(tmp_path / "a.pdf", pages=3)
    out = str(tmp_path / "out.pdf")
    assemble([{"pdf_path": src, "pages": "1-2"}], out)
    with pikepdf.open(out) as pdf:
        assert len(pdf.pages) == 2


def test_assemble_multiple_sources(tmp_path):
    a = _make_pdf(tmp_path / "a.pdf", pages=2)
    b = _make_pdf(tmp_path / "b.pdf", pages=3)
    out = str(tmp_path / "out.pdf")
    assemble(
        [{"pdf_path": a, "pages": "all"}, {"pdf_path": b, "pages": "1,3"}],
        out,
    )
    with pikepdf.open(out) as pdf:
        assert len(pdf.pages) == 4  # 2 from a + 2 from b


# ---------------------------------------------------------------------------
# assemble() — metadata
# ---------------------------------------------------------------------------

def test_assemble_default_title_is_output_stem(tmp_path):
    """Without explicit metadata, title defaults to the output filename stem."""
    src = _make_pdf(tmp_path / "src.pdf", pages=1)
    out = str(tmp_path / "my_report.pdf")
    assemble([{"pdf_path": src, "pages": "all"}], out)
    with pikepdf.open(out) as pdf:
        assert str(pdf.docinfo["/Title"]) == "my_report"


def test_assemble_default_author_is_system_user(tmp_path):
    """Without explicit author, falls back to system username."""
    src = _make_pdf(tmp_path / "src.pdf", pages=1)
    out = str(tmp_path / "out.pdf")
    with __import__("unittest.mock", fromlist=["patch"]).patch(
        "getpass.getuser", return_value="testuser"
    ):
        assemble([{"pdf_path": src, "pages": "all"}], out)
    with pikepdf.open(out) as pdf:
        assert str(pdf.docinfo["/Author"]) == "testuser"


def test_assemble_source_author_takes_priority_over_username(tmp_path):
    """If first source has an author, it is used instead of system username."""
    src = _make_pdf(tmp_path / "src.pdf", pages=1, author="Alice")
    out = str(tmp_path / "out.pdf")
    with __import__("unittest.mock", fromlist=["patch"]).patch(
        "getpass.getuser", return_value="testuser"
    ):
        assemble([{"pdf_path": src, "pages": "all"}], out)
    with pikepdf.open(out) as pdf:
        assert str(pdf.docinfo["/Author"]) == "Alice"


def test_assemble_no_default_subject(tmp_path):
    """Subject is never set unless explicitly provided."""
    src = _make_pdf(tmp_path / "src.pdf", pages=1)
    out = str(tmp_path / "out.pdf")
    assemble([{"pdf_path": src, "pages": "all"}], out)
    with pikepdf.open(out) as pdf:
        assert "/Subject" not in pdf.docinfo


def test_assemble_explicit_metadata_overrides_default(tmp_path):
    """Explicit metadata dict overrides the default stem title and source docinfo."""
    src = _make_pdf(tmp_path / "src.pdf", pages=1, title="Source Title", author="Alice")
    out = str(tmp_path / "out.pdf")
    assemble(
        [{"pdf_path": src, "pages": "all"}],
        out,
        metadata={"title": "Custom Title", "author": "Bob", "subject": "Q4"},
    )
    with pikepdf.open(out) as pdf:
        assert str(pdf.docinfo["/Title"]) == "Custom Title"
        assert str(pdf.docinfo["/Author"]) == "Bob"
        assert str(pdf.docinfo["/Subject"]) == "Q4"


def test_assemble_preserves_author_from_source(tmp_path):
    """Author from first source is preserved when not explicitly overridden."""
    src = _make_pdf(tmp_path / "src.pdf", pages=2, title="My Report", author="Alice")
    out = str(tmp_path / "out.pdf")
    assemble([{"pdf_path": src, "pages": "all"}], out)
    with pikepdf.open(out) as pdf:
        assert str(pdf.docinfo["/Author"]) == "Alice"


def test_assemble_metadata_from_first_source_only(tmp_path):
    """Author comes from first source, not second."""
    a = _make_pdf(tmp_path / "a.pdf", pages=1, title="First Doc", author="Alice")
    b = _make_pdf(tmp_path / "b.pdf", pages=1, title="Second Doc", author="Bob")
    out = str(tmp_path / "out.pdf")
    assemble(
        [{"pdf_path": a, "pages": "all"}, {"pdf_path": b, "pages": "all"}],
        out,
    )
    with pikepdf.open(out) as pdf:
        assert str(pdf.docinfo["/Author"]) == "Alice"


def test_assemble_stamps_producer(tmp_path):
    src = _make_pdf(tmp_path / "src.pdf", pages=1)
    out = str(tmp_path / "out.pdf")
    assemble([{"pdf_path": src, "pages": "all"}], out)
    with pikepdf.open(out) as pdf:
        assert str(pdf.docinfo["/Producer"]) == "PageFuse"


def test_assemble_no_metadata_source(tmp_path):
    """Assembly should still stamp Producer even when source has no docinfo."""
    src = _make_pdf(tmp_path / "src.pdf", pages=1)
    out = str(tmp_path / "out.pdf")
    assemble([{"pdf_path": src, "pages": "all"}], out)
    with pikepdf.open(out) as pdf:
        assert str(pdf.docinfo["/Producer"]) == "PageFuse"


def test_parse_all():
    assert parse_page_spec("all", 5) == [0, 1, 2, 3, 4]


def test_parse_single_page():
    assert parse_page_spec("3", 5) == [2]


def test_parse_int_spec():
    assert parse_page_spec(2, 5) == [1]


def test_parse_range():
    assert parse_page_spec("1-3", 5) == [0, 1, 2]


def test_parse_list():
    assert parse_page_spec("1,3,5", 5) == [0, 2, 4]


def test_parse_mixed():
    assert parse_page_spec("1-2,4", 5) == [0, 1, 3]


def test_parse_complex():
    assert parse_page_spec("1-3,5,7-9", 10) == [0, 1, 2, 4, 6, 7, 8]


def test_parse_page_out_of_range():
    with pytest.raises(ValueError):
        parse_page_spec("10", 5)


def test_parse_range_out_of_range():
    with pytest.raises(ValueError):
        parse_page_spec("3-8", 5)


def test_parse_first_page():
    assert parse_page_spec("1", 3) == [0]


def test_parse_last_page():
    assert parse_page_spec("3", 3) == [2]
