"""Test the pypdf.annotations submodule."""

from io import BytesIO
from pathlib import Path

import pytest

from pypdf import PdfReader, PdfWriter
from pypdf.annotations import (
    AnnotationDictionary,
    Ellipse,
    FreeText,
    Highlight,
    Line,
    Link,
    Polygon,
    PolyLine,
    Popup,
    Rectangle,
    Text,
)
from pypdf.errors import PdfReadError
from pypdf.generic import ArrayObject, FloatObject, NumberObject

from . import RESOURCE_ROOT, get_data_from_url


def test_ellipse(pdf_file_path):
    # Arrange
    pdf_path = RESOURCE_ROOT / "crazyones.pdf"
    reader = PdfReader(pdf_path)
    page = reader.pages[0]
    writer = PdfWriter()
    writer.add_page(page)

    # Act
    ellipse_annotation = Ellipse(
        rect=(50, 550, 500, 650),
        interior_color="ff0000",
    )
    writer.add_annotation(0, ellipse_annotation)

    # Assert: You need to inspect the file manually
    with open(pdf_file_path, "wb") as fp:
        writer.write(fp)


def test_text(pdf_file_path):
    # Arrange
    pdf_path = RESOURCE_ROOT / "outline-without-title.pdf"
    reader = PdfReader(pdf_path)
    page = reader.pages[0]
    writer = PdfWriter()
    writer.add_page(page)

    # Act
    text_annotation = Text(
        text="Hello World\nThis is the second line!",
        rect=(50, 550, 500, 650),
        open=True,
    )
    writer.add_annotation(0, text_annotation)

    # Assert: You need to inspect the file manually
    with open(pdf_file_path, "wb") as fp:
        writer.write(fp)


def test_free_text(pdf_file_path):
    # Arrange
    pdf_path = RESOURCE_ROOT / "crazyones.pdf"
    reader = PdfReader(pdf_path)
    page = reader.pages[0]
    writer = PdfWriter()
    writer.add_page(page)

    # Act
    free_text_annotation = FreeText(
        text="Hello World - bold and italic\nThis is the second line!",
        rect=(50, 550, 200, 650),
        font="Arial",
        bold=True,
        italic=True,
        font_size="20pt",
        font_color="00ff00",
        border_color=None,
        background_color=None,
    )
    writer.add_annotation(0, free_text_annotation)

    free_text_annotation = FreeText(
        text="Another free text annotation (not bold, not italic)",
        rect=(500, 550, 200, 650),
        font="Arial",
        bold=False,
        italic=False,
        font_size="20pt",
        font_color="00ff00",
        border_color="0000ff",
        background_color="cdcdcd",
    )
    writer.add_annotation(0, free_text_annotation)

    # Assert: You need to inspect the file manually
    with open(pdf_file_path, "wb") as fp:
        writer.write(fp)


def test_free_text__font_specifier():
    free_text_annotation = FreeText(
        text="Hello World",
        rect=(0, 0, 0, 0),
    )
    assert free_text_annotation["/DS"] == "font: normal normal 14pt Helvetica;text-align:left;color:#000000"
    free_text_annotation = FreeText(
        text="Hello World",
        rect=(50, 550, 200, 650),
        font="Arial",
        bold=True,
        italic=True,
        font_size="20pt",
        font_color="00ff00",
        border_color=None,
        background_color=None,
    )
    assert free_text_annotation["/DS"] == "font: italic bold 20pt Arial;text-align:left;color:#00ff00"


def test_annotation_dictionary():
    a = AnnotationDictionary()
    a.flags = 123
    assert a.flags == 123


def test_polygon(pdf_file_path):
    # Arrange
    pdf_path = RESOURCE_ROOT / "crazyones.pdf"
    reader = PdfReader(pdf_path)
    page = reader.pages[0]
    writer = PdfWriter()
    writer.add_page(page)

    with pytest.raises(ValueError):
        Polygon(
            vertices=[],
        )

    annotation = Polygon(
        vertices=[(50, 550), (200, 650), (70, 750), (50, 700)],
    )
    writer.add_annotation(0, annotation)

    # Assert: You need to inspect the file manually
    with open(pdf_file_path, "wb") as fp:
        writer.write(fp)


