import re

from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator
from django.db.models import Count, F, Prefetch, Q
from drf_spectacular.utils import OpenApiParameter, OpenApiTypes, extend_schema
from rest_framework import filters, viewsets
from rest_framework.permissions import AllowAny, IsAuthenticated
from rest_framework.response import Response

from api.models import (
    AlbumDate,
    AlbumPlace,
    AlbumThing,
    AlbumUser,
    Face,
    File,
    Person,
    Photo,
    User,
)
from api.models.photo_stack import PhotoStack
from api.serializers.album_date import (
    AlbumDateSerializer,
    IncompleteAlbumDateSerializer,
)
from api.serializers.album_place import (
    AlbumPlaceListSerializer,
    AlbumPlaceSerializer,
    GroupedPlacePhotosSerializer,
)
from api.serializers.album_thing import (
    AlbumThingListSerializer,
    AlbumThingSerializer,
    GroupedThingPhotosSerializer,
)
from api.serializers.album_user import (
    AlbumUserListSerializer,
    AlbumUserPublicSerializer,
    AlbumUserSerializer,
)
from api.serializers.person import GroupedPersonPhotosSerializer, PersonSerializer
from api.serializers.photos import PhotoSummarySerializer
from api.util import logger
from api.views.custom_api_view import ListViewSet
from api.views.pagination import (
    RegularResultsSetPagination,
    StandardResultsSetPagination,
)


# To-Do: Not used as far as I can tell, only in mobile app
@extend_schema(
    deprecated=True,
    description="This endpoint is deprecated. Use /api/persons instead.",
)
class AlbumPersonViewSet(viewsets.ModelViewSet):
    serializer_class = GroupedPersonPhotosSerializer

    def get_queryset(self):
        if self.request.user.is_anonymous:
            return Person.objects.none()

        return (
            Person.objects.annotate(
                photo_count=Count(
                    "faces", filter=Q(faces__photo__hidden=False), distinct=True
                )
            )
            .filter(Q(photo_count__gt=0))
            .prefetch_related(
                Prefetch(
                    "faces",
                    queryset=Face.objects.filter(Q(person__isnull=False)),
                )
            )
            .prefetch_related(
                Prefetch(
                    "faces__photo",
                    queryset=Photo.objects.filter(
                        Q(faces__photo__hidden=False) & Q(owner=self.request.user)
                    )
                    .distinct()
                    .order_by("-exif_timestamp")
                    .only("image_hash", "exif_timestamp", "rating", "public", "hidden"),
                )
            )
        )

    def retrieve(self, *args, **kwargs):
        queryset = self.get_queryset()
        logger.warning(args[0].__str__())
        albumid = re.findall(r"\'(.+?)\'", args[0].__str__())[0].split("/")[-2]
        serializer = GroupedPersonPhotosSerializer(
            queryset.filter(id=albumid).first(), context={"request": self.request}
        )
        return Response({"results": serializer.data})

    def list(self, *args, **kwargs):
        queryset = self.get_queryset()
        serializer = GroupedPersonPhotosSerializer(
            queryset, many=True, context={"request": self.request}
        )
        return Response({"results": serializer.data})


class PersonViewSet(viewsets.ModelViewSet):
    serializer_class = PersonSerializer
    pagination_class = StandardResultsSetPagination
    filter_backends = (filters.SearchFilter,)
    search_fields = ["name"]
    ordering_fields = ["name"]

    def get_queryset(self):
        if self.request.user.is_anonymous:
            return Person.objects.none()
        qs = (
            Person.objects.filter(
                Q(kind=Person.KIND_USER) & Q(cluster_owner=self.request.user)
            )
            .select_related("cover_photo", "cover_face")
            .only(
                "name",
                "face_count",
                "id",
                "cover_face",
                "cover_photo",
            )
            .order_by("name")
        )

        return qs


def _get_active_tag_thing_types():
    """Return the AlbumThing thing_type values for the active tagging model."""
    from constance import config as site_config

    tagging_model = site_config.TAGGING_MODEL
    if tagging_model == "siglip2":
        return ["siglip2_tag"]
    return ["places365_attribute", "places365_category"]


