import logging
from unittest.mock import MagicMock, patch

import pikepdf
import pytest
from pikepdf import Array, Pdf

# --- Import functions to test ---
from pdftl.utils.transform import (
    _rotate_pair,
    transform_destination_coordinates,
    transform_pdf,
)

# --- Tests for _rotate_pair ---


@pytest.mark.parametrize(
    "angle, x_in, y_in, w, h, x_out, y_out",
    [
        (0, 10, 20, 100, 200, 10, 20),  # No rotation
        (90, 10, 20, 100, 200, 180, 10),  # 90 deg: (h-y, x)
        (180, 10, 20, 100, 200, 90, 180),  # 180 deg: (w-x, h-y)
        (270, 10, 20, 100, 200, 20, 90),  # 270 deg: (y, w-x)
    ],
)
def test_rotate_pair_valid_angles(angle, x_in, y_in, w, h, x_out, y_out):
    """Tests the coordinate transformation for 0, 90, 180, 270 degrees."""
    result = _rotate_pair(angle, x_in, y_in, w, h)
    assert result == (x_out, y_out)


def test_rotate_pair_unsupported_angle(caplog):
    """Tests that an unsupported angle logs a warning and returns original coords."""
    # caplog is a pytest fixture that captures log output
    caplog.set_level(logging.WARNING)

    result = _rotate_pair(45, 10, 20, 100, 200)
    assert result == (10, 20)  # Should return original coords
    assert "Unsupported rotation angle 45°" in caplog.text


# --- Tests for transform_destination_coordinates ---

TEST_BOX = [0, 0, 100, 200]  # width=100, height=200


@pytest.mark.parametrize(
    "coords_in, box, angle, scale, coords_out",
    [
        # No op
        ([10, 20, 0], TEST_BOX, 0, 1.0, [10.0, 20.0, 0.0]),
        # Rotation only (90 deg)
        ([10, 20, 0], TEST_BOX, 90, 1.0, [180.0, 10.0, 0.0]),
        # Rotation only (180 deg)
        ([10, 20, 0], TEST_BOX, 180, 1.0, [90.0, 180.0, 0.0]),
        # Rotation only (270 deg)
        ([10, 20, 0], TEST_BOX, 270, 1.0, [20.0, 90.0, 0.0]),
        # Scaling only
        ([10, 20, 0], TEST_BOX, 0, 2.0, [20.0, 40.0, 0.0]),
        # Rotation (90) AND Scaling (2.0)
        # (h-y, x) -> (180, 10) -> (180*2, 10*2) -> (360, 20)
        ([10, 20, 0], TEST_BOX, 90, 2.0, [360.0, 20.0, 0.0]),
        # Rotation (180) AND Scaling (0.5)
        # (w-x, h-y) -> (90, 180) -> (90*0.5, 180*0.5) -> (45, 90)
        ([10, 20, 0], TEST_BOX, 180, 0.5, [45.0, 90.0, 0.0]),
        # Handle None in coordinates (x=None, y=20) -> (h-y, x) -> (180, None) -> scaled (360, None)
        ([None, 20, 0], TEST_BOX, 90, 2.0, [360.0, None, 0.0]),
        # Handle None in coordinates (x=10, y=None) -> (h-y, x) -> (None, 10) -> scaled (None, 20)
        ([10, None, 0], TEST_BOX, 90, 2.0, [None, 20.0, 0.0]),
        # Handle extra coords (like zoom)
        ([10, 20, 0.5, 500], TEST_BOX, 0, 1.0, [10.0, 20.0, 0.5, 500.0]),
        # Handle extra coords with rotation and scaling
        ([10, 20, 0.5, 500], TEST_BOX, 270, 3.0, [60.0, 270.0, 0.5, 500.0]),
    ],
)
def test_transform_destination_coordinates(coords_in, box, angle, scale, coords_out):
    """
    Tests various combinations of rotation and scaling on /XYZ coordinates.
    """
    # Use pikepdf.Array to match one of the type hints
    page_box_array = Array(box)
    result = transform_destination_coordinates(coords_in, page_box_array, angle, scale)
    assert result == coords_out


# --- Tests for transform_pdf ---


@pytest.fixture
def mock_pdf():
    """Creates a mock pikepdf.Pdf object with 4 mock pages."""
    # We use a real Pdf object so len() works, but mock its pages
    pdf = Pdf.new()
    pdf.add_blank_page()
    pdf.add_blank_page()
    pdf.add_blank_page()
    pdf.add_blank_page()

    # Replace the real pages with mocks so we can check calls
    mock_pages = [
        MagicMock(spec=pikepdf.Page),
        MagicMock(spec=pikepdf.Page),
        MagicMock(spec=pikepdf.Page),
        MagicMock(spec=pikepdf.Page),
    ]
    # We patch .pages to return our list of mocks
    with patch.object(Pdf, "pages", new=mock_pages):
        yield pdf


@patch("pdftl.utils.transform.apply_scaling")
# We MUST patch the entry point transform_pdf uses
@patch("pdftl.utils.transform.expand_specs_to_pages")
def test_transform_pdf(
    mock_expand,
    mock_apply_scaling,
    mock_pdf,
):
    """
    Tests the orchestration logic of transform_pdf by mocking the expansion.
    """
    # --- Arrange ---
    spec_str = "1,3"

    # 1. Setup mock pages
    # We use a list so identities (page1, page3) remain stable
    mock_pages = [MagicMock(name=f"page{i}") for i in range(4)]
    mock_pdf.pages = mock_pages

    # 2. Define mock PageTransform objects
    # These mimic the 'PageTransform' objects the debugger showed us
    m1 = MagicMock(name="transform1")
    m1.index = 0
    m1.rotation = (90, True)
    m1.scale = 2.0

    m3 = MagicMock(name="transform3")
    m3.index = 2
    m3.rotation = (90, True)
    m3.scale = 2.0

    # The loop in transform_pdf: for page_transform in expand_specs_to_pages(...):
    mock_expand.return_value = [m1, m3]

    # --- Act ---
    returned_pdf = transform_pdf(mock_pdf, [spec_str])

    # --- Assert ---
    assert returned_pdf is mock_pdf

    # Verify expand was called with the right context
    mock_expand.assert_called_once_with([spec_str], opened_pdfs=[mock_pdf])

    # Check transformations on PAGE 1 (index 0)
    mock_apply_scaling.assert_any_call(mock_pages[0], 2.0)
    mock_pages[0].rotate.assert_called_with(90, relative=True)

    # Check transformations on PAGE 3 (index 2)
    mock_apply_scaling.assert_any_call(mock_pages[2], 2.0)
    mock_pages[2].rotate.assert_called_with(90, relative=True)

    # Check that pages index 1 and 3 (pages 2 and 4) were NOT touched
    mock_pages[1].rotate.assert_not_called()
    mock_pages[3].rotate.assert_not_called()


import pytest

from pdftl.exceptions import InvalidArgumentError


@pytest.fixture
def dummy_pdf():
    # Create a simple 5-page PDF (default rotation 0)
    pdf = pikepdf.new()
    for _ in range(5):
        pdf.add_blank_page()
    return pdf


def test_transform_even_odd_qualifiers(dummy_pdf):
    # Tests lines 46 (even) and 48 (odd)
    # Syntax: "evenright" -> Rotate even pages 90°
    # Syntax: "odddown"   -> Rotate odd pages 180°
    # (Assuming concatenation is allowed like 1-5right)
    specs = ["evenright", "odddown"]
    transform_pdf(dummy_pdf, specs)

    # Check Page 1 (Odd) -> 180
    assert dummy_pdf.pages[0].get("/Rotate") == 180
    # Check Page 2 (Even) -> 90
    assert dummy_pdf.pages[1].get("/Rotate") == 90


def test_transform_omissions(dummy_pdf):
    # Tests line 53 (omissions)
    # Syntax: "1-5~3right" -> Range 1-5, omit 3, rotate 90°
    specs = ["1-5~3right"]
    transform_pdf(dummy_pdf, specs)

    # Page 1 (Included) -> 90
    assert dummy_pdf.pages[0].get("/Rotate") == 90
    # Page 3 (Omitted) -> Should be None (0 default)
    assert dummy_pdf.pages[2].get("/Rotate") is None
    # Page 5 (Included) -> 90
    assert dummy_pdf.pages[4].get("/Rotate") == 90


def test_transform_page_out_of_bounds(dummy_pdf):
    # Tests lines 62-63 (IndexError -> InvalidArgumentError)
    # Page 10 on a 5-page PDF, rotate right
    specs = ["10right"]

    with pytest.raises(InvalidArgumentError) as exc:
        transform_pdf(dummy_pdf, specs)

    assert "only 5 pages" in str(exc.value)


import pytest


def test_transform_pdf_index_error():
    """
    Covers transform.py lines 40-42:
    Checks that InvalidArgumentError is raised if specs refer to a non-existent page.
    """
    # Mock a PDF with 1 page
    mock_pdf = MagicMock()
    mock_pdf.pages = [MagicMock()]

    # Spec asks for page index 5 (which is page 6)
    # We mock expand_specs_to_pages to return a spec for index 5
    mock_spec = MagicMock()
    mock_spec.index = 5
    mock_spec.rotation = (0, False)
    mock_spec.scale = 1.0

    with patch("pdftl.utils.transform.expand_specs_to_pages", return_value=[mock_spec]):
        with pytest.raises(InvalidArgumentError, match="Page 6 does not exist"):
            transform_pdf(mock_pdf, ["ignored_spec"])


def test_rotate_pair_invalid_angle(caplog):
    """
    Covers transform.py lines 79-82:
    Checks that a warning is logged if an unsupported rotation angle (e.g., 45) is used.
    """
    with caplog.at_level(logging.WARNING):
        x, y = _rotate_pair(45, 10, 10, 100, 100)

    assert "Unsupported rotation angle 45" in caplog.text
    # Fallback behavior: returns original coords
    assert x == 10
    assert y == 10


import pytest

from pdftl.utils.page_specs import PageTransform


def test_transform_pdf_invalid_angle(dummy_pdf):
    """Hits transform.py:50 by providing a non-90-degree rotation."""
    dummy_pdf.add_blank_page()

    # We create a manual PageTransform object to bypass the parser
    # and hit the validation logic inside the loop directly.
    # Rotation is (angle, relative)
    bad_transform = PageTransform(dummy_pdf, index=0, rotation=(45, False), scale=1.0)

    # We mock expand_specs_to_pages to yield our specific bad transform
    with pytest.raises(InvalidArgumentError, match="multiple of 90 degrees"):
        with patch("pdftl.utils.transform.expand_specs_to_pages", return_value=[bad_transform]):
            transform_pdf(dummy_pdf, ["ignored_spec"])
