# SPDX-License-Identifier: LGPL-2.1-or-later

import unittest
import unittest.mock
import pathlib
from tempfile import TemporaryDirectory
from PySide import QtCore, QtGui
from CAMTests.PathTestUtils import PathTestWithAssets
from Path.Tool.assets import DummyAssetSerializer
from Path.Tool.shape.models.icon import (
    ToolBitShapeIcon,
    ToolBitShapeSvgIcon,
    ToolBitShapePngIcon,
)


class TestToolBitShapeIconBase(PathTestWithAssets):
    """Base class for ToolBitShapeIcon tests."""

    ICON_CLASS = ToolBitShapeIcon

    def setUp(self):
        super().setUp()
        # Ensure a QApplication exists for QPixmap tests
        self.app = QtGui.QApplication.instance()

        # Create a test shape and a test SVG icon.
        self.test_shape = self.assets.get("toolbitshape://ballend")
        self.test_svg = self.test_shape.icon
        assert self.test_svg is not None
        self.icon = self.ICON_CLASS("test_icon_base", b"")

    def tearDown(self):
        self.app = None
        return super().tearDown()

    def test_create_instance(self):
        # Test basic instance creation
        icon_id = "test_icon_123.dat"
        icon = self.ICON_CLASS(icon_id, b"")
        self.assertEqual(icon.get_id(), icon_id)
        self.assertEqual(icon.data, b"")
        self.assertIsInstance(icon.abbreviations, dict)

    def test_to_bytes(self):
        # Test serializing to bytes
        icon_id = "test_to_bytes.bin"
        icon_data = b"some_binary_data"
        icon = ToolBitShapeIcon(icon_id, icon_data)
        self.assertEqual(icon.to_bytes(DummyAssetSerializer), icon_data)

    def test_get_size_in_bytes(self):
        # Test getting icon data length
        icon_with_data = ToolBitShapeIcon("with_data.bin", b"abc")
        self.assertEqual(icon_with_data.get_size_in_bytes(), 3)

        icon_no_data = ToolBitShapeIcon("no_data.dat", b"")
        self.assertEqual(icon_no_data.get_size_in_bytes(), 0)

    @unittest.mock.patch("Path.Tool.shape.util.create_thumbnail_from_data")
    def test_from_shape_data_success(self, mock_create_thumbnail):
        # Test creating instance from shape data - success case
        shape_id = "test_shape"
        thumbnail_data = b"png thumbnail data"
        mock_create_thumbnail.return_value = thumbnail_data
        icon = ToolBitShapeIcon.from_shape_data(self.test_svg.data, shape_id)

        mock_create_thumbnail.assert_called_once_with(self.test_svg.data)
        self.assertIsNotNone(icon)
        self.assertIsInstance(icon, ToolBitShapePngIcon)
        self.assertEqual(icon.get_id(), shape_id)
        self.assertEqual(icon.data, thumbnail_data)
        self.assertEqual(icon.abbreviations, {})

    @unittest.mock.patch("Path.Tool.shape.util.create_thumbnail_from_data")
    def test_from_shape_data_failure(self, mock_create_thumbnail):
        # Test creating instance from shape data - failure case
        shape_id = "test_shape"
        mock_create_thumbnail.return_value = None
        icon_failed = ToolBitShapeIcon.from_shape_data(self.test_svg.data, shape_id)

        mock_create_thumbnail.assert_called_once_with(self.test_svg.data)
        self.assertIsNone(icon_failed)

    def test_get_png(self):
        if not self.app:
            self.skipTest("QApplication not available, skipping test_get_png")
        if type(self) is TestToolBitShapeIconBase:
            self.skipTest("Skipping test on abstract base class")
        # Test getting PNG data from the icon
        icon_size = QtCore.QSize(16, 16)
        png_data = self.icon.get_png(icon_size)
        self.assertIsInstance(png_data, bytes)
        self.assertTrue(len(png_data) > 0)

    def test_get_qpixmap(self):
        if not self.app:
            self.skipTest("QApplication not available, skipping test_get_qpixmap")
        if type(self) is TestToolBitShapeIconBase:
            self.skipTest("Skipping test on abstract base class")
        # Test getting QPixmap from the icon
        icon_size = QtCore.QSize(31, 32)
        pixmap = self.icon.get_qpixmap(icon_size)
        self.assertIsInstance(pixmap, QtGui.QPixmap)
        self.assertFalse(pixmap.isNull())
        self.assertEqual(pixmap.size().width(), 31)
        self.assertEqual(pixmap.size().height(), 32)