class AlbumThingViewSet(viewsets.ModelViewSet):
    serializer_class = AlbumThingSerializer
    pagination_class = StandardResultsSetPagination

    def get_queryset(self):
        if self.request.user.is_anonymous:
            return AlbumThing.objects.none()
        active_types = _get_active_tag_thing_types()
        return (
            AlbumThing.objects.filter(Q(owner=self.request.user))
            .filter(Q(photo_count__gt=0))
            .filter(Q(thing_type__in=active_types) | Q(thing_type="hashtag_attribute"))
            .prefetch_related(
                Prefetch(
                    "photos",
                    queryset=Photo.visible.order_by("-exif_timestamp"),
                ),
                Prefetch(
                    "photos__owner",
                    queryset=User.objects.only(
                        "id", "username", "first_name", "last_name"
                    ),
                ),
            )
            .order_by("title")
        )

    def retrieve(self, *args, **kwargs):
        queryset = self.get_queryset()
        logger.warning(args[0].__str__())
        albumid = re.findall(r"\'(.+?)\'", args[0].__str__())[0].split("/")[-2]
        serializer = GroupedThingPhotosSerializer(
            queryset.filter(id=albumid).first(), context={"request": self.request}
        )
        return Response({"results": serializer.data})

    def list(self, *args, **kwargs):
        queryset = self.get_queryset()
        serializer = GroupedThingPhotosSerializer(
            queryset, many=True, context={"request": self.request}
        )
        return Response({"results": serializer.data})


# To-Do: Could be literally the list command in AlbumThingViewSet
class AlbumThingListViewSet(ListViewSet):
    serializer_class = AlbumThingListSerializer
    pagination_class = StandardResultsSetPagination
    filter_backends = (filters.SearchFilter,)
    search_fields = ["title"]

    def get_queryset(self):
        if self.request.user.is_anonymous:
            return AlbumThing.objects.none()

        active_types = _get_active_tag_thing_types()
        queryset = (
            AlbumThing.objects.filter(owner=self.request.user)
            .prefetch_related("cover_photos")
            .filter(photo_count__gt=0)
            .filter(Q(thing_type__in=active_types) | Q(thing_type="hashtag_attribute"))
            .order_by("-title")
        )

        return queryset


class AlbumPlaceViewSet(viewsets.ModelViewSet):
    serializer_class = AlbumPlaceSerializer
    pagination_class = StandardResultsSetPagination

    def get_queryset(self):
        if self.request.user.is_anonymous:
            return AlbumPlace.objects.none()
        return (
            AlbumPlace.objects.annotate(
                photo_count=Count(
                    "photos", filter=Q(photos__hidden=False), distinct=True
                )
            )
            .filter(Q(photo_count__gt=0) & Q(owner=self.request.user))
            .prefetch_related(
                Prefetch(
                    "photos",
                    queryset=Photo.objects.filter(hidden=False)
                    .only("image_hash", "public", "rating", "hidden", "exif_timestamp")
                    .order_by("-exif_timestamp"),
                )
            )
            .order_by("title")
        )

    def retrieve(self, *args, **kwargs):
        queryset = self.get_queryset()
        logger.warning(args[0].__str__())
        albumid = re.findall(r"\'(.+?)\'", args[0].__str__())[0].split("/")[-2]
        serializer = GroupedPlacePhotosSerializer(
            queryset.filter(id=albumid).first(), context={"request": self.request}
        )
        return Response({"results": serializer.data})


# To-Do: Could be literally the list command in AlbumPlaceViewSet
class AlbumPlaceListViewSet(ListViewSet):
    serializer_class = AlbumPlaceListSerializer
    pagination_class = StandardResultsSetPagination
    filter_backends = (filters.SearchFilter,)
    search_fields = ["title"]

    def get_queryset(self):
        if self.request.user.is_anonymous:
            return AlbumPlace.objects.none()
        cover_photos_query = Photo.objects.filter(hidden=False).only(
            "image_hash", "video"
        )

        return (
            AlbumPlace.objects.filter(owner=self.request.user)
            .annotate(
                photo_count=Count(
                    "photos", filter=Q(photos__hidden=False), distinct=True
                )
            )
            .prefetch_related(
                Prefetch(
                    "photos", queryset=cover_photos_query[:4], to_attr="cover_photos"
                )
            )
            .filter(Q(photo_count__gt=0) & Q(owner=self.request.user))
            .order_by("title")
        )


