"""Tests for img2ico functionality."""

from __future__ import annotations

import tempfile
from pathlib import Path

import pytest
from PIL import Image

from pytola_office.img2ico.cli import (
    ImageToIcoRunner,
    is_valid_image,
    main,
    parse_sizes,
)


class TestParseSizes:
    """Tests for parse_sizes function."""

    def test_parse_sizes_valid_single(self) -> None:
        """Test parsing single size."""
        result = parse_sizes("32")
        assert result == {32}

    def test_parse_sizes_valid_multiple(self) -> None:
        """Test parsing multiple comma-separated sizes."""
        result = parse_sizes("16,32,48,256")
        assert result == {16, 32, 48, 256}

    def test_parse_sizes_with_spaces(self) -> None:
        """Test parsing sizes with spaces."""
        result = parse_sizes("16, 32, 48, 256")
        assert result == {16, 32, 48, 256}

    def test_parse_sizes_invalid_empty(self) -> None:
        """Test parsing empty string raises ValueError."""
        with pytest.raises(ValueError, match="No valid sizes provided"):
            parse_sizes("")

    def test_parse_sizes_invalid_non_integer(self) -> None:
        """Test parsing non-integer values raises ValueError."""
        with pytest.raises(ValueError, match="Invalid sizes format"):
            parse_sizes("16,abc,32")

    def test_parse_sizes_invalid_negative(self) -> None:
        """Test parsing negative values."""
        # Negative sizes are technically valid integers, but may not make sense
        result = parse_sizes("-16,32")
        assert -16 in result
        assert 32 in result


class TestIsValidImage:
    """Tests for is_valid_image function."""

    def test_is_valid_image_with_valid_jpg(self) -> None:
        """Test is_valid_image with a valid JPG file."""
        with tempfile.NamedTemporaryFile(suffix=".jpg", delete=False) as tmp:
            # Create a valid JPEG image
            img = Image.new("RGB", (100, 100), color="red")
            img.save(tmp.name, "JPEG")

        result = is_valid_image(Path(tmp.name))
        assert result is True

    def test_is_valid_image_with_valid_png(self) -> None:
        """Test is_valid_image with a valid PNG file."""
        with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp:
            # Create a valid PNG image
            img = Image.new("RGB", (100, 100), color="blue")
            img.save(tmp.name, "PNG")

        result = is_valid_image(Path(tmp.name))
        assert result is True

    def test_is_valid_image_with_valid_gif(self) -> None:
        """Test is_valid_image with a valid GIF file."""
        with tempfile.NamedTemporaryFile(suffix=".gif", delete=False) as tmp:
            img = Image.new("P", (50, 50))
            img.save(tmp.name, "GIF")

        result = is_valid_image(Path(tmp.name))
        assert result is True

    def test_is_valid_image_with_valid_bmp(self) -> None:
        """Test is_valid_image with a valid BMP file."""
        with tempfile.NamedTemporaryFile(suffix=".bmp", delete=False) as tmp:
            img = Image.new("RGB", (50, 50))
            img.save(tmp.name, "BMP")

        result = is_valid_image(Path(tmp.name))
        assert result is True

    def test_is_valid_image_with_invalid_extension(self) -> None:
        """Test is_valid_image with unsupported file extension."""
        with tempfile.NamedTemporaryFile(suffix=".txt", delete=False) as tmp:
            tmp.write(b"This is not an image file")

        result = is_valid_image(Path(tmp.name))
        assert result is False

    def test_is_valid_image_with_nonexistent_file(self) -> None:
        """Test is_valid_image with nonexistent file."""
        fake_path = Path("nonexistent_file.jpg")
        result = is_valid_image(fake_path)
        assert result is False

    def test_is_valid_image_with_empty_file(self) -> None:
        """Test is_valid_image with empty file."""
        with tempfile.NamedTemporaryFile(suffix=".jpg", delete=False) as tmp:
            # Create an empty file
            pass

        result = is_valid_image(Path(tmp.name))
        assert result is False

    def test_is_valid_image_with_corrupted_image(self) -> None:
        """Test is_valid_image with corrupted image content."""
        with tempfile.NamedTemporaryFile(suffix=".jpg", delete=False) as tmp:
            # Write some random bytes that aren't a valid image
            tmp.write(b"not an image file at all")

        result = is_valid_image(Path(tmp.name))
        assert result is False


