"""Tests for img2pdf functionality."""

import json
import tempfile
from pathlib import Path
from unittest.mock import patch

import pytest
from PIL import Image

from pytola.office.img2pdf.img2pdf import (
    ImageToPdfConfig,
    ImageToPDFRunner,
    is_valid_image,
    main,
    parse_args,
)


class TestImageToPdfConfig:
    """Tests for ImageToPdfConfig class."""

    def test_config_initialization_defaults(self):
        """Test config initialization with default values."""
        config = ImageToPdfConfig()
        assert config.DPI == 300
        assert isinstance(config.EXTENSIONS, set)
        expected_extensions = {
            ".jpg",
            ".jpeg",
            ".png",
            ".gif",
            ".bmp",
            ".webp",
            ".tiff",
            ".ico",
        }
        assert expected_extensions == config.EXTENSIONS

    def test_config_post_init_with_none_extensions(self):
        """Test __post_init__ when EXTENSIONS is None."""
        # Directly test the post init behavior
        config = ImageToPdfConfig.__new__(ImageToPdfConfig)  # Create without calling __init__
        config.DPI = 300
        config.EXTENSIONS = None

        # Call __post_init__ manually
        config.__post_init__()

        assert config.EXTENSIONS is not None
        assert isinstance(config.EXTENSIONS, set)
        expected_extensions = {
            ".jpg",
            ".jpeg",
            ".png",
            ".gif",
            ".bmp",
            ".webp",
            ".tiff",
            ".ico",
        }
        assert expected_extensions == config.EXTENSIONS

    def test_config_save_method(self):
        """Test saving configuration to file."""
        with tempfile.TemporaryDirectory() as temp_dir:
            # Create a temporary config file path
            temp_config_file = Path(temp_dir) / "img2pdf.json"

            # Create config with modified values
            config = ImageToPdfConfig()
            config.DPI = 150
            config.EXTENSIONS = {".jpg", ".png"}

            # Mock CONFIG_FILE to use our temp file
            with patch("pytola.office.img2pdf.img2pdf.CONFIG_FILE", temp_config_file):
                config.save()

                # Verify file was created and contains correct data
                assert temp_config_file.exists()
                saved_data = json.loads(temp_config_file.read_text())
                assert saved_data["DPI"] == 150
                assert set(saved_data["EXTENSIONS"]) == {".jpg", ".png"}

    def test_config_load_with_invalid_file(self):
        """Test config loading with invalid JSON file."""
        with tempfile.TemporaryDirectory() as temp_dir:
            temp_config_file = Path(temp_dir) / "img2pdf.json"
            temp_config_file.write_text("invalid json content")

            with patch("pytola.office.img2pdf.img2pdf.CONFIG_FILE", temp_config_file):
                config = ImageToPdfConfig()
                # Should fall back to defaults
                assert config.DPI == 300


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

    def test_is_valid_image_with_valid_jpg(self):
        """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):
        """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_invalid_extension(self):
        """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):
        """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):
        """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):
        """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

    def test_is_valid_image_os_error_handling(self):
        """Test is_valid_image with file access error."""
        with patch("pathlib.Path.stat", side_effect=OSError("Permission denied")):
            result = is_valid_image(Path("/restricted/file.jpg"))
            assert result is False


class TestImageToPDFRunner:
    """Tests for ImageToPDFRunner class."""

    def test_runner_initialization(self):
        """Test ImageToPDFRunner initialization."""
        with tempfile.TemporaryDirectory() as temp_dir:
            temp_path = Path(temp_dir)
            runner = ImageToPDFRunner(root_dir=temp_path, dpi=150, normalize=True)

            assert runner.root_dir == temp_path
            assert runner.dpi == 150
            assert runner.normalize is True

    def test_page_size_calculation(self):
        """Test page size calculation based on DPI."""
        with tempfile.TemporaryDirectory() as temp_dir:
            temp_path = Path(temp_dir)
            runner = ImageToPDFRunner(root_dir=temp_path, dpi=150, normalize=True)

            # Expected size: (8.27 * dpi, 11.69 * dpi)
            expected_width = int(8.27 * 150)
            expected_height = int(11.69 * 150)
            assert runner.page_size == (expected_width, expected_height)

    def test_size_property(self):
        """Test size property calculation."""
        with tempfile.TemporaryDirectory() as temp_dir:
            temp_path = Path(temp_dir)
            runner = ImageToPDFRunner(root_dir=temp_path, dpi=100, normalize=True)

            # Expected size: (8.27 * dpi, 11.69 * dpi)
            expected_width = int(8.27 * 100)
            expected_height = int(11.69 * 100)
            assert runner.size == (expected_width, expected_height)

    def test_output_pdf_path(self):
        """Test output PDF path generation."""
        with tempfile.TemporaryDirectory() as temp_dir:
            temp_path = Path(temp_dir)
            runner = ImageToPDFRunner(root_dir=temp_path, dpi=150, normalize=True)

            expected_pdf_path = temp_path / f"{temp_path.name}.pdf"
            assert runner.output_pdf == expected_pdf_path

    def test_convert_single_image(self):
        """Test converting a single image."""
        with tempfile.TemporaryDirectory() as temp_dir:
            temp_path = Path(temp_dir)

            # Create a sample image
            img_path = temp_path / "sample.jpg"
            img = Image.new("RGB", (100, 100), color="green")
            img.save(img_path, "JPEG")

            runner = ImageToPDFRunner(root_dir=temp_path, dpi=72, normalize=False)

            # Convert the image
            converted = runner._convert(img_path, normalize=False)
            assert converted is not None
            assert converted.mode == "RGB"

    def test_convert_image_with_transparency(self):
        """Test converting an image with transparency."""
        with tempfile.TemporaryDirectory() as temp_dir:
            temp_path = Path(temp_dir)

            # Create an image with transparency (RGBA mode)
            img_path = temp_path / "transparent.png"
            img = Image.new("RGBA", (100, 100), (255, 0, 0, 128))  # Red with transparency
            img.save(img_path, "PNG")

            runner = ImageToPDFRunner(root_dir=temp_path, dpi=72, normalize=False)

            # Convert the image - should handle transparency properly
            converted = runner._convert(img_path, normalize=False)
            assert converted is not None
            assert converted.mode == "RGB"

    def test_convert_image_normalize_enabled(self):
        """Test converting an image with normalize enabled."""
        with tempfile.TemporaryDirectory() as temp_dir:
            temp_path = Path(temp_dir)

            # Create a sample image
            img_path = temp_path / "sample.jpg"
            img = Image.new("RGB", (100, 200), color="green")
            img.save(img_path, "JPEG")

            runner = ImageToPDFRunner(root_dir=temp_path, dpi=72, normalize=True)

            # Convert the image with normalization
            converted = runner._convert(img_path, normalize=True)
            assert converted is not None
            assert converted.mode == "RGB"

    def test_convert_image_with_failure(self):
        """Test converting an image that fails to open."""
        with tempfile.TemporaryDirectory() as temp_dir:
            temp_path = Path(temp_dir)

            # Create a file that isn't actually an image
            fake_img_path = temp_path / "fake.jpg"
            fake_img_path.write_bytes(b"this is not an image")

            runner = ImageToPDFRunner(root_dir=temp_path, dpi=72, normalize=False)

            # Convert should return None for invalid image
            converted = runner._convert(fake_img_path, normalize=False)
            assert converted is None

    def test_auto_rotate_image_portrait_to_landscape(self):
        """Test auto rotation of landscape image to portrait."""
        with tempfile.TemporaryDirectory() as temp_dir:
            temp_path = Path(temp_dir)
            runner = ImageToPDFRunner(root_dir=temp_path, dpi=72, normalize=True)

            # Create a landscape image (width > height)
            landscape_img = Image.new("RGB", (200, 100), color="yellow")

            rotated = runner._auto_rotate_image(landscape_img)

            # Since width > height, it should be rotated 90 degrees,
            # changing dimensions from (200, 100) to (100, 200)
            assert rotated.size == (100, 200)

    def test_auto_rotate_image_landscape_no_rotation(self):
        """Test that portrait image is not rotated."""
        with tempfile.TemporaryDirectory() as temp_dir:
            temp_path = Path(temp_dir)
            runner = ImageToPDFRunner(root_dir=temp_path, dpi=72, normalize=True)

            # Create a portrait image (height > width)
            portrait_img = Image.new("RGB", (100, 200), color="purple")

            rotated = runner._auto_rotate_image(portrait_img)

            # Since width <= height, it should not be rotated
            assert rotated.size == (100, 200)

    def test_auto_scale_image_when_smaller_than_page(self):
        """Test auto scaling of image smaller than page size."""
        with tempfile.TemporaryDirectory() as temp_dir:
            temp_path = Path(temp_dir)
            runner = ImageToPDFRunner(root_dir=temp_path, dpi=72, normalize=True)

            # Create a small image (smaller than page size at 72 DPI)
            # Page size at 72 DPI: (8.27*72, 11.69*72) = (595, 841)
            small_img = Image.new("RGB", (100, 100), color="orange")

            scaled = runner._auto_scale_image(small_img)

            # The scaled image should be larger than the original
            assert scaled.size[0] >= small_img.size[0]
            assert scaled.size[1] >= small_img.size[1]

    def test_image_files_property_with_no_valid_images(self):
        """Test image_files property when no valid images are found."""
        with tempfile.TemporaryDirectory() as temp_dir:
            temp_path = Path(temp_dir)

            # Create only invalid files
            invalid_file = temp_path / "not_an_image.txt"
            invalid_file.write_text("not an image")

            runner = ImageToPDFRunner(root_dir=temp_path, dpi=72, normalize=False)

            # No valid images should be found
            image_files = runner.image_files
            assert len(image_files) == 0

    def test_converted_images_property(self):
        """Test converted_images property with valid images."""
        with tempfile.TemporaryDirectory() as temp_dir:
            temp_path = Path(temp_dir)

            # Create a valid image
            img_path = temp_path / "test.jpg"
            img = Image.new("RGB", (100, 100), color="cyan")
            img.save(img_path, "JPEG")

            runner = ImageToPDFRunner(root_dir=temp_path, dpi=72, normalize=False)

            # Should convert the image
            converted_images = runner.converted_images
            assert len(converted_images) == 1
            assert isinstance(converted_images[0], Image.Image)

    def test_run_with_no_images(self):
        """Test run method when no images are found."""
        with tempfile.TemporaryDirectory() as temp_dir:
            temp_path = Path(temp_dir)

            runner = ImageToPDFRunner(root_dir=temp_path, dpi=72, normalize=False)

            # Should handle gracefully when no images to convert
            runner.run()
            # Check that no PDF was created since no images
            temp_path / f"{temp_path.name}.pdf"


def test_integration_full_conversion():
    """Integration test for full image to PDF conversion process."""
    with tempfile.TemporaryDirectory() as temp_dir:
        temp_path = Path(temp_dir)

        # Create some test images
        img1 = temp_path / "page1.jpg"
        img2 = temp_path / "page2.png"
        Image.new("RGB", (200, 300), color="white").save(img1, "JPEG")
        Image.new("RGB", (200, 300), color="black").save(img2, "PNG")

        # Run the conversion
        runner = ImageToPDFRunner(root_dir=temp_path, dpi=72, normalize=False)
        runner.run()

        # Check if PDF was created
        expected_pdf = temp_path / f"{temp_path.name}.pdf"
        assert expected_pdf.exists()


def test_parse_args_default_values():
    """Test command line argument parsing with default values."""
    with patch("sys.argv", ["img2pdf"]):  # No arguments provided
        args = parse_args()
        assert args.dpi == 300
        # When no normalize flags are provided, normalize defaults to False
        # because --no-normalize (store_false) comes after --normalize (store_true)
        # in the argparser definition, so normalize gets the default value of False
        assert args.normalize is False  # Default value when no flag provided


def test_main_function_with_valid_directory():
    """Test main function with a valid directory."""
    with tempfile.TemporaryDirectory() as temp_dir:
        temp_path = Path(temp_dir)

        # Create an image file
        img_path = temp_path / "test.jpg"
        img = Image.new("RGB", (100, 100), color="red")
        img.save(img_path, "JPEG")

        # Test main function with the temp directory
        with patch("sys.argv", ["img2pdf", str(temp_path)]):
            main()  # Should run without errors

        # Check if PDF was created
        expected_pdf = temp_path / f"{temp_path.name}.pdf"
        assert expected_pdf.exists()


def test_main_function_with_invalid_directory():
    """Test main function with an invalid directory."""
    with patch("sys.argv", ["img2pdf", "/nonexistent/directory"]):
        # Capture logs to verify error message
        main()  # Should handle gracefully without crashing


def test_main_function_with_file_instead_of_directory():
    """Test main function with a file instead of directory."""
    with tempfile.NamedTemporaryFile(delete=False) as tmp:
        tmp.write(b"not a directory")
        temp_file_path = tmp.name

    try:
        with patch("sys.argv", ["img2pdf", temp_file_path]):
            main()  # Should handle gracefully
    finally:
        Path(temp_file_path).unlink()  # Clean up


if __name__ == "__main__":
    pytest.main([__file__])
