# pylint: disable=no-self-use, protected-access
from pathlib import Path

import fpdf
from ..conftest import assert_pdf_equal

from defusedxml.ElementTree import fromstring as parse_xml_str
import pytest


from . import parameters

HERE = Path(__file__).resolve().parent
GENERATED_PDF_DIR = HERE / "generated_pdf"


def assert_style_match(lhs, rhs):
    mismatches = []
    for attr in lhs.MERGE_PROPERTIES:
        left = getattr(lhs, attr)
        right = getattr(rhs, attr)
        # can't short circuit because we want to report all of the issues
        if left != right:
            mismatches.append(f"left.{attr} ({left}) != right.{attr} ({right})")

    if mismatches:
        raise AssertionError("Styles do not match:\n    " + "\n    ".join(mismatches))


class TestUnits:
    @pytest.mark.parametrize(
        "name", [pytest.param(name, id=name) for name in fpdf.svg.relative_length_units]
    )
    def test_resolve_relative_length_units(self, name):
        with pytest.raises(ValueError):
            fpdf.svg.resolve_length(f" 1{name}")

    @pytest.mark.parametrize(
        "name", [pytest.param(name, id=name) for name in fpdf.svg.absolute_length_units]
    )
    def test_resolve_good_length_units(self, name):
        computed = fpdf.svg.resolve_length(f"  1 {name} ")
        assert isinstance(computed, float)

    def test_resolve_bad_length_units(self):
        with pytest.raises(ValueError):
            fpdf.svg.resolve_length("1 fake")

    def test_resolve_implicit_length_units(self):
        value = 1.5
        assert fpdf.svg.resolve_length(f"{value}") == value

    def test_resolve_angle_bad_units(self):
        with pytest.raises(ValueError):
            fpdf.svg.resolve_angle("1 fake")

    @pytest.mark.parametrize(
        "name", [pytest.param(name, id=name) for name in fpdf.svg.angle_units]
    )
    def test_resolve_good_angle_units(self, name):
        computed = fpdf.svg.resolve_angle(f"  1 {name} ")
        assert isinstance(computed, float)


def test_xmlns_lookup_failure():
    result = fpdf.svg.xmlns("this is not a real xml namespace", "rect")
    assert result == "rect"


def test_optional_converter():
    canary = object()
    conv = fpdf.svg.optional(object(), converter=lambda val: canary)
    assert conv is canary


class TestSVGPathParsing:
    @pytest.mark.parametrize("path, result", parameters.svg_path_directives)
    def test_parsing_all_directives(self, path, result):
        pdf_path = fpdf.drawing.PaintedPath()

        fpdf.svg.svg_path_converter(pdf_path, path)

        assert result == pdf_path._root_graphics_context.path_items

    @pytest.mark.parametrize("path, result", parameters.svg_path_implicit_directives)
    def test_parsing_all_implicit_directives(self, path, result):
        pdf_path = fpdf.drawing.PaintedPath()

        fpdf.svg.svg_path_converter(pdf_path, path)

        assert result == pdf_path._root_graphics_context.path_items

    @pytest.mark.parametrize("path, result", parameters.svg_path_edge_cases)
    def test_parsing_edge_cases(self, path, result):
        pdf_path = fpdf.drawing.PaintedPath()

        fpdf.svg.svg_path_converter(pdf_path, path)

        assert result == pdf_path._root_graphics_context.path_items

    def test_bad_path_start(self):
        pdf_path = fpdf.drawing.PaintedPath()
        with pytest.raises(ValueError):
            fpdf.svg.svg_path_converter(pdf_path, "L 1 2")
        with pytest.raises(ValueError):
            fpdf.svg.svg_path_converter(pdf_path, "C 1 2 3 4 5 6")
        with pytest.raises(ValueError):
            fpdf.svg.svg_path_converter(pdf_path, "Q 1 2 3 4")
        with pytest.raises(ValueError):
            fpdf.svg.svg_path_converter(pdf_path, "A 1 2 0 1 0 4 5")

    @pytest.mark.parametrize("path, expected", parameters.svg_path_render_tests)
    def test_rendering_smooth_curves(self, path, expected):
        pdf_path = fpdf.drawing.PaintedPath()

        fpdf.svg.svg_path_converter(pdf_path, path)

        resource_catalog = fpdf.output.ResourceCatalog()
        style = fpdf.drawing.GraphicsStyle()
        first_point = fpdf.drawing.Point(0, 0)
        start = fpdf.drawing.Move(first_point)

        result = pdf_path.render(resource_catalog, style, start, first_point)[0]

        assert result == expected