class TestToolBitShapeSvgIcon(TestToolBitShapeIconBase):
    """Tests specifically for ToolBitShapeSvgIcon."""

    ICON_CLASS = ToolBitShapeSvgIcon

    def setUp(self):
        super().setUp()
        self.icon = ToolBitShapeSvgIcon("test_icon_svg", self.test_svg.data)

    def test_from_bytes_svg(self):
        # Test creating instance from bytes with SVG
        icon_svg = ToolBitShapeSvgIcon.from_bytes(
            self.test_svg.data, "test_from_bytes.svg", {}, DummyAssetSerializer
        )
        self.assertEqual(icon_svg.get_id(), "test_from_bytes.svg")
        self.assertEqual(icon_svg.data, self.test_svg.data)
        self.assertIsInstance(icon_svg.abbreviations, dict)

    def test_round_trip_serialization_svg(self):
        # Test serialization and deserialization round trip for SVG
        svg_id = "round_trip_svg.svg"
        icon_svg = ToolBitShapeSvgIcon(svg_id, self.test_svg.data)
        serialized_svg = icon_svg.to_bytes(DummyAssetSerializer)
        deserialized_svg = ToolBitShapeSvgIcon.from_bytes(
            serialized_svg, svg_id, {}, DummyAssetSerializer
        )
        self.assertEqual(deserialized_svg.get_id(), svg_id)
        self.assertEqual(deserialized_svg.data, self.test_svg.data)
        # Abbreviations are extracted on access, so we don't check the dict directly
        self.assertIsInstance(deserialized_svg.abbreviations, dict)

    def test_from_file_svg(self):
        # We cannot use NamedTemporaryFile on Windows, because there
        # we may not have permission to read the tempfile while it is
        # still open.
        # So we use TemporaryDirectory instead, to ensure cleanup while
        # still having a the temporary file inside it.
        with TemporaryDirectory() as thedir:
            tempfile = pathlib.Path(thedir, "test.svg")
            tempfile.write_bytes(self.test_svg.data)

            icon_id = "dummy_icon"
            icon = ToolBitShapeIcon.from_file(tempfile, icon_id)
            self.assertIsInstance(icon, ToolBitShapeSvgIcon)
            self.assertEqual(icon.get_id(), icon_id)
            self.assertEqual(icon.data, self.test_svg.data)
            self.assertIsInstance(icon.abbreviations, dict)

    def test_abbreviations_cached_property_svg(self):
        # Test abbreviations property and caching for SVG
        icon_svg = ToolBitShapeSvgIcon("cached_abbr.svg", self.test_svg.data)

        # Accessing the property should call the static method
        with unittest.mock.patch.object(
            ToolBitShapeSvgIcon, "get_abbreviations_from_svg"
        ) as mock_get_abbr:
            mock_get_abbr.return_value = {"param1": "A1"}
            abbr1 = icon_svg.abbreviations
            abbr2 = icon_svg.abbreviations
            mock_get_abbr.assert_called_once_with(self.test_svg.data)
            self.assertEqual(abbr1, {"param1": "A1"})
            self.assertEqual(abbr2, {"param1": "A1"})

    def test_get_abbr_svg(self):
        # Test getting abbreviations for SVG
        icon_data = self.test_svg.data
        icon = ToolBitShapeSvgIcon("abbr_test.svg", icon_data)
        # Assuming the test_svg data has 'diameter' and 'length' ids
        self.assertIsNotNone(icon.get_abbr("Diameter"))
        self.assertIsNotNone(icon.get_abbr("Length"))
        self.assertIsNone(icon.get_abbr("NonExistent"))

    def test_get_abbreviations_from_svg_static(self):
        # Test static method get_abbreviations_from_svg
        svg_content = self.test_svg.data
        abbr = ToolBitShapeSvgIcon.get_abbreviations_from_svg(svg_content)
        # Assuming the test_svg data has 'diameter' and 'length' ids
        self.assertIn("Diameter", abbr)
        self.assertIn("Length", abbr)

        # Test with invalid SVG
        invalid_svg = b"<svg><text id='param1'>A1</text>"  # Missing closing tag
        abbr_invalid = ToolBitShapeSvgIcon.get_abbreviations_from_svg(invalid_svg)
        self.assertEqual(abbr_invalid, {})

        # Test with no text elements
        no_text_svg = b'<svg xmlns="http://www.w3.org/2000/svg"><rect/></svg>'
        abbr_no_text = ToolBitShapeSvgIcon.get_abbreviations_from_svg(no_text_svg)
        self.assertEqual(abbr_no_text, {})