class AlbumUserViewSet(viewsets.ModelViewSet):
    serializer_class = AlbumUserSerializer
    pagination_class = StandardResultsSetPagination

    def perform_create(self, serializer):
        serializer.save(owner=self.request.user)

    def get_queryset(self):
        # Support public access when explicitly requested. Public access is
        # READ-ONLY: only honor ?public for the safe list/retrieve actions, so a
        # public album id can't be mutated or deleted through this unscoped
        # queryset. See issue #1861.
        if self.request.query_params.get("public") and self.action in (
            "list",
            "retrieve",
        ):
            # Anyone can view a public album if the album itself is marked public and active (not expired)
            username = self.request.query_params.get("username")
            from django.utils import timezone

            # Use share model exclusively
            base_qs = AlbumUser.objects.filter(
                Q(share__enabled=True)
                & (
                    Q(share__expires_at__isnull=True)
                    | Q(share__expires_at__gte=timezone.now())
                )
            )
            if username:
                base_qs = base_qs.filter(owner__username=username)
            return base_qs.order_by("-id")

        if self.request.user.is_anonymous:
            return AlbumUser.objects.none()
        return (
            AlbumUser.objects.filter(
                Q(owner=self.request.user) | Q(shared_to__exact=self.request.user.id)
            )
            .distinct()
            .order_by("-id")
        )

    def get_permissions(self):
        # Anonymous public access is allowed only for read actions. Write actions
        # (create/update/partial_update/destroy) always require authentication,
        # even when ?public is present. See issue #1861.
        if self.request.query_params.get("public") and self.action in (
            "list",
            "retrieve",
        ):
            permission_classes = [AllowAny]
        else:
            permission_classes = [IsAuthenticated]
        return [permission() for permission in permission_classes]

    def get_serializer_class(self):
        if self.request.query_params.get("public") and self.action in (
            "list",
            "retrieve",
        ):
            return AlbumUserPublicSerializer
        return super().get_serializer_class()


# To-Do: Could be the list command in AlbumUserViewSet
class AlbumUserListViewSet(ListViewSet):
    serializer_class = AlbumUserListSerializer
    pagination_class = StandardResultsSetPagination
    filter_backends = (filters.SearchFilter,)
    search_fields = ["title"]

    def get_queryset(self):
        if self.request.user.is_anonymous:
            return AlbumUser.objects.none()
        return (
            AlbumUser.objects.filter(owner=self.request.user)
            .annotate(
                photo_count=Count(
                    "photos", filter=Q(photos__hidden=False), distinct=True
                )
            )
            .filter(Q(photo_count__gt=0) & Q(owner=self.request.user))
            .order_by("title")
        )


