"""
Tests for optimized pagan avatar generator
"""

import hashlib
import time
import unittest
from PIL import Image
from io import BytesIO
from django.test import TestCase
import pagan

from .pagan_optimized import (
    OptimizedPagan,
    create_optimized_pagan,
    get_pagan_cache_info,
    clear_pagan_cache,
)


class TestOptimizedPagan(TestCase):
    """Test optimized pagan functionality"""

    def setUp(self):
        """Clear cache before each test"""
        clear_pagan_cache()

    def test_pagan_generation(self):
        """Test basic optimized pagan generation"""
        digest = hashlib.md5(b"test@example.com").hexdigest()

        img = OptimizedPagan.generate_optimized(digest, 80)

        self.assertIsNotNone(img)
        self.assertIsInstance(img, Image.Image)
        self.assertEqual(img.size, (80, 80))
        self.assertEqual(img.mode, "RGBA")

    def test_deterministic_generation(self):
        """Test that same digest produces identical images"""
        digest = hashlib.md5(b"deterministic@example.com").hexdigest()

        img1 = OptimizedPagan.generate_optimized(digest, 80)
        img2 = OptimizedPagan.generate_optimized(digest, 80)

        # Convert to bytes for comparison
        data1 = BytesIO()
        img1.save(data1, format="PNG")

        data2 = BytesIO()
        img2.save(data2, format="PNG")

        self.assertEqual(data1.getvalue(), data2.getvalue())

    def test_different_digests_produce_different_images(self):
        """Test that different digests produce different images"""
        digest1 = hashlib.md5(b"user1@example.com").hexdigest()
        digest2 = hashlib.md5(b"user2@example.com").hexdigest()

        img1 = OptimizedPagan.generate_optimized(digest1, 80)
        img2 = OptimizedPagan.generate_optimized(digest2, 80)

        # Convert to bytes for comparison
        data1 = BytesIO()
        img1.save(data1, format="PNG")

        data2 = BytesIO()
        img2.save(data2, format="PNG")

        self.assertNotEqual(data1.getvalue(), data2.getvalue())

    def test_compatibility_with_original(self):
        """Test that optimized version produces identical results to original"""
        digest = hashlib.md5(b"compatibility@example.com").hexdigest()

        # Generate with original pagan
        original_avatar = pagan.Avatar(digest)
        original_img = original_avatar.img.resize((80, 80), Image.LANCZOS)

        # Generate with optimized version
        optimized_img = OptimizedPagan.generate_optimized(digest, 80)

        # Images should be identical
        self.assertEqual(original_img.size, optimized_img.size)
        self.assertEqual(original_img.mode, optimized_img.mode)

        # Convert to bytes for pixel-perfect comparison
        original_data = BytesIO()
        original_img.save(original_data, format="PNG")

        optimized_data = BytesIO()
        optimized_img.save(optimized_data, format="PNG")

        self.assertEqual(original_data.getvalue(), optimized_data.getvalue())

    def test_caching_functionality(self):
        """Test that caching works correctly"""
        digest = hashlib.md5(b"cache_test@example.com").hexdigest()

        # Clear cache and check initial stats
        clear_pagan_cache()
        initial_stats = get_pagan_cache_info()
        self.assertEqual(initial_stats["hits"], 0)
        self.assertEqual(initial_stats["misses"], 0)

        # First generation (should be cache miss)
        img1 = OptimizedPagan.generate_optimized(digest, 80)
        stats_after_first = get_pagan_cache_info()
        self.assertEqual(stats_after_first["misses"], 1)
        self.assertEqual(stats_after_first["hits"], 0)

        # Second generation (should be cache hit)
        img2 = OptimizedPagan.generate_optimized(digest, 80)
        stats_after_second = get_pagan_cache_info()
        self.assertEqual(stats_after_second["misses"], 1)
        self.assertEqual(stats_after_second["hits"], 1)

        # Images should be identical
        data1 = BytesIO()
        img1.save(data1, format="PNG")

        data2 = BytesIO()
        img2.save(data2, format="PNG")

        self.assertEqual(data1.getvalue(), data2.getvalue())

    def test_different_sizes(self):
        """Test pagan generation at different sizes"""
        digest = hashlib.md5(b"sizes@example.com").hexdigest()
        sizes = [40, 80, 120, 200]

        for size in sizes:
            with self.subTest(size=size):
                img = OptimizedPagan.generate_optimized(digest, size)
                self.assertEqual(img.size, (size, size))

    def test_cache_size_limit(self):
        """Test that cache respects size limits"""
        # Set a small cache size for testing
        original_size = OptimizedPagan._max_cache_size
        OptimizedPagan._max_cache_size = 3

        try:
            clear_pagan_cache()

            # Generate more avatars than cache size
            for i in range(6):
                digest = hashlib.md5(
                    f"cache_limit_{i}@example.com".encode()
                ).hexdigest()
                OptimizedPagan.generate_optimized(digest, 80)

            # Cache size should not exceed limit
            stats = get_pagan_cache_info()
            self.assertLessEqual(stats["size"], 3)

        finally:
            # Restore original cache size
            OptimizedPagan._max_cache_size = original_size

    def test_create_optimized_pagan_function(self):
        """Test the convenience function"""
        digest = hashlib.md5(b"function_test@example.com").hexdigest()

        data = create_optimized_pagan(digest, 80)

        self.assertIsInstance(data, BytesIO)

        # Should be able to load as image
        data.seek(0)
        img = Image.open(data)
        self.assertEqual(img.size, (80, 80))
        self.assertEqual(img.mode, "RGBA")

    def test_error_handling(self):
        """Test error handling with invalid input"""
        # Test with invalid digest (should not crash)
        try:
            img = OptimizedPagan.generate_optimized("", 80)
            # Should either return None or a valid image
            if img is not None:
                self.assertIsInstance(img, Image.Image)
        except Exception:
            self.fail("Optimized pagan should handle errors gracefully")

    def test_performance_improvement(self):
        """Test that optimization provides performance improvement"""
        digest = hashlib.md5(b"performance@example.com").hexdigest()
        iterations = 5

        # Test original pagan performance
        original_times = []
        for _ in range(iterations):
            start_time = time.time()
            avatar = pagan.Avatar(digest)
            img = avatar.img.resize((80, 80), Image.LANCZOS)
            data = BytesIO()
            img.save(data, format="PNG")
            end_time = time.time()
            original_times.append((end_time - start_time) * 1000)

        # Clear cache and test optimized performance (first run - cache miss)
        clear_pagan_cache()
        optimized_first_times = []
        for _ in range(iterations):
            start_time = time.time()
            data = create_optimized_pagan(digest, 80)
            end_time = time.time()
            optimized_first_times.append((end_time - start_time) * 1000)

        # Test optimized performance (subsequent runs - cache hits)
        optimized_cached_times = []
        for _ in range(iterations):
            start_time = time.time()
            data = create_optimized_pagan(digest, 80)
            end_time = time.time()
            optimized_cached_times.append((end_time - start_time) * 1000)

        original_avg = sum(original_times) / len(original_times)
        optimized_cached_avg = sum(optimized_cached_times) / len(optimized_cached_times)

        print("\nPerformance Comparison:")
        print(f"Original average: {original_avg:.2f}ms")
        print(f"Optimized (cached): {optimized_cached_avg:.2f}ms")

        if optimized_cached_avg > 0:
            improvement = original_avg / optimized_cached_avg
            print(f"Improvement: {improvement:.1f}x faster")

            # Should be significantly faster with caching
            self.assertGreater(
                improvement, 10, "Optimization should provide significant improvement"
            )

    def test_cache_stats(self):
        """Test cache statistics tracking"""
        clear_pagan_cache()

        digest1 = hashlib.md5(b"stats1@example.com").hexdigest()
        digest2 = hashlib.md5(b"stats2@example.com").hexdigest()

        # Generate first avatar (cache miss)
        OptimizedPagan.generate_optimized(digest1, 80)

        # Generate second avatar (cache miss)
        OptimizedPagan.generate_optimized(digest2, 80)

        # Generate first avatar again (cache hit)
        OptimizedPagan.generate_optimized(digest1, 80)

        stats = get_pagan_cache_info()

        self.assertEqual(stats["misses"], 2)
        self.assertEqual(stats["hits"], 1)
        self.assertEqual(stats["size"], 2)
        self.assertIn("hit_rate", stats)