@pytest.mark.parametrize("shape, output, guard", parameters.test_svg_shape_tags)
def test_svg_shape_conversion(shape, output, guard):
    xml = parse_xml_str(shape)
    converter = getattr(fpdf.svg.ShapeBuilder, xml.tag)

    with guard:
        path = converter(xml)
        assert output == path._root_graphics_context.path_items


class TestSVGAttributeConversion:
    @pytest.mark.parametrize(
        "transform, expected, guard", parameters.test_svg_transforms
    )
    def test_svg_transform_conversion(self, transform, expected, guard):
        with guard:
            result = fpdf.svg.convert_transforms(transform)
            assert result == pytest.approx(expected)

    @pytest.mark.parametrize("svg_file", parameters.test_svg_transform_documents)
    def test_svg_transform_conversion_visual(self, tmp_path, svg_file):
        # this only tests the SVG 1.1 transforms for now because the 2.0 transforms do not seem to be widely supported?
        svg = fpdf.svg.SVGObject.from_file(svg_file)

        pdf = fpdf.FPDF(unit="pt", format=(svg.width, svg.height))
        pdf.set_margin(0)
        pdf.add_page()

        svg.draw_to_page(pdf)

        assert_pdf_equal(
            pdf, GENERATED_PDF_DIR / "transforms" / f"{svg_file.stem}.pdf", tmp_path
        )

    @pytest.mark.parametrize(
        "element, expected, guard", parameters.test_svg_attribute_conversion
    )
    def test_attribute_conversion(self, element, expected, guard):
        xml = parse_xml_str(element)

        stylable = fpdf.drawing.PaintedPath()
        with guard:
            fpdf.svg.apply_styles(stylable, xml)
            assert_style_match(stylable.style, expected)


