"""Advanced pytest-based tests for multi-size ICO generation.

This module contains comprehensive parameterized tests and edge case scenarios
for the img2ico module's multi-size ICO generation functionality.
"""

from __future__ import annotations

import struct
from pathlib import Path

import pytest
from PIL import Image, ImageDraw

from pytola.office.img2ico.img2ico import ImageToIcoRunner


class TestMultiSizeICOStructure:
    """Tests for multi-size ICO file structure and format."""

    @pytest.fixture
    def sample_image(self, tmp_path: Path) -> Path:
        """Create a sample test image."""
        img_path = tmp_path / "sample.png"
        img = Image.new("RGBA", (256, 256), color=(255, 128, 64, 255))

        # Add some distinctive pattern
        draw = ImageDraw.Draw(img)
        draw.rectangle([0, 0, 128, 128], fill=(0, 0, 255, 255))
        draw.rectangle([128, 128, 256, 256], fill=(0, 255, 0, 255))
        draw.text((100, 100), "TEST", fill=(255, 255, 255, 255))

        img.save(img_path)
        return img_path

    @pytest.mark.parametrize(
        "sizes",
        [
            {16},
            {16, 32},
            {16, 32, 48, 64},
            {16, 24, 32, 48, 64, 128},  # Exclude 256+ for PIL compatibility
            {1, 2, 4, 8, 16, 32},  # Very small sizes
        ],
    )
    def test_multi_size_generation(self, sample_image: Path, sizes: set[int], tmp_path: Path):
        """Test multi-size ICO generation with various size combinations."""
        output_file = tmp_path / "test_output.ico"

        runner = ImageToIcoRunner(
            input_path=sample_image,
            output_path=output_file,
            sizes=sizes,
            quality="high",
        )
        runner.run()

        assert output_file.exists(), "ICO file should be created"

        # Verify ICO structure
        with open(output_file, "rb") as f:
            # Read header
            header = f.read(6)
            reserved, ico_type, count = struct.unpack("<HHH", header)

            assert reserved == 0, "Reserved field should be 0"
            assert ico_type == 1, "ICO type should be 1"
            assert count == len(sizes), f"Should have {len(sizes)} images"

            # Read directory entries
            entries = []
            for _ in range(count):
                entry_data = f.read(16)
                entry = struct.unpack("<BBBBHHII", entry_data)
                entries.append(entry)

            # Verify each entry
            actual_sizes = set()
            for i, (
                width,
                _height,
                _color_count,
                reserved_field,
                planes,
                bit_count,
                size,
                offset,
            ) in enumerate(entries):
                # Convert 0 to 256 for display
                display_width = width if width > 0 else 256

                actual_sizes.add(display_width)
                assert reserved_field == 0, f"Entry {i}: reserved field should be 0"
                assert planes in [0, 1], f"Entry {i}: planes should be 0 or 1"
                assert bit_count == 32, f"Entry {i}: bit_count should be 32 for PNG"
                assert size > 0, f"Entry {i}: size should be positive"
                assert offset >= 6 + count * 16, f"Entry {i}: offset should be valid"

            assert actual_sizes == sizes, f"Generated sizes {actual_sizes} don't match expected {sizes}"

    def test_ico_content_integrity(self, sample_image: Path, tmp_path: Path):
        """Test that ICO content maintains image integrity (limited PIL support)."""
        sizes = {16, 32, 64}
        output_file = tmp_path / "integrity_test.ico"

        runner = ImageToIcoRunner(
            input_path=sample_image,
            output_path=output_file,
            sizes=sizes,
            quality="high",
        )
        runner.run()

        # Open original image
        with Image.open(sample_image) as orig_img:
            if orig_img.mode != "RGBA":
                orig_img = orig_img.convert("RGBA")

            # Test ICO structure directly (PIL has limited multi-size support)
            with open(output_file, "rb") as f:
                # Read header
                header = f.read(6)
                _reserved, _ico_type, count = struct.unpack("<HHH", header)
                assert count == len(sizes), f"Expected {len(sizes)} entries, got {count}"

                # Read and verify entries
                entries = []
                for i in range(count):
                    entry = struct.unpack("<BBBBHHII", f.read(16))
                    (
                        width,
                        height,
                        _color_count,
                        _reserved_field,
                        _planes,
                        _bit_count,
                        size,
                        offset,
                    ) = entry
                    display_width = width if width > 0 else 256
                    entries.append(
                        {
                            "index": i,
                            "width": display_width,
                            "height": height if height > 0 else 256,
                            "size": size,
                            "offset": offset,
                        }
                    )

                # Verify sizes match expected
                actual_sizes = {entry["width"] for entry in entries}
                assert actual_sizes == sizes, f"Sizes mismatch: expected {sizes}, got {actual_sizes}"

                # Basic content verification - check that data exists and is reasonable
                for entry in entries:
                    assert entry["size"] > 0, f"Entry {entry['index']}: zero size data"
                    assert entry["offset"] > 0, f"Entry {entry['index']}: invalid offset"

                    # Seek to data and verify it looks like PNG
                    f.seek(entry["offset"])
                    data = f.read(min(8, entry["size"]))  # Read first 8 bytes
                    png_signature = b"\x89PNG\r\n\x1a\n"
                    assert data[:8] == png_signature[: len(data)], (
                        f"Entry {entry['index']}: data doesn't start with PNG signature"
                    )

    @pytest.mark.parametrize("quality", ["low", "medium", "high"])
    def test_quality_levels(self, sample_image: Path, quality: str, tmp_path: Path):
        """Test different quality settings produce different file sizes."""
        output_file = tmp_path / f"quality_{quality}.ico"
        sizes = {32, 64, 128}

        runner = ImageToIcoRunner(
            input_path=sample_image,
            output_path=output_file,
            sizes=sizes,
            quality=quality,
        )
        runner.run()

        assert output_file.exists(), f"Quality {quality}: ICO should be created"

        # Basic verification
        file_size = output_file.stat().st_size
        assert file_size > 0, f"Quality {quality}: file should not be empty"

        # Note: We don't enforce size ordering as it depends on many factors
        # but we do verify the files are created correctly

    def test_file_structure_integrity(self, sample_image: Path, tmp_path: Path):
        """Test ICO file structure integrity and non-overlapping data."""
        sizes = {16, 32, 48, 64, 128}
        output_file = tmp_path / "structure_test.ico"

        runner = ImageToIcoRunner(
            input_path=sample_image,
            output_path=output_file,
            sizes=sizes,
            quality="high",
        )
        runner.run()

        file_size = output_file.stat().st_size

        # Parse and verify structure
        with open(output_file, "rb") as f:
            # Header
            header = f.read(6)
            _reserved, _ico_type, count = struct.unpack("<HHH", header)
            assert count == len(sizes)

            # Directory entries
            entries = []
            for _ in range(count):
                entry_data = f.read(16)
                entry = struct.unpack("<BBBBHHII", entry_data)
                entries.append(entry)

            # Verify no overlapping data blocks
            data_ranges = []
            for (
                _width,
                _height,
                _color_count,
                _reserved_field,
                _planes,
                _bit_count,
                size,
                offset,
            ) in entries:
                data_ranges.append((offset, offset + size))

            # Sort by offset
            data_ranges.sort()

            # Check for overlaps
            for i in range(len(data_ranges) - 1):
                current_end = data_ranges[i][1]
                next_start = data_ranges[i + 1][0]
                assert current_end <= next_start, "Data blocks should not overlap"

            # Check that all data fits in file
            max_end = max(end for _, end in data_ranges)
            assert max_end <= file_size, "All data should fit within file size"


class TestEdgeCases:
    """Tests for edge cases and boundary conditions."""

    @pytest.mark.parametrize("edge_size", [1, 2, 4096, 8192])
    def test_extreme_sizes(self, tmp_path: Path, edge_size: int):
        """Test handling of extreme icon sizes."""
        # Create appropriately sized test image
        img_size = max(edge_size, 64)  # Ensure source image is adequate
        img_path = tmp_path / f"extreme_{edge_size}.png"
        img = Image.new("RGBA", (img_size, img_size), color=(255, 0, 0, 255))
        img.save(img_path)

        output_file = tmp_path / f"extreme_{edge_size}.ico"

        try:
            runner = ImageToIcoRunner(
                input_path=img_path,
                output_path=output_file,
                sizes={edge_size},
                quality="high",
            )
            runner.run()

            # Should either succeed or fail gracefully
            if output_file.exists():
                # If it exists, verify basic structure
                with open(output_file, "rb") as f:
                    header = f.read(6)
                    _reserved, ico_type, count = struct.unpack("<HHH", header)
                    assert ico_type == 1
                    assert count >= 0
            # If it doesn't exist, that's also acceptable for extreme cases

        except Exception as e:
            # Extreme cases may legitimately fail, but should not crash
            pytest.skip(f"Extreme size {edge_size} not supported: {e}")

    def test_single_pixel_image(self, tmp_path: Path):
        """Test conversion of single-pixel image."""
        # Create 1x1 pixel image
        img_path = tmp_path / "single_pixel.png"
        img = Image.new("RGBA", (1, 1), color=(255, 128, 0, 255))
        img.save(img_path)

        output_file = tmp_path / "single_pixel.ico"

        runner = ImageToIcoRunner(input_path=img_path, output_path=output_file, sizes={16, 32}, quality="high")

        # Should handle gracefully
        runner.run()

        # May or may not create file, but shouldn't crash
        assert True  # Test passes if no exception

    def test_transparent_images(self, tmp_path: Path):
        """Test handling of images with transparency."""
        # Create image with transparency
        img_path = tmp_path / "transparent.png"
        img = Image.new("RGBA", (128, 128), color=(0, 0, 0, 0))  # Fully transparent

        # Add some semi-transparent content
        draw = ImageDraw.Draw(img)
        draw.ellipse([32, 32, 96, 96], fill=(255, 0, 0, 128))  # Semi-transparent red circle
        draw.rectangle([48, 48, 80, 80], fill=(0, 255, 0, 192))  # More opaque green square

        img.save(img_path)

        output_file = tmp_path / "transparent.ico"

        runner = ImageToIcoRunner(
            input_path=img_path,
            output_path=output_file,
            sizes={16, 32, 64},
            quality="high",
        )
        runner.run()

        assert output_file.exists(), "Transparent image should create ICO"

        # Verify structure
        with open(output_file, "rb") as f:
            header = f.read(6)
            _reserved, ico_type, count = struct.unpack("<HHH", header)
            assert ico_type == 1
            assert count == 3


class TestCrossFormatCompatibility:
    """Tests for compatibility with different input image formats."""

    @pytest.fixture(
        params=[
            ("PNG", "RGBA"),
            ("JPEG", "RGB"),
            ("BMP", "RGB"),
            ("GIF", "P"),
        ]
    )
    def image_format_data(self, request, tmp_path: Path):
        """Parametrized fixture for different image formats."""
        format_name, mode = request.param
        img_path = tmp_path / f"test_{format_name.lower()}.{format_name.lower()}"

        # Create test image
        img = Image.new(mode, (256, 256), color=(100, 150, 200))
        if mode == "P":
            # Add some pattern for palette mode
            draw = ImageDraw.Draw(img)
            draw.rectangle([0, 0, 128, 128], fill=0)
            draw.rectangle([128, 128, 256, 256], fill=1)

        # Save in appropriate format
        if format_name == "JPEG":
            img.save(img_path, "JPEG", quality=95)
        else:
            img.save(img_path, format_name)

        return img_path, format_name

    def test_various_input_formats(self, image_format_data, tmp_path: Path):
        """Test ICO generation from various input image formats."""
        img_path, format_name = image_format_data
        output_file = tmp_path / f"output_{format_name.lower()}.ico"

        runner = ImageToIcoRunner(input_path=img_path, output_path=output_file, sizes={32, 64}, quality="high")

        try:
            runner.run()
            assert output_file.exists(), f"{format_name} format should create ICO"

            # Basic structure verification
            with open(output_file, "rb") as f:
                header = f.read(6)
                _reserved, ico_type, count = struct.unpack("<HHH", header)
                assert ico_type == 1
                assert count == 2

        except Exception as e:
            pytest.fail(f"{format_name} format failed: {e}")