class TestPaganPerformance(TestCase):
    """Performance-focused tests for pagan optimization"""

    def test_bulk_generation_performance(self):
        """Test performance with multiple generations"""
        clear_pagan_cache()

        # Generate multiple pagan avatars
        test_count = 20
        digests = [
            hashlib.md5(f"bulk{i}@example.com".encode()).hexdigest()
            for i in range(test_count)
        ]

        start_time = time.time()
        for digest in digests:
            create_optimized_pagan(digest, 80)
        end_time = time.time()

        total_time = (end_time - start_time) * 1000  # ms
        avg_time = total_time / test_count

        print(f"Bulk generation: {test_count} pagan avatars in {total_time:.1f}ms")
        print(f"Average per avatar: {avg_time:.2f}ms")

        # Should average under 100ms per avatar (cache misses are still high, but cache hits are much faster)
        self.assertLess(
            avg_time, 100.0, f"Bulk generation too slow: {avg_time:.2f}ms avg"
        )

    def test_cache_hit_performance(self):
        """Test performance improvement with cache hits"""
        digest = hashlib.md5(b"cache_perf@example.com").hexdigest()

        # First generation (cache miss)
        start_time = time.time()
        create_optimized_pagan(digest, 80)
        first_time = (time.time() - start_time) * 1000

        # Second generation (cache hit)
        start_time = time.time()
        create_optimized_pagan(digest, 80)
        second_time = (time.time() - start_time) * 1000

        print(f"First generation (miss): {first_time:.2f}ms")
        print(f"Second generation (hit): {second_time:.2f}ms")

        if second_time > 0:
            improvement = first_time / second_time
            print(f"Cache hit improvement: {improvement:.1f}x faster")

            # Cache hits should be much faster
            self.assertGreater(
                improvement, 5, "Cache hits should provide significant speedup"
            )


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