import shutil
from uuid import uuid4

import pytest

from hotpdf import HotPdf
from hotpdf.data.classes import ElementDimension as El
from hotpdf.data.classes import HotCharacter
from hotpdf.encodings.decoder import Decoder
from hotpdf.encodings.types import EncodingTypes
from hotpdf.sparse_matrix import SparseMatrix
from hotpdf.utils import filter_adjacent_coords, intersect, to_text


@pytest.fixture()
def test_uuid():
    return uuid4()


def xml_copy_file_name(xml_file_name: str):
    shutil.copy(xml_file_name, f"{xml_file_name}_copy.xml")
    return f"{xml_file_name}_copy.xml"


def test_load_file(valid_file_name):
    hot_pdf_object = HotPdf()
    hot_pdf_object.load(valid_file_name)


def test_element_dimensions_empty():
    assert filter_adjacent_coords(["d", "u", "m", "m", "y"], []) == []


def test_sparse_matrix_insert_and_get():
    matrix = SparseMatrix(3, 3)
    matrix.insert("A", 0, 0)
    matrix.insert("B", 1, 1)
    matrix.insert("C", 2, 2)
    assert matrix.get(0, 0) == "A"
    assert matrix.get(1, 1) == "B"
    assert matrix.get(2, 2) == "C"

    # Test getting non-inserted values (should return empty string)
    assert matrix.get(0, 1) == ""
    assert matrix.get(1, 0) == ""
    assert matrix.get(2, 1) == ""


def test_sparse_matrix_getitem_and_setitem():
    matrix = SparseMatrix(3, 3)
    matrix[0, 0] = "A"
    matrix[1, 1] = "B"
    matrix[2, 2] = "C"
    assert matrix[0, 0] == "A"
    assert matrix[1, 1] == "B"
    assert matrix[2, 2] == "C"

    matrix[1, 1] = "D"
    assert matrix[1, 1] == "D"
    with pytest.raises(IndexError, match="Specified index is out of range"):
        matrix[0, -1]
    with pytest.warns(UserWarning, match="Index Error. Skipping insertion into SparseMatrix"):
        matrix[-1, 0] = "Y"

    with pytest.raises(IndexError, match="Specified index is out of range"):
        matrix.get(-1, 0)
    with pytest.warns(UserWarning, match="Index Error. Skipping insertion into SparseMatrix"):
        matrix.insert("Y", -1, 0)


def test_sparse_matrix_iterator():
    matrix = SparseMatrix(3, 3)
    matrix[0, 0] = "A"
    matrix[1, 1] = "B"
    matrix[2, 2] = "C"
    non_empty_values = list(matrix)
    expected_result = [((0, 0), "A"), ((1, 1), "B"), ((2, 2), "C")]
    assert non_empty_values == expected_result


@pytest.mark.parametrize(
    "bbox1, bbox2, expected",
    [
        (El(0, 0, 2, 2), El(3, 3, 5, 5), False),
        (El(0, 0, 2, 2), El(3, 3, 4, 4), False),
        (El(0, 0, 2, 2), El(3, 3, 5, 5), False),
        (El(1, 1, 4, 4), El(3, 2, 6, 5), True),
        (El(1, 1, 6, 6), El(2, 2, 5, 5), True),
        (El(0, 0, 4, 4), El(4, 0, 8, 4), True),
        (El(0, 0, 3, 3), El(3, 3, 6, 6), True),
        (El(2, 1, 5, 4), El(4, 0, 7, 3), True),
        (El(1, 2, 4, 5), El(0, 4, 3, 7), True),
        (El(0, 0, 3, 3), El(0, 0, 3, 3), True),
        (El(2, 0, 6, 4), El(4, 2, 8, 6), True),
    ],
)
def test_intersect(bbox1, bbox2, expected):
    assert intersect(bbox1, bbox2) == expected


@pytest.mark.parametrize(
    "hot_characters, expected",
    [
        ([HotCharacter(value="H", x=0, y=0, x_end=10, span_id=test_uuid)], "H"),
        (
            [
                HotCharacter(value="H", x=0, y=0, x_end=10, span_id=test_uuid),
                HotCharacter(value="e", x=0, y=0, x_end=10, span_id=test_uuid),
                HotCharacter(value="l", x=0, y=0, x_end=10, span_id=test_uuid),
                HotCharacter(value="l", x=0, y=0, x_end=10, span_id=test_uuid),
                HotCharacter(value="o", x=0, y=0, x_end=10, span_id=test_uuid),
            ],
            "Hello",
        ),
        (
            [
                HotCharacter(value="H", x=0, y=0, x_end=10, span_id=test_uuid),
                HotCharacter(value="e", x=0, y=0, x_end=10, span_id=test_uuid),
                HotCharacter(value="l", x=0, y=0, x_end=10, span_id=test_uuid),
                HotCharacter(value="l", x=0, y=0, x_end=10, span_id=test_uuid),
                HotCharacter(value="o", x=0, y=0, x_end=10, span_id=test_uuid),
                HotCharacter(value=" ", x=0, y=0, x_end=10, span_id=test_uuid),
                HotCharacter(value="W", x=0, y=0, x_end=10, span_id=test_uuid),
                HotCharacter(value="o", x=0, y=0, x_end=10, span_id=test_uuid),
                HotCharacter(value="r", x=0, y=0, x_end=10, span_id=test_uuid),
                HotCharacter(value="l", x=0, y=0, x_end=10, span_id=test_uuid),
                HotCharacter(value="d", x=0, y=0, x_end=10, span_id=test_uuid),
            ],
            "Hello World",
        ),
    ],
)
def test_to_text(hot_characters, expected):
    assert to_text(hot_characters) == expected


def test_invalid_page_number(valid_file_name):
    hotpdf_object = HotPdf()
    hotpdf_object.load(valid_file_name)
    with pytest.raises(ValueError, match="Invalid page number"):
        _ = hotpdf_object.extract_page_text(page=2)


def test_len_memory_map_magic_function(valid_file_name):
    hotpdf_object = HotPdf()
    hotpdf_object.load(valid_file_name)
    assert len(hotpdf_object.pages[0].span_map) > 10


def test_span_map_get_span_none(valid_file_name):
    hotpdf_object = HotPdf(valid_file_name)
    assert hotpdf_object.pages[0].span_map.get_span("xyz") is None
    assert hotpdf_object.pages[0].span_map["xyz"] is None


@pytest.mark.skip(reason="Not implemented")
def test_span_map_set_none_error(valid_file_name):
    # Setting non span object in spanmap should throw error
    pass


def test_cid_str_to_str_with_mapping():
    # Test with a cid value that has a mapping
    decoder = Decoder(EncodingTypes.LATIN)
    assert decoder.cid_str_to_str("(cid:65)") == "A"
    assert decoder.cid_str_to_str("(cid:128)") == "€"
    assert decoder.cid_str_to_str("(cid:999)") == ""
    assert decoder.cid_str_to_str("(cid:x)") == "(cid:x)"