class TestPerformance:
    """Performance-related tests."""

    def test_reasonable_performance(self, tmp_path: Path):
        """Test that conversion completes within reasonable time."""
        import time

        # Create large test image
        img_path = tmp_path / "performance_test.png"
        img = Image.new("RGBA", (1024, 1024), color=(255, 128, 64, 255))
        img.save(img_path)

        # Many sizes to test
        sizes = {16, 24, 32, 48, 64, 96, 128, 192, 256, 384, 512}
        output_file = tmp_path / "performance_output.ico"

        runner = ImageToIcoRunner(
            input_path=img_path,
            output_path=output_file,
            sizes=sizes,
            quality="medium",  # Medium quality for performance test
        )

        start_time = time.time()
        runner.run()
        end_time = time.time()

        processing_time = end_time - start_time
        print(f"Processing time: {processing_time:.2f} seconds for {len(sizes)} sizes")

        assert output_file.exists(), "Performance test should create output"
        assert processing_time < 30.0, f"Processing took too long: {processing_time:.2f}s"


class TestLargeSizeSupport:
    """Tests for large size (>256) support and special cases."""

    def test_large_sizes_direct_verification(self, tmp_path: Path):
        """Test large sizes using direct file structure verification."""
        # Create appropriately sized source image
        img_path = tmp_path / "large_source.png"
        img = Image.new("RGBA", (1024, 1024), color=(255, 128, 64, 255))
        img.save(img_path)

        sizes = {256, 512, 1024}
        output_file = tmp_path / "large_sizes.ico"

        runner = ImageToIcoRunner(input_path=img_path, output_path=output_file, sizes=sizes, quality="high")
        runner.run()

        assert output_file.exists(), "Large sizes ICO should be created"

        # Direct verification since PIL has limitations
        with open(output_file, "rb") as f:
            header = f.read(6)
            _reserved, ico_type, count = struct.unpack("<HHH", header)
            assert ico_type == 1, "Should be ICO type"
            assert count == len(sizes), f"Should have {len(sizes)} entries"

            # Read entries and verify large size handling
            large_size_entries = 0
            for _i in range(count):
                entry = struct.unpack("<BBBBHHII", f.read(16))
                width, height = entry[0], entry[1]
                # For sizes >= 256, width/height should be 0 in ICO format
                if width == 0 and height == 0:
                    large_size_entries += 1
                else:
                    # Should be the actual size for < 256
                    assert width > 0
                    assert width < 256
                    assert height > 0
                    assert height < 256

            # All our test sizes are >= 256
            assert large_size_entries == count, "All entries should represent large sizes"

    def test_mixed_sizes(self, tmp_path: Path):
        """Test mixing small and large sizes."""
        img_path = tmp_path / "mixed_source.png"
        img = Image.new("RGBA", (1024, 1024), color=(100, 150, 200, 255))
        img.save(img_path)

        # Mix of small and large sizes
        sizes = {16, 32, 256, 512}
        output_file = tmp_path / "mixed_sizes.ico"

        runner = ImageToIcoRunner(input_path=img_path, output_path=output_file, sizes=sizes, quality="high")
        runner.run()

        assert output_file.exists(), "Mixed sizes ICO should be created"

        # Verify mixed size handling
        with open(output_file, "rb") as f:
            header = f.read(6)
            _reserved, _ico_type, count = struct.unpack("<HHH", header)
            assert count == len(sizes)

            small_entries = 0
            large_entries = 0
            for _i in range(count):
                entry = struct.unpack("<BBBBHHII", f.read(16))
                width, height = entry[0], entry[1]
                if width == 0 and height == 0:
                    large_entries += 1
                else:
                    small_entries += 1
                    assert width in sizes
                    assert width < 256
                    assert height in sizes
                    assert height < 256

            # Should have 2 small (16, 32) and 2 large (256, 512)
            assert small_entries == 2, f"Expected 2 small entries, got {small_entries}"
            assert large_entries == 2, f"Expected 2 large entries, got {large_entries}"