class AlbumDateViewSet(viewsets.ModelViewSet):
    serializer_class = AlbumDateSerializer
    pagination_class = RegularResultsSetPagination

    def get_queryset(self):
        photo_filter = []
        photo_filter.append(Q(thumbnail__aspect_ratio__isnull=False))

        if not self.request.user.is_anonymous and not self.request.query_params.get(
            "public"
        ):
            photo_filter.append(Q(owner=self.request.user))
        if self.request.query_params.get("favorite"):
            min_rating = self.request.user.favorite_min_rating
            photo_filter.append(Q(rating__gte=min_rating))

        if self.request.query_params.get("public"):
            if self.request.query_params.get("username"):
                username = self.request.query_params.get("username")
                photo_filter.append(Q(owner__username=username))
            photo_filter.append(Q(public=True))

        # Filter by folder path if provided
        if self.request.query_params.get("folder"):
            folder_path = self.request.query_params.get("folder")
            photo_filter.append(Q(files__path__startswith=folder_path))

        if self.request.query_params.get("hidden"):
            photo_filter.append(Q(hidden=True))
        else:
            photo_filter.append(Q(hidden=False))

        if self.request.query_params.get("video"):
            photo_filter.append(Q(video=True))

        if self.request.query_params.get("photo"):
            photo_filter.append(Q(video=False))

        if self.request.query_params.get("in_trashcan"):
            photo_filter.append(Q(in_trashcan=True) & Q(removed=False))
        else:
            photo_filter.append(Q(in_trashcan=False))

        # Stack filtering: Show photos that are either:
        # 1. Not in any stack, OR
        # 2. The primary photo of their stack
        # Stacks are for organizational purposes (RAW+JPEG pairs, bursts, brackets, live photos, manual)
        # Non-primary photos are hidden in the timeline but accessible via stack expansion
        # NOTE: Duplicates are handled separately via the Duplicate model and are not filtered here
        if not self.request.query_params.get("show_all_stack_photos"):
            photo_filter.append(
                Q(stacks__isnull=True) | Q(primary_in_stack__isnull=False)
            )

        if self.request.query_params.get("person"):
            photo_filter.append(
                Q(faces__person__id=self.request.query_params.get("person"))
            )
        if self.request.query_params.get("last_modified"):
            photo_filter = []
            photo_filter.append(Q(owner=self.request.user))
            photo_filter.append(
                Q(exif_timestamp__gte=self.request.query_params.get("last_modified"))
            )

        album_date = AlbumDate.objects.filter(id=self.kwargs["pk"]).first()

        # Build a prefetch for stacks that filters to valid types and annotates
        # photo_count, avoiding N+1 queries in the serializer
        valid_stack_types = PhotoStack.VALID_STACK_TYPES + [
            PhotoStack.StackType.RAW_JPEG_PAIR,
            PhotoStack.StackType.LIVE_PHOTO,
        ]
        stacks_prefetch = Prefetch(
            "stacks",
            queryset=PhotoStack.objects.filter(
                stack_type__in=valid_stack_types
            ).annotate(photo_count_annotation=Count("photos")),
        )

        photo_qs = (
            album_date.photos.filter(*photo_filter)
            .prefetch_related(
                Prefetch(
                    "owner",
                    queryset=User.objects.only(
                        "id", "username", "first_name", "last_name"
                    ),
                ),
                Prefetch(
                    "main_file__embedded_media",
                    queryset=File.objects.only("hash"),
                ),
                stacks_prefetch,
                "files",  # Prefetch files for get_has_raw_variant()
            )
            .select_related("thumbnail", "search_instance", "main_file")
            .distinct()  # Remove duplicates that can occur when filtering through reverse ForeignKey relationships (e.g., faces__person__id)
            .order_by("-exif_timestamp")
            .only(
                "image_hash",
                "thumbnail__aspect_ratio",
                "thumbnail__dominant_color",
                "video",
                "main_file",
                "search_instance__search_location",
                "public",
                "rating",
                "hidden",
                "exif_timestamp",
                "owner",
                "video_length",
                "exif_gps_lat",
                "exif_gps_lon",
                "removed",
                "in_trashcan",
            )
        )

        # Paginate photo queryset
        page_size = self.request.query_params.get("size") or 100
        paginator = Paginator(photo_qs, page_size)
        page = self.request.query_params.get("page")

        try:
            photos = paginator.page(page)
        except PageNotAnInteger:
            photos = paginator.page(1)
        except EmptyPage:
            photos = paginator.page(paginator.num_pages)

        return album_date, photos, paginator.count

    def get_permissions(self):
        if self.request.query_params.get("public"):
            permission_classes = [AllowAny]
        else:
            permission_classes = [IsAuthenticated]
        return [permission() for permission in permission_classes]

    @extend_schema(
        parameters=[
            OpenApiParameter("favorite", OpenApiTypes.BOOL),
            OpenApiParameter("public", OpenApiTypes.BOOL),
            OpenApiParameter("in_trashcan", OpenApiTypes.BOOL),
            OpenApiParameter("hidden", OpenApiTypes.BOOL),
            OpenApiParameter("video", OpenApiTypes.BOOL),
            OpenApiParameter("username", OpenApiTypes.STR),
            OpenApiParameter("person", OpenApiTypes.INT),
            OpenApiParameter("last_modified", OpenApiTypes.DATE),
        ],
        description="Returns the actual images, for a given day in chunks of 100 images.",
    )
    def retrieve(self, *args, **kwargs):
        album_date, photos, count = self.get_queryset()
        serializer = AlbumDateSerializer(album_date, context={"request": self.request})
        serializer_data = serializer.data
        serializer_data["items"] = PhotoSummarySerializer(
            photos, many=True
        ).data  # Assuming you have a PhotoSerializer
        serializer_data["numberOfItems"] = count
        return Response({"results": serializer_data})