def test_polyline(pdf_file_path):
    # Arrange
    pdf_path = RESOURCE_ROOT / "crazyones.pdf"
    reader = PdfReader(pdf_path)
    page = reader.pages[0]
    writer = PdfWriter()
    writer.add_page(page)

    with pytest.raises(
            ValueError,
            match=r"A polyline needs at least 1 vertex with two coordinates",
    ):
        PolyLine(
            vertices=[],
        )

    annotation = PolyLine(
        vertices=[(50, 550), (200, 650), (70, 750), (50, 700)],
    )
    writer.add_annotation(0, annotation)

    # Assert: You need to inspect the file manually
    with open(pdf_file_path, "wb") as fp:
        writer.write(fp)


def test_line(pdf_file_path):
    # Arrange
    pdf_path = RESOURCE_ROOT / "crazyones.pdf"
    reader = PdfReader(pdf_path)
    page = reader.pages[0]
    writer = PdfWriter()
    writer.add_page(page)

    # Act
    line_annotation = Line(
        text="Hello World\nLine2",
        rect=(50, 550, 200, 650),
        p1=(50, 550),
        p2=(200, 650),
    )
    writer.add_annotation(0, line_annotation)

    # Assert: You need to inspect the file manually
    with open(pdf_file_path, "wb") as fp:
        writer.write(fp)


def test_rectangle(pdf_file_path):
    # Arrange
    pdf_path = RESOURCE_ROOT / "crazyones.pdf"
    reader = PdfReader(pdf_path)
    page = reader.pages[0]
    writer = PdfWriter()
    writer.add_page(page)

    # Act
    square_annotation = Rectangle(
        rect=(50, 550, 200, 650), interior_color="ff0000"
    )
    writer.add_annotation(0, square_annotation)

    square_annotation = Rectangle(rect=(40, 400, 150, 450))
    writer.add_annotation(0, square_annotation)

    # Assert: You need to inspect the file manually
    with open(pdf_file_path, "wb") as fp:
        writer.write(fp)


def test_highlight(pdf_file_path):
    # Arrange
    pdf_path = RESOURCE_ROOT / "crazyones.pdf"
    reader = PdfReader(pdf_path)
    page = reader.pages[0]
    writer = PdfWriter()
    writer.add_page(page)

    # Act
    highlight_annotation = Highlight(
        rect=(95.79332, 704.31777, 138.55779, 724.6855),
        highlight_color="ff0000",
        quad_points=ArrayObject(
            [
                FloatObject(100.060779),
                FloatObject(723.55398),
                FloatObject(134.29033),
                FloatObject(723.55398),
                FloatObject(100.060779),
                FloatObject(705.4493),
                FloatObject(134.29033),
                FloatObject(705.4493),
            ]
        ),
        printing=False,
    )
    writer.add_annotation(0, highlight_annotation)
    for annot in writer.pages[0]["/Annots"]:
        obj = annot.get_object()
        subtype = obj["/Subtype"]
        if subtype == "/Highlight":
            assert "/F" not in obj or obj["/F"] == NumberObject(0)

    writer.add_page(page)
    # Act
    highlight_annotation = Highlight(
        rect=(95.79332, 704.31777, 138.55779, 724.6855),
        highlight_color="ff0000",
        quad_points=ArrayObject(
            [
                FloatObject(100.060779),
                FloatObject(723.55398),
                FloatObject(134.29033),
                FloatObject(723.55398),
                FloatObject(100.060779),
                FloatObject(705.4493),
                FloatObject(134.29033),
                FloatObject(705.4493),
            ]
        ),
        printing=True,
    )
    writer.add_annotation(1, highlight_annotation)
    for annot in writer.pages[1]["/Annots"]:
        obj = annot.get_object()
        subtype = obj["/Subtype"]
        if subtype == "/Highlight":
            assert obj["/F"] == NumberObject(4)

    # Assert: You need to inspect the file manually
    with open(pdf_file_path, "wb") as fp:
        writer.write(fp)


