from django.test import TestCase, override_settings
from django.core.files.uploadedfile import SimpleUploadedFile
from django.contrib.auth import get_user_model
from django.contrib.contenttypes.models import ContentType
from django.utils import timezone
from django.conf import settings
from django.db import models
from datetime import datetime
from PIL import Image
import piexif  # type: ignore
import io
import shutil
import tempfile
import os
import logging
from typing import Optional, Any, Generator, cast
from contextlib import contextmanager
from .models import Photo
from .storage import MediaStorage
from parks.models import Park, Company as Operator

User = get_user_model()
logger = logging.getLogger(__name__)


@override_settings(MEDIA_ROOT=tempfile.mkdtemp())
class PhotoModelTests(TestCase):
    test_media_root: str
    user: models.Model
    park: Park
    content_type: ContentType

    @classmethod
    def setUpClass(cls) -> None:
        super().setUpClass()
        cls.test_media_root = settings.MEDIA_ROOT

    @classmethod
    def tearDownClass(cls) -> None:
        try:
            shutil.rmtree(cls.test_media_root, ignore_errors=True)
        except Exception as e:
            logger.warning(f"Failed to clean up test media directory: {e}")
        super().tearDownClass()

    def setUp(self) -> None:
        self.user = self._create_test_user()
        self.park = self._create_test_park()
        self.content_type = ContentType.objects.get_for_model(Park)
        self._setup_test_directory()

    def tearDown(self) -> None:
        self._cleanup_test_directory()
        Photo.objects.all().delete()
        with self._reset_storage_state():
            pass

    def _create_test_user(self) -> models.Model:
        """Create a test user for the tests"""
        return User.objects.create_user(username="testuser", password="testpass123")

    def _create_test_park(self) -> Park:
        """Create a test park for the tests"""
        operator = Operator.objects.create(name="Test Operator")
        return Park.objects.create(
            name="Test Park", slug="test-park", operator=operator
        )

    def _setup_test_directory(self) -> None:
        """Set up test directory and clean any existing test files"""
        try:
            # Clean up any existing test park directory
            test_park_dir = os.path.join(settings.MEDIA_ROOT, "park", "test-park")
            if os.path.exists(test_park_dir):
                shutil.rmtree(test_park_dir, ignore_errors=True)

            # Create necessary directories
            os.makedirs(test_park_dir, exist_ok=True)

        except Exception as e:
            logger.warning(f"Failed to set up test directory: {e}")
            raise

    def _cleanup_test_directory(self) -> None:
        """Clean up test directories and files"""
        try:
            test_park_dir = os.path.join(settings.MEDIA_ROOT, "park", "test-park")
            if os.path.exists(test_park_dir):
                shutil.rmtree(test_park_dir, ignore_errors=True)
        except Exception as e:
            logger.warning(f"Failed to clean up test directory: {e}")

    @contextmanager
    def _reset_storage_state(self) -> Generator[None, None, None]:
        """Safely reset storage state"""
        try:
            MediaStorage.reset_counters()
            yield
        finally:
            MediaStorage.reset_counters()

    def create_test_image_with_exif(
        self, date_taken: Optional[datetime] = None, filename: str = "test.jpg"
    ) -> SimpleUploadedFile:
        """Helper method to create a test image with EXIF data"""
        image = Image.new("RGB", (100, 100), color="red")
        image_io = io.BytesIO()

        # Save image first without EXIF
        image.save(image_io, "JPEG")
        image_io.seek(0)

        if date_taken:
            # Create EXIF data
            exif_dict = {
                "0th": {},
                "Exif": {
                    piexif.ExifIFD.DateTimeOriginal: date_taken.strftime(
                        "%Y:%m:%d %H:%M:%S"
                    ).encode()
                },
            }
            exif_bytes = piexif.dump(exif_dict)

            # Insert EXIF into image
            image_with_exif = io.BytesIO()
            piexif.insert(exif_bytes, image_io.getvalue(), image_with_exif)
            image_with_exif.seek(0)
            image_data = image_with_exif.getvalue()
        else:
            image_data = image_io.getvalue()

        return SimpleUploadedFile(filename, image_data, content_type="image/jpeg")

    def test_filename_normalization(self) -> None:
        """Test that filenames are properly normalized"""
        with self._reset_storage_state():
            # Test with various problematic filenames
            test_cases = [
                ("test with spaces.jpg", "test-park_1.jpg"),
                ("TEST_UPPER.JPG", "test-park_2.jpg"),
                ("special@#chars.jpeg", "test-park_3.jpg"),
                ("no-extension", "test-park_4.jpg"),
                ("multiple...dots.jpg", "test-park_5.jpg"),
                ("très_açaí.jpg", "test-park_6.jpg"),  # Unicode characters
            ]

            for input_name, expected_suffix in test_cases:
                photo = Photo.objects.create(
                    image=self.create_test_image_with_exif(filename=input_name),
                    uploaded_by=self.user,
                    content_type=self.content_type,
                    object_id=self.park.pk,
                )

                # Check that the filename follows the normalized pattern
                self.assertTrue(
                    photo.image.name.endswith(expected_suffix),
                    f"Expected filename to end with {expected_suffix}, got {
                        photo.image.name}",
                )

                # Verify the path structure
                expected_path = f"park/{self.park.slug}/"
                self.assertTrue(
                    photo.image.name.startswith(expected_path),
                    f"Expected path to start with {expected_path}, got {
                        photo.image.name}",
                )

    def test_sequential_filename_numbering(self) -> None:
        """Test that sequential files get proper numbering"""
        with self._reset_storage_state():
            # Create multiple photos and verify numbering
            for i in range(1, 4):
                photo = Photo.objects.create(
                    image=self.create_test_image_with_exif(),
                    uploaded_by=self.user,
                    content_type=self.content_type,
                    object_id=self.park.pk,
                )

                expected_name = f"park/{self.park.slug}/test-park_{i}.jpg"
                self.assertEqual(
                    photo.image.name,
                    expected_name,
                    f"Expected {expected_name}, got {photo.image.name}",
                )

    def test_exif_date_extraction(self) -> None:
        """Test EXIF date extraction from uploaded photos"""
        test_date = datetime(2024, 1, 1, 12, 0, 0)
        image_file = self.create_test_image_with_exif(test_date)

        photo = Photo.objects.create(
            image=image_file,
            uploaded_by=self.user,
            content_type=self.content_type,
            object_id=self.park.pk,
        )

        if photo.date_taken:
            self.assertEqual(
                photo.date_taken.strftime("%Y-%m-%d %H:%M:%S"),
                test_date.strftime("%Y-%m-%d %H:%M:%S"),
            )
        else:
            self.skipTest("EXIF data extraction not supported in test environment")

    def test_photo_without_exif(self) -> None:
        """Test photo upload without EXIF data"""
        image_file = self.create_test_image_with_exif()

        photo = Photo.objects.create(
            image=image_file,
            uploaded_by=self.user,
            content_type=self.content_type,
            object_id=self.park.pk,
        )

        self.assertIsNone(photo.date_taken)

    def test_default_caption(self) -> None:
        """Test default caption generation"""
        photo = Photo.objects.create(
            image=self.create_test_image_with_exif(),
            uploaded_by=self.user,
            content_type=self.content_type,
            object_id=self.park.pk,
        )

        expected_prefix = f"Uploaded by {cast(Any, self.user).username} on"
        self.assertTrue(photo.caption.startswith(expected_prefix))

    def test_primary_photo_toggle(self) -> None:
        """Test primary photo functionality"""
        photo1 = Photo.objects.create(
            image=self.create_test_image_with_exif(),
            uploaded_by=self.user,
            content_type=self.content_type,
            object_id=self.park.pk,
            is_primary=True,
        )

        photo2 = Photo.objects.create(
            image=self.create_test_image_with_exif(),
            uploaded_by=self.user,
            content_type=self.content_type,
            object_id=self.park.pk,
            is_primary=True,
        )

        photo1.refresh_from_db()
        photo2.refresh_from_db()

        self.assertFalse(photo1.is_primary)
        self.assertTrue(photo2.is_primary)

    def test_date_taken_field(self) -> None:
        """Test date_taken field functionality"""
        test_date = timezone.now()
        photo = Photo.objects.create(
            image=self.create_test_image_with_exif(),
            uploaded_by=self.user,
            content_type=self.content_type,
            object_id=self.park.pk,
            date_taken=test_date,
        )

        self.assertEqual(photo.date_taken, test_date)