# To-Do: Could be the summary command in AlbumDateViewSet
class AlbumDateListViewSet(ListViewSet):
    serializer_class = IncompleteAlbumDateSerializer
    pagination_class = None
    filter_backends = (filters.SearchFilter,)
    search_fields = [
        "photos__search_instance__search_captions",
        "photos__search_instance__search_location",
        "photos__faces__person__name",
    ]

    def get_queryset(self):
        filter = []
        filter.append(Q(photos__thumbnail__aspect_ratio__isnull=False))

        if self.request.query_params.get("hidden"):
            filter.append(Q(photos__hidden=True))
        else:
            filter.append(Q(photos__hidden=False))

        if self.request.query_params.get("in_trashcan"):
            filter.append(Q(photos__in_trashcan=True) & Q(photos__removed=False))
        else:
            filter.append(Q(photos__in_trashcan=False))

        # Stack filtering: Only count photos that are either:
        # 1. Not in any stack, OR
        # 2. The primary photo of their stack
        # Stacks are for organizational purposes (RAW+JPEG pairs, bursts, brackets, live photos, manual)
        # Non-primary photos are hidden in the timeline but accessible via stack expansion
        # NOTE: Duplicates are handled separately via the Duplicate model and are not filtered here
        if not self.request.query_params.get("show_all_stack_photos"):
            filter.append(
                Q(photos__stacks__isnull=True)
                | Q(photos__primary_in_stack__isnull=False)
            )

        # Filter by folder path if provided
        if self.request.query_params.get("folder"):
            folder_path = self.request.query_params.get("folder")
            filter.append(Q(photos__files__path__startswith=folder_path))

        if not self.request.user.is_anonymous and not self.request.query_params.get(
            "public"
        ):
            filter.append(Q(owner=self.request.user))
            filter.append(Q(photos__owner=self.request.user))

        if self.request.query_params.get("favorite"):
            min_rating = self.request.user.favorite_min_rating
            filter.append(Q(photos__rating__gte=min_rating))

        if self.request.query_params.get("public"):
            username = self.request.query_params.get("username")
            filter.append(Q(owner__username=username))
            filter.append(Q(photos__public=True))

        if self.request.query_params.get("video"):
            filter.append(Q(photos__video=True))

        if self.request.query_params.get("photo"):
            filter.append(Q(photos__video=False))

        if self.request.query_params.get("person"):
            filter.append(
                Q(photos__faces__person__id=self.request.query_params.get("person"))
            )
        if self.request.query_params.get("last_modified"):
            filter = []
            filter.append(Q(owner=self.request.user))
            filter.append(Q(photos__owner=self.request.user))
            filter.append(
                Q(
                    photos__last_modified__gte=self.request.query_params.get(
                        "last_modified"
                    )
                )
            )

        qs = (
            AlbumDate.objects.filter(*filter)
            .annotate(photo_count=Count("photos", distinct=True))
            .filter(Q(photo_count__gt=0))
            .order_by(F("date").desc(nulls_last=True))
        )

        return qs

    def get_permissions(self):
        if self.request.query_params.get("public"):
            permission_classes = [AllowAny]
        else:
            permission_classes = [IsAuthenticated]
        return [permission() for permission in permission_classes]

    @extend_schema(
        parameters=[
            OpenApiParameter("favorite", OpenApiTypes.BOOL),
            OpenApiParameter("public", OpenApiTypes.BOOL),
            OpenApiParameter("in_trashcan", OpenApiTypes.BOOL),
            OpenApiParameter("hidden", OpenApiTypes.BOOL),
            OpenApiParameter("video", OpenApiTypes.BOOL),
            OpenApiParameter("username", OpenApiTypes.STR),
            OpenApiParameter("person", OpenApiTypes.INT),
            OpenApiParameter("last_modified", OpenApiTypes.DATE),
        ],
        description="Gives you a list of days with the number of elements. This is not paginated and can be large.",
    )
    def list(self, *args, **kwargs):
        serializer = IncompleteAlbumDateSerializer(self.get_queryset(), many=True)
        return Response({"results": serializer.data})