def test_link(pdf_file_path):
    # Arrange
    pdf_path = RESOURCE_ROOT / "outline-without-title.pdf"
    reader = PdfReader(pdf_path)
    page = reader.pages[0]
    writer = PdfWriter()
    writer.add_page(page)

    # Act
    # Part 1: Too many args
    with pytest.raises(ValueError):
        Link(
            rect=(50, 550, 200, 650),
            url="https://martin-thoma.com/",
            target_page_index=3,
        )

    # Part 2: Too few args
    with pytest.raises(ValueError):
        Link(
            rect=(50, 550, 200, 650),
        )

    # Part 3: External Link
    link_annotation = Link(
        rect=(50, 50, 100, 100),
        url="https://martin-thoma.com/",
        border=[1, 0, 6, [3, 2]],
    )
    writer.add_annotation(0, link_annotation)

    # Part 4: Internal Link
    link_annotation = Link(
        rect=(100, 100, 300, 200),
        target_page_index=1,
        border=[50, 10, 4],
    )
    writer.add_annotation(0, link_annotation)

    for page in reader.pages[1:]:
        writer.add_page(page)

    # Assert: You need to inspect the file manually
    with open(pdf_file_path, "wb") as fp:
        writer.write(fp)


def test_popup(caplog):
    # Arrange
    pdf_path = RESOURCE_ROOT / "outline-without-title.pdf"
    reader = PdfReader(pdf_path)
    page = reader.pages[0]
    writer = PdfWriter()
    writer.add_page(page)

    # Act
    text_annotation = Text(
        title_bar="hello world",
        text="Hello World\nThis is the second line!",
        rect=(50, 550, 200, 650),
        open=True,
    )
    ta = writer.add_annotation(0, text_annotation)
    popup_annotation = Popup(
        rect=(50, 550, 200, 650),
        open=True,
        parent=ta,  # prefer to use for evolutivity
    )
    writer.add_annotation(writer.pages[0], popup_annotation)

    Popup(
        rect=(50, 550, 200, 650),
        open=True,
        parent=True,  # broken parameter  # type: ignore
    )
    assert "Unregistered Parent object : No Parent field set" in caplog.text

    target = "annotated-pdf-popup.pdf"
    writer.write(target)
    Path(target).unlink()  # comment this out for manual inspection


def test_markup_annotation_in_reply_to():
    """Test that a reply annotation gets /IRT, /RT, and /NM after a write/read cycle."""
    writer = PdfWriter(clone_from=RESOURCE_ROOT / "crazyones.pdf")

    parent = Text(
        text="Parent comment",
        rect=(50, 550, 200, 650),
        open=True,
    )
    parent_ref = writer.add_annotation(0, parent)

    reply = Text(
        text="Reply to parent",
        rect=(50, 550, 200, 650),
        in_reply_to=parent_ref,
    )
    writer.add_annotation(0, reply)

    assert "/IRT" in reply
    assert reply["/IRT"].get_object() is parent_ref
    assert reply["/RT"] == "/R"
    assert "/NM" in reply

    assert "/NM" not in parent_ref

    buf = BytesIO()
    writer.write(buf)

    reader2 = PdfReader(buf)
    annots = reader2.pages[0]["/Annots"]
    assert len(annots) == 2

    reply_obj = annots[1].get_object()
    assert reply_obj["/IRT"].get_object()["/Contents"] == "Parent comment"
    assert reply_obj["/NM"] == reply["/NM"]