class TestSVGObject:
    def test_bad_root_tag(self):
        notsvg = """<sometag></sometag>"""

        with pytest.raises(ValueError):
            fpdf.svg.SVGObject(notsvg)

    @pytest.mark.parametrize(
        "svg_data, expected_dim, expected_tf, guard", parameters.svg_shape_info_tests
    )
    def test_document_shape_info(self, svg_data, expected_dim, expected_tf, guard):
        pdf = fpdf.FPDF(unit="pt", format=(10, 10))
        pdf.set_margin(0)
        pdf.add_page()

        with guard:
            svg = fpdf.svg.SVGObject(svg_data)
            width, height, base_group = svg.transform_to_page_viewport(pdf)

            assert (width, height) == pytest.approx(expected_dim)
            assert base_group.transform == pytest.approx(expected_tf)

    @pytest.mark.parametrize("svg_file", parameters.test_svg_sources)
    def test_svg_conversion(self, tmp_path, svg_file):
        svg = fpdf.svg.SVGObject.from_file(svg_file)

        pdf = fpdf.FPDF(unit="pt", format=(svg.width, svg.height))
        pdf.set_margin(0)
        pdf.add_page()

        svg.draw_to_page(pdf)

        assert_pdf_equal(pdf, GENERATED_PDF_DIR / f"{svg_file.stem}.pdf", tmp_path)

    @pytest.mark.parametrize("svg_file", parameters.test_svg_sources[0:1])
    def test_draw_to_page_offset(self, tmp_path, svg_file):
        svg = fpdf.svg.SVGObject.from_file(svg_file)

        pdf = fpdf.FPDF(unit="pt", format=(svg.width, svg.height))
        pdf.set_margin(0)
        pdf.add_page()

        svg.draw_to_page(pdf, x=5, y=5)

        assert_pdf_equal(
            pdf, GENERATED_PDF_DIR / f"{svg_file.stem}-offset.pdf", tmp_path
        )

    def test_path_def(self):
        svg_data = (
            '<svg xmlns="http://www.w3.org/2000/svg" '
            'xmlns:xlink="http://www.w3.org/1999/xlink">'
            '<defs><path id="path" d="M 0 0 L 1 2 Z"/></defs></svg>'
        )
        fpdf.svg.SVGObject(svg_data)

    def test_bad_xref(self):
        svg_data = (
            '<svg xmlns="http://www.w3.org/2000/svg" '
            'xmlns:xlink="http://www.w3.org/1999/xlink">'
            '<use transform="rotate(45)"/></svg>'
        )
        with pytest.raises(ValueError):
            fpdf.svg.SVGObject(svg_data)

    def test_missing_xref(self):
        svg_data = (
            '<svg xmlns="http://www.w3.org/2000/svg" '
            'xmlns:xlink="http://www.w3.org/1999/xlink">'
            '<use xlink:href="#missing"/></svg>'
        )
        with pytest.raises(ValueError):
            fpdf.svg.SVGObject(svg_data)

    def test_svg_conversion_no_transparency(self, tmp_path):
        svg = fpdf.svg.SVGObject.from_file(parameters.svgfile("SVG_logo.svg"))

        pdf = fpdf.FPDF(unit="pt", format=(svg.width, svg.height))
        pdf.set_margin(0)
        pdf.allow_images_transparency = False
        pdf.add_page()

        svg.draw_to_page(pdf)

        assert_pdf_equal(
            pdf, GENERATED_PDF_DIR / "SVG_logo_notransparency.pdf", tmp_path
        )

    def test_svg_conversion_priority_styles(self, tmp_path):
        svg_file = parameters.svgfile("simple_rect.svg")

        svg = fpdf.svg.SVGObject.from_file(svg_file)

        pdf = fpdf.FPDF(unit="pt", format=(svg.width, svg.height))
        pdf.set_margin(0)
        pdf.allow_images_transparency = False
        pdf.add_page()

        svg.draw_to_page(pdf)

        assert_pdf_equal(pdf, GENERATED_PDF_DIR / f"{svg_file.stem}.pdf", tmp_path)

    @pytest.mark.parametrize(
        "fill_color, stroke_color, file_suffix", parameters.svg_current_color
    )
    def test_svg_current_color(self, tmp_path, fill_color, stroke_color, file_suffix):
        svg_file = parameters.svgfile("simple_rect_current_color.svg")

        svg = fpdf.svg.SVGObject.from_file(svg_file)

        pdf = fpdf.FPDF(unit="pt", format=(svg.width, svg.height))
        pdf.set_margin(0)
        pdf.allow_images_transparency = False
        pdf.add_page()

        if fill_color is not None:
            pdf.set_fill_color(fill_color[0], fill_color[1], fill_color[2])
        if stroke_color is not None:
            pdf.set_draw_color(stroke_color[0], stroke_color[1], stroke_color[2])
        svg.draw_to_page(pdf)

        assert_pdf_equal(
            pdf, GENERATED_PDF_DIR / f"{svg_file.stem}{file_suffix}.pdf", tmp_path
        )

    def test_svg_render_content_in_a_tag(self, tmp_path):
        svg_file = parameters.svgfile("simple_rect_in_a_tag.svg")

        svg = fpdf.svg.SVGObject.from_file(svg_file)

        pdf = fpdf.FPDF(unit="pt", format=(svg.width, svg.height))
        pdf.set_margin(0)
        pdf.allow_images_transparency = False
        pdf.add_page()

        svg.draw_to_page(pdf)

        assert_pdf_equal(pdf, GENERATED_PDF_DIR / f"{svg_file.stem}.pdf", tmp_path)

    def test_svg_rendering_image_over_page_break(self, tmp_path):
        pdf = fpdf.FPDF()
        pdf.add_page()
        pdf.y += 150
        pdf.image(parameters.svgfile("embedded-raster-images.svg"))
        # In the resulting document, a page break occurs before the image being rendered:
        assert_pdf_equal(
            pdf, GENERATED_PDF_DIR / "svg_rendering_image_over_page_break.pdf", tmp_path
        )

    def test_svg_text_ttf_font(self, tmp_path):
        pdf = fpdf.FPDF()
        pdf.add_page()
        pdf.add_font(
            family="serif", style="", fname=HERE.parent / "fonts" / "DejaVuSans.ttf"
        )
        pdf.image(
            name=HERE / "svg_sources" / "ocanada.svg",
            x=pdf.l_margin,
            y=pdf.t_margin,
            w=pdf.epw,
            h=pdf.eph,
            keep_aspect_ratio=True,
        )
        assert_pdf_equal(
            pdf,
            GENERATED_PDF_DIR / "ocanada.pdf",
            tmp_path,
        )