class TestToolBitShapePngIcon(TestToolBitShapeIconBase):
    """Tests specifically for ToolBitShapePngIcon."""

    ICON_CLASS = ToolBitShapePngIcon

    def setUp(self):
        super().setUp()
        self.png_data = b"\x89PNG\r\n\x1a\n"  # Basic PNG signature
        self.icon = ToolBitShapePngIcon("test_icon_png", self.png_data)

    def test_from_bytes_png(self):
        # Test creating instance from bytes with PNG
        icon_png = ToolBitShapePngIcon.from_bytes(
            self.png_data, "test_from_bytes.png", {}, DummyAssetSerializer
        )
        self.assertEqual(icon_png.get_id(), "test_from_bytes.png")
        self.assertEqual(icon_png.data, self.png_data)
        self.assertEqual(icon_png.abbreviations, {})  # No abbreviations for PNG

    def test_round_trip_serialization_png(self):
        # Test serialization and deserialization round trip for PNG
        png_id = "round_trip_png.png"
        serialized_png = self.icon.to_bytes(DummyAssetSerializer)
        deserialized_png = ToolBitShapePngIcon.from_bytes(
            serialized_png, png_id, {}, DummyAssetSerializer
        )
        self.assertEqual(deserialized_png.get_id(), png_id)
        self.assertEqual(deserialized_png.data, self.png_data)
        self.assertEqual(deserialized_png.abbreviations, {})

    def test_from_file_png(self):
        png_data = b"\\x89PNG\\r\\n\\x1a\\n"
        # We cannot use NamedTemporaryFile on Windows, because there
        # we may not have permission to read the tempfile while it is
        # still open.
        # So we use TemporaryDirectory instead, to ensure cleanup while
        # still having a the temporary file inside it.
        with TemporaryDirectory() as thedir:
            tempfile = pathlib.Path(thedir, "test.png")
            tempfile.write_bytes(png_data)

            icon_id = "dummy_icon"
            icon = ToolBitShapeIcon.from_file(tempfile, icon_id)
            self.assertIsInstance(icon, ToolBitShapePngIcon)
            self.assertEqual(icon.get_id(), icon_id)
            self.assertEqual(icon.data, png_data)
            self.assertEqual(icon.abbreviations, {})

    def test_abbreviations_cached_property_png(self):
        # Test abbreviations property and caching for PNG
        self.assertEqual(self.icon.abbreviations, {})

    def test_get_abbr_png(self):
        # Test getting abbreviations for PNG
        self.assertIsNone(self.icon.get_abbr("Diameter"))

    def test_get_qpixmap(self):
        self.skipTest("Skipping test, have no test data")


if __name__ == "__main__":
    unittest.main()