def test_markup_annotation_in_reply_to_group_type():
    """Test that a grouped annotation sets /RT to /Group."""
    writer = PdfWriter(clone_from=RESOURCE_ROOT / "crazyones.pdf")

    parent = Text(
        text="Parent",
        rect=(50, 550, 200, 650),
    )
    parent_ref = writer.add_annotation(0, parent)

    grouped = Text(
        text="Grouped with parent",
        rect=(50, 550, 200, 650),
        in_reply_to=parent_ref,
        reply_type="Group",
    )
    writer.add_annotation(0, grouped)

    assert grouped["/RT"] == "/Group"
    assert "/IRT" in grouped
    assert "/NM" in grouped


def test_markup_annotation_name_without_reply():
    """Test that annotation_name without in_reply_to raises ValueError."""
    with pytest.raises(ValueError, match="annotation_name is only supported when in_reply_to is set"):
        Text(
            text="Named but not a reply",
            rect=(50, 550, 200, 650),
            annotation_name="my-unique-name",
        )


def test_markup_annotation_reply_type_without_reply():
    """Test that non-default reply_type without in_reply_to raises ValueError."""
    with pytest.raises(ValueError, match="reply_type is only meaningful when in_reply_to is set"):
        Text(
            text="Grouped but not a reply",
            rect=(50, 550, 200, 650),
            reply_type="Group",
        )


def test_markup_annotation_in_reply_to_custom_name():
    """Test explicit annotation_name with in_reply_to."""
    writer = PdfWriter()
    writer.add_blank_page(width=200, height=200)

    parent = Text(text="Parent", rect=(0, 0, 100, 100))
    parent_ref = writer.add_annotation(0, parent)

    reply = Text(
        text="Reply",
        rect=(0, 0, 100, 100),
        in_reply_to=parent_ref,
        annotation_name="custom-reply-name",
    )
    writer.add_annotation(0, reply)

    assert reply["/NM"] == "custom-reply-name"
    assert "/IRT" in reply


def test_markup_annotation_in_reply_to_unregistered():
    """Test that an unregistered parent raises ValueError."""
    unregistered = Text(text="Not added to writer", rect=(0, 0, 100, 100))
    with pytest.raises(ValueError, match="in_reply_to must be a registered annotation"):
        Text(
            text="Reply",
            rect=(0, 0, 100, 100),
            in_reply_to=unregistered,
        )


def test_markup_annotation_in_reply_to_indirect_object():
    """Test passing an IndirectObject directly as in_reply_to."""
    writer = PdfWriter()
    writer.add_blank_page(width=200, height=200)

    parent = Text(text="Parent", rect=(0, 0, 100, 100))
    parent_ref = writer.add_annotation(0, parent)
    indirect_ref = parent_ref.indirect_reference

    reply = Text(
        text="Reply via IndirectObject",
        rect=(0, 0, 100, 100),
        in_reply_to=indirect_ref,
    )
    writer.add_annotation(0, reply)

    assert "/IRT" in reply
    assert reply["/RT"] == "/R"
    assert "/NM" in reply

    buf = BytesIO()
    writer.write(buf)

    reader = PdfReader(buf)
    annots = reader.pages[0]["/Annots"]
    assert len(annots) == 2
    reply_obj = annots[1].get_object()
    assert reply_obj["/IRT"].get_object()["/Contents"] == "Parent"
    assert reply_obj["/NM"] == reply["/NM"]


@pytest.mark.enable_socket
def test_outline_action_without_d_lenient():
    reader = PdfReader(BytesIO(get_data_from_url(name="iss3268.pdf")))
    assert len(reader.outline) == 2


@pytest.mark.enable_socket
def test_outline_action_without_d_strict(pdf_file_path):
    reader = PdfReader(BytesIO(get_data_from_url(name="iss3268.pdf")))
    reader.strict = True
    with pytest.raises(PdfReadError) as e:
        assert len(reader.outline) == 2
    assert "Outline Action Missing /D" in str(e)
