import pytest
import numpy as np

from src.services.image_service import ImageService

image_service = ImageService()

@pytest.mark.parametrize("quality,max_size_ratio", [
    (10, 0.85),    # Low quality - smaller than JPEG equivalent
    (80, 0.99),   # Default quality - better compression than JPEG
    (100, 1.0),   # High quality - baseline
])
def test_encode_image_webp_quality(image_service, sample_images, quality, max_size_ratio):
    """Test WebP encoding with different quality settings."""
    # Get test image
    image = sample_images['rgb']

    # Encode with specified quality
    base64_str = image_service.encode_image_to_base64(image, quality=quality)

    # Basic validation
    assert isinstance(base64_str, str)
    assert base64_str.startswith('data:image/webp;base64,')

    # Compare sizes of different quality settings
    if quality != 100:  # Skip for baseline quality
        high_quality = image_service.encode_image_to_base64(image, quality=100)

        # Compare base64 string lengths (approximate size comparison)
        actual_ratio = len(base64_str) / len(high_quality)
        assert actual_ratio <= max_size_ratio, \
            f"Expected compression ratio <= {max_size_ratio}, got {actual_ratio}"

        # Also verify that lower quality actually results in smaller size
        assert actual_ratio < 1.0, \
            f"Lower quality ({quality}) should result in smaller size than quality=100"
@pytest.mark.parametrize("image_type,expected_success", [
    ('rgb', True),      # Standard RGB image
    ('gray', True),     # Grayscale should work
])
def test_encode_image_different_types(image_service, sample_images, image_type, expected_success):
    """Test encoding different image types to JPEG."""
    image = sample_images[image_type]

    if expected_success:
        base64_str = image_service.encode_image_to_base64(image)
        assert base64_str.startswith('data:image/webp;base64,')
    else:
        with pytest.raises(ValueError):
            image_service.encode_image_to_base64(image)

def test_encode_decode_jpeg_quality_visual(image_service, sample_images):
    """Test that encoded/decoded images maintain reasonable visual quality."""
    original = sample_images['rgb']

    # Encode and decode
    base64_str = image_service.encode_image_to_base64(original, quality=85)
    decoded = image_service.decode_base64_to_image(base64_str)

    # Images won't be exactly equal due to JPEG compression
    # but should be reasonably similar (using a more realistic threshold)
    diff = np.mean(np.abs(original.astype(float) - decoded.astype(float)))
    assert diff < 50.0, f"Difference too high: {diff}"

    # The correlation should be high (images should be recognizable)
    correlation = np.corrcoef(original.reshape(-1), decoded.reshape(-1))[0, 1]
    assert correlation > 0.8, f"Correlation too low: {correlation}"


def test_encode_image_size_ordering(image_service, sample_images):
    """Test that lower quality settings consistently produce smaller file sizes."""
    image = sample_images['rgb']

    # Encode at different quality levels
    sizes = {
        q: len(image_service.encode_image_to_base64(image, quality=q))
        for q in [10, 50, 85, 100]
    }

    # Verify that sizes increase with quality
    size_list = [sizes[q] for q in sorted(sizes.keys())]
    assert all(a <= b for a, b in zip(size_list, size_list[1:])), \
        f"File sizes should increase with quality. Sizes: {sizes}"


def test_encode_decode_image_roundtrip():
    """Test that encoding and then decoding an image preserves the image data."""
    image_service = ImageService()

    # Create test images with different characteristics
    test_cases = [
        {
            "name": "random_rgb",
            "image": np.random.randint(0, 256, (100, 100, 3), dtype=np.uint8),
            "description": "Random RGB image"
        },
        {
            "name": "solid_color",
            "image": np.ones((50, 50, 3), dtype=np.uint8) * 128,  # Gray image
            "description": "Solid color image"
        },
        {
            "name": "gradient",
            "image": np.repeat(np.linspace(0, 255, 100).reshape(100, 1).astype(np.uint8), 100, axis=1).reshape(100, 100, 1),
            "description": "Gradient image"
        }
    ]

    for case in test_cases:
        # For RGB images, test with different quality settings
        if len(case["image"].shape) == 3 and case["image"].shape[2] == 3:
            for quality in [85, 100]:
                # Encode and then decode
                encoded = image_service.encode_image_to_base64(case["image"], quality=quality)
                decoded = image_service.decode_base64_to_image(encoded)

                # Check shapes match
                assert decoded.shape == case["image"].shape, \
                    f"Shape mismatch for {case['description']} with quality={quality}"

                # For quality=100, the difference should be relatively small
                if quality == 100:
                    # Calculate mean absolute difference
                    diff = np.mean(np.abs(decoded.astype(float) - case["image"].astype(float)))

                    assert diff < 50.0, \
                        f"High difference ({diff}) for {case['description']} with quality={quality}"

                # For solid color images, we can't use correlation (it will be NaN)
                # Instead, check that the mean absolute difference is small
                if case["name"] == "solid_color":
                    diff = np.mean(np.abs(decoded.astype(float) - case["image"].astype(float)))
                    assert diff < 10.0, \
                        f"High difference ({diff}) for {case['description']} with quality={quality}"
                else:
                    # For other images, the decoded image should be recognizable
                    # (correlation coefficient should be high enough)
                    correlation = np.corrcoef(
                        case["image"].reshape(-1),
                        decoded.reshape(-1)
                    )[0, 1]
                    assert correlation > 0.5, \
                        f"Low correlation ({correlation}) for {case['description']} with quality={quality}"
        else:
            # For non-RGB images, just test basic encoding/decoding
            encoded = image_service.encode_image_to_base64(case["image"])
            decoded = image_service.decode_base64_to_image(encoded)

            # Check shapes match (accounting for grayscale to RGB conversion)
            if len(case["image"].shape) == 2 or case["image"].shape[2] == 1:
                # Grayscale images get converted to RGB
                expected_shape = case["image"].shape[:2] + (3,)
                assert decoded.shape == expected_shape, \
                    f"Shape mismatch for {case['description']}"
            else:
                assert decoded.shape == case["image"].shape, \
                    f"Shape mismatch for {case['description']}"