class TestEnhancedCoverage:
    """Additional tests to improve overall coverage."""

    def test_single_size_generation(self, tmp_path: Path):
        """Test generation of single-size ICO (edge case)."""
        img_path = tmp_path / "single_size.png"
        img = Image.new("RGBA", (128, 128), color=(255, 0, 0, 255))
        img.save(img_path)

        output_file = tmp_path / "single_size.ico"
        runner = ImageToIcoRunner(input_path=img_path, output_path=output_file, sizes={64}, quality="high")
        runner.run()

        assert output_file.exists()

        # Verify single entry
        with open(output_file, "rb") as f:
            header = f.read(6)
            _reserved, _ico_type, count = struct.unpack("<HHH", header)
            assert count == 1, "Should have exactly one entry"

            entry = struct.unpack("<BBBBHHII", f.read(16))
            width, height = entry[0], entry[1]
            assert width == 64, "Width should be 64"
            assert height == 64, "Height should be 64"

    def test_error_handling_invalid_input(self, tmp_path: Path):
        """Test graceful error handling for invalid inputs."""
        # Test with non-existent file
        runner = ImageToIcoRunner(input_path=Path("/nonexistent/file.png"), sizes={32, 64})
        # Should not crash, just log error
        runner.run()

        # Test with invalid size
        img_path = tmp_path / "test.png"
        img = Image.new("RGBA", (100, 100), color=(0, 255, 0, 255))
        img.save(img_path)

        runner2 = ImageToIcoRunner(
            input_path=img_path,
            output_path=tmp_path / "invalid_sizes.ico",
            sizes=set(),  # Empty set
        )
        runner2.run()  # Should handle gracefully

        # Test completed without exceptions
        assert True

    def test_quality_setting_effects(self, tmp_path: Path):
        """Test that different quality settings produce different results."""
        img_path = tmp_path / "quality_test.png"
        img = Image.new("RGBA", (256, 256), color=(128, 64, 192, 255))
        # Add some detail to make quality differences more apparent
        draw = ImageDraw.Draw(img)
        for i in range(0, 256, 16):
            draw.line([(i, 0), (i, 256)], fill=(255, 255, 255, 128), width=2)
            draw.line([(0, i), (256, i)], fill=(255, 255, 255, 128), width=2)
        img.save(img_path)

        sizes = {64}
        outputs = {}

        # Generate with different qualities
        for quality in ["low", "medium", "high"]:
            output_file = tmp_path / f"quality_{quality}.ico"
            runner = ImageToIcoRunner(
                input_path=img_path,
                output_path=output_file,
                sizes=sizes,
                quality=quality,
            )
            runner.run()
            assert output_file.exists()
            outputs[quality] = output_file.stat().st_size

        # High quality should generally be larger than low quality
        # (though this isn't guaranteed due to compression algorithms)
        print(f"File sizes - Low: {outputs['low']}, Medium: {outputs['medium']}, High: {outputs['high']}")

        # At minimum, files should exist and have content
        for quality, size in outputs.items():
            assert size > 0, f"{quality} quality file should not be empty"


if __name__ == "__main__":
    # Allow running directly for debugging
    pytest.main([__file__, "-v"])