class TestImageToIcoRunner:
    """Tests for ImageToIcoRunner class."""

    @pytest.fixture
    def sample_image(self, tmp_path) -> Path:
        """Create a sample image for testing."""
        img_path = tmp_path / "test_image.png"
        img = Image.new("RGBA", (100, 100), color=(255, 0, 0, 255))
        img.save(img_path, "PNG")
        return img_path

    @pytest.fixture
    def sample_svg(self, tmp_path) -> Path:
        """Create a sample SVG file for testing."""
        svg_path = tmp_path / "test_image.svg"
        svg_content = """<?xml version="1.0" encoding="UTF-8"?>
<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">
  <circle cx="50" cy="50" r="40" fill="red"/>
</svg>"""
        svg_path.write_text(svg_content)
        return svg_path

    def test_runner_initialization(self, sample_image: Path) -> None:
        """Test ImageToIcoRunner initialization."""
        runner = ImageToIcoRunner(input_path=sample_image)

        assert runner.input_path == sample_image
        assert runner.output_path is None
        # sizes can be None initially, will use DEFAULT_SIZES during conversion
        assert runner.sizes is None or len(runner.sizes) > 0

    def test_runner_with_custom_sizes(self, sample_image: Path) -> None:
        """Test ImageToIcoRunner with custom sizes."""
        custom_sizes = {16, 32, 64}
        runner = ImageToIcoRunner(input_path=sample_image, sizes=custom_sizes)

        assert runner.sizes == custom_sizes

    def test_runner_with_quality_setting(self, sample_image: Path) -> None:
        """Test ImageToIcoRunner with different quality settings."""
        for quality in ["low", "medium", "high"]:
            runner = ImageToIcoRunner(input_path=sample_image, quality=quality)
            assert runner.quality == quality

    def test_run_single_file_conversion(self, sample_image, tmp_path) -> None:
        """Test converting a single image file to ICO."""
        output_path = tmp_path / "output.ico"
        runner = ImageToIcoRunner(input_path=sample_image, output_path=output_path)

        # Run conversion
        runner.run()

        # Verify output file was created
        assert output_path.exists()
        assert output_path.suffix == ".ico"

    def test_run_with_output_directory(self, sample_image, tmp_path) -> None:
        """Test converting with output directory."""
        output_dir = tmp_path / "ico_output"
        runner = ImageToIcoRunner(input_path=sample_image, output_path=output_dir)

        runner.run()

        # Should create file in output directory
        expected_output = output_dir / f"{sample_image.stem}.ico"
        assert expected_output.exists()

    def test_run_directory_conversion(self, tmp_path: Path) -> None:
        """Test converting multiple images in a directory."""
        # Create multiple test images
        input_dir = tmp_path / "input_images"
        input_dir.mkdir()

        for i in range(3):
            img = Image.new("RGB", (100, 100), color=(i * 50, 0, 0))
            img_path = input_dir / f"image_{i}.png"
            img.save(img_path, "PNG")

        runner = ImageToIcoRunner(input_path=input_dir)
        runner.run()

        # Check that ICO files were created
        output_dir = input_dir / "ico_output"
        assert output_dir.exists()
        ico_files = list(output_dir.glob("*.ico"))
        assert len(ico_files) == 3

    def test_run_with_nonexistent_input(self, tmp_path: Path) -> None:
        """Test running with nonexistent input path."""
        fake_path = tmp_path / "nonexistent.png"
        runner = ImageToIcoRunner(input_path=fake_path)

        # Should log error but not raise exception
        runner.run()

    def test_run_with_invalid_image(self, tmp_path: Path) -> None:
        """Test running with invalid image file."""
        invalid_file = tmp_path / "invalid.jpg"
        invalid_file.write_bytes(b"not an image")

        runner = ImageToIcoRunner(input_path=invalid_file)
        runner.run()

        # Should not create output file
        assert not (tmp_path / "invalid.ico").exists()

    def test_resize_for_icon_maintains_aspect_ratio(self, sample_image: Path) -> None:
        """Test that resize maintains aspect ratio."""
        runner = ImageToIcoRunner(input_path=sample_image)

        # Load the test image
        with Image.open(str(sample_image)) as img:
            # Resize to different sizes
            for size in [16, 32, 64, 128]:
                resized = runner._resize_for_icon(img, size)
                # Check that the icon is square
                assert resized.width == size
                assert resized.height == size

    def test_resize_for_icon_quality_modes(self, sample_image: Path) -> None:
        """Test resize with different quality modes."""
        runner_low = ImageToIcoRunner(input_path=sample_image, quality="low")
        runner_medium = ImageToIcoRunner(input_path=sample_image, quality="medium")
        runner_high = ImageToIcoRunner(input_path=sample_image, quality="high")

        with Image.open(str(sample_image)) as img:
            # All should produce valid output
            for runner in [runner_low, runner_medium, runner_high]:
                resized = runner._resize_for_icon(img, 32)
                assert resized.width == 32
                assert resized.height == 32

    @pytest.mark.skip(reason="SVG support requires cairosvg which may not be installed")
    def test_convert_svg_file(self, sample_svg, tmp_path) -> None:
        """Test converting SVG file to ICO."""
        output_path = tmp_path / "output.ico"
        runner = ImageToIcoRunner(input_path=sample_svg, output_path=output_path)

        runner.run()

        # Verify output file was created (if cairosvg is available)
        # This test will be skipped if cairosvg is not installed
        try:
            import cairosvg  # noqa: F401

            assert output_path.exists()
        except ImportError:
            pytest.skip("cairosvg not available")

    def test_image_files_property(self, tmp_path: Path) -> None:
        """Test the image_files cached property."""
        # Create multiple test images
        for i in range(3):
            img = Image.new("RGB", (100, 100), color=(i * 50, 0, 0))
            img_path = tmp_path / f"image_{i}.png"
            img.save(img_path, "PNG")

        # Create a non-image file
        (tmp_path / "readme.txt").write_text("not an image")

        runner = ImageToIcoRunner(input_path=tmp_path)
        image_files = runner.image_files

        # Should only include valid image files
        assert len(image_files) == 3
        assert all(f.suffix == ".png" for f in image_files)

    def test_create_genuine_multisize_ico(self, sample_image, tmp_path) -> None:
        """Test creating genuine multisize ICO file."""
        runner = ImageToIcoRunner(input_path=sample_image)

        # Create test images for different sizes
        sizes = [16, 32, 64]
        icon_images = []

        with Image.open(str(sample_image)) as img:
            for size in sizes:
                resized = runner._resize_for_icon(img, size)
                icon_images.append(resized)

        output_file = tmp_path / "multisize.ico"

        # Call the internal method directly
        runner._create_genuine_multisize_ico(icon_images, sizes, output_file)

        # Verify file was created
        assert output_file.exists()
        assert output_file.stat().st_size > 0


class TestMainFunction:
    """Tests for main function."""

    def test_main_with_no_arguments(self, monkeypatch: pytest.MonkeyPatch) -> None:
        """Test main function with no arguments (uses current directory)."""
        # Mock sys.argv
        monkeypatch.setattr("sys.argv", ["img2ico"])

        # Should not raise exception
        main()

    def test_main_with_invalid_sizes(self, monkeypatch, caplog) -> None:
        """Test main function with invalid sizes argument."""
        monkeypatch.setattr("sys.argv", ["img2ico", "--sizes", "invalid"])

        # Should handle error gracefully
        main()

        # Should log error about invalid sizes
        assert "Invalid sizes" in caplog.text or "error" in caplog.text.lower()

    def test_main_with_nonexistent_input(self, monkeypatch, caplog) -> None:
        """Test main function with nonexistent input path."""
        monkeypatch.setattr("sys.argv", ["img2ico", "/nonexistent/path"])

        # Should handle error gracefully
        main()

        # Should log error about nonexistent path
        assert "does not exist" in caplog.text.lower()
