from rest_framework import viewsets, status
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.permissions import IsAuthenticated, AllowAny
from rest_framework.decorators import action
from rest_framework.parsers import MultiPartParser, FormParser
from drf_yasg.utils import swagger_auto_schema
from drf_yasg import openapi
from django.shortcuts import get_object_or_404, get_list_or_404
from django.db import transaction
import os, json, requests

from .models import Stream, Channel, ChannelGroup, Logo, ChannelProfile, ChannelProfileMembership, Recording
from .serializers import StreamSerializer, ChannelSerializer, ChannelGroupSerializer, LogoSerializer, ChannelProfileMembershipSerializer, BulkChannelProfileMembershipSerializer, ChannelProfileSerializer, RecordingSerializer
from .tasks import match_epg_channels
import django_filters
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework.filters import SearchFilter, OrderingFilter
from apps.epg.models import EPGData
from django.db.models import Q
from django.http import StreamingHttpResponse, FileResponse, Http404
import mimetypes

from rest_framework.pagination import PageNumberPagination


class OrInFilter(django_filters.Filter):
    """
    Custom filter that handles the OR condition instead of AND.
    """
    def filter(self, queryset, value):
        if value:
            # Create a Q object for each value and combine them with OR
            query = Q()
            for val in value.split(','):
                query |= Q(**{self.field_name: val})
            return queryset.filter(query)
        return queryset

class StreamPagination(PageNumberPagination):
    page_size = 25  # Default page size
    page_size_query_param = 'page_size'  # Allow clients to specify page size
    max_page_size = 10000  # Prevent excessive page sizes

class StreamFilter(django_filters.FilterSet):
    name = django_filters.CharFilter(lookup_expr='icontains')
    channel_group_name = OrInFilter(field_name="channel_group__name", lookup_expr="icontains")
    m3u_account = django_filters.NumberFilter(field_name="m3u_account__id")
    m3u_account_name = django_filters.CharFilter(field_name="m3u_account__name", lookup_expr="icontains")
    m3u_account_is_active = django_filters.BooleanFilter(field_name="m3u_account__is_active")

    class Meta:
        model = Stream
        fields = ['name', 'channel_group_name', 'm3u_account', 'm3u_account_name', 'm3u_account_is_active']

# ─────────────────────────────────────────────────────────
# 1) Stream API (CRUD)
# ─────────────────────────────────────────────────────────
class StreamViewSet(viewsets.ModelViewSet):
    queryset = Stream.objects.all()
    serializer_class = StreamSerializer
    permission_classes = [IsAuthenticated]
    pagination_class = StreamPagination

    filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
    filterset_class = StreamFilter
    search_fields = ['name', 'channel_group__name']
    ordering_fields = ['name', 'channel_group__name']
    ordering = ['-name']

    def get_queryset(self):
        qs = super().get_queryset()
        # Exclude streams from inactive M3U accounts
        qs = qs.exclude(m3u_account__is_active=False)

        assigned = self.request.query_params.get('assigned')
        if assigned is not None:
            qs = qs.filter(channels__id=assigned)

        unassigned = self.request.query_params.get('unassigned')
        if unassigned == '1':
            qs = qs.filter(channels__isnull=True)

        channel_group = self.request.query_params.get('channel_group')
        if channel_group:
            group_names = channel_group.split(',')
            qs = qs.filter(channel_group__name__in=group_names)

        return qs

    def list(self, request, *args, **kwargs):
        ids = request.query_params.get('ids', None)
        if ids:
            ids = ids.split(',')
            streams = get_list_or_404(Stream, id__in=ids)
            serializer = self.get_serializer(streams, many=True)
            return Response(serializer.data)

        return super().list(request, *args, **kwargs)

    @action(detail=False, methods=['get'], url_path='ids')
    def get_ids(self, request, *args, **kwargs):
        # Get the filtered queryset
        queryset = self.get_queryset()

        # Apply filtering, search, and ordering
        queryset = self.filter_queryset(queryset)

        # Return only the IDs from the queryset
        stream_ids = queryset.values_list('id', flat=True)

        # Return the response with the list of IDs
        return Response(list(stream_ids))

    @action(detail=False, methods=['get'], url_path='groups')
    def get_groups(self, request, *args, **kwargs):
        # Get unique ChannelGroup names that are linked to streams
        group_names = ChannelGroup.objects.filter(streams__isnull=False).order_by('name').values_list('name', flat=True).distinct()

        # Return the response with the list of unique group names
        return Response(list(group_names))

# ─────────────────────────────────────────────────────────
# 2) Channel Group Management (CRUD)
# ─────────────────────────────────────────────────────────
class ChannelGroupViewSet(viewsets.ModelViewSet):
    queryset = ChannelGroup.objects.all()
    serializer_class = ChannelGroupSerializer
    permission_classes = [IsAuthenticated]


# ─────────────────────────────────────────────────────────
# 3) Channel Management (CRUD)
# ─────────────────────────────────────────────────────────
class ChannelPagination(PageNumberPagination):
    page_size = 25  # Default page size
    page_size_query_param = 'page_size'  # Allow clients to specify page size
    max_page_size = 10000  # Prevent excessive page sizes


    def paginate_queryset(self, queryset, request, view=None):
        if not request.query_params.get(self.page_query_param):
            return None  # disables pagination, returns full queryset

        return super().paginate_queryset(queryset, request, view)

class ChannelFilter(django_filters.FilterSet):
    name = django_filters.CharFilter(lookup_expr='icontains')
    channel_group_name = OrInFilter(field_name="channel_group__name", lookup_expr="icontains")

    class Meta:
        model = Channel
        fields = ['name', 'channel_group_name',]

class ChannelViewSet(viewsets.ModelViewSet):
    queryset = Channel.objects.all()
    serializer_class = ChannelSerializer
    permission_classes = [IsAuthenticated]
    pagination_class = ChannelPagination

    filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
    filterset_class = ChannelFilter
    search_fields = ['name', 'channel_group__name']
    ordering_fields = ['channel_number', 'name', 'channel_group__name']
    ordering = ['-channel_number']

    def get_queryset(self):
        qs = super().get_queryset().select_related(
            'channel_group',
            'logo',
            'epg_data',
            'stream_profile',
        ).prefetch_related('streams')

        channel_group = self.request.query_params.get('channel_group')
        if channel_group:
            group_names = channel_group.split(',')
            qs = qs.filter(channel_group__name__in=group_names)

        return qs

    def get_serializer_context(self):
        context = super().get_serializer_context()
        include_streams = self.request.query_params.get('include_streams', 'false') == 'true'
        context['include_streams'] = include_streams
        return context

    @action(detail=False, methods=['patch'], url_path='edit/bulk')
    def edit_bulk(self, request):
        data_list = request.data
        if not isinstance(data_list, list):
            return Response({"error": "Expected a list of channel objects objects"}, status=status.HTTP_400_BAD_REQUEST)

        updated_channels = []
        try:
            with transaction.atomic():
                for item in data_list:
                    channel = Channel.objects.id(id=item.pop('id'))
                    for key, value in item.items():
                        setattr(channel, key, value)

                    channel.save(update_fields=item.keys())
                    updated_channels.append(channel)
        except Exception as e:
            logger.error("Error during bulk channel edit", e)
            return Response({"error": e}, status=500)

        response_data = ChannelSerializer(updated_channels, many=True).data

        return Response(response_data, status=status.HTTP_200_OK)

    @action(detail=False, methods=['get'], url_path='ids')
    def get_ids(self, request, *args, **kwargs):
        # Get the filtered queryset
        queryset = self.get_queryset()

        # Apply filtering, search, and ordering
        queryset = self.filter_queryset(queryset)

        # Return only the IDs from the queryset
        channel_ids = queryset.values_list('id', flat=True)

        # Return the response with the list of IDs
        return Response(list(channel_ids))

    @swagger_auto_schema(
        method='post',
        operation_description="Auto-assign channel_number in bulk by an ordered list of channel IDs.",
        request_body=openapi.Schema(
            type=openapi.TYPE_OBJECT,
            required=["channel_ids"],
            properties={
                "starting_number": openapi.Schema(type=openapi.TYPE_STRING, description="Starting channel number to assign"),
                "channel_ids": openapi.Schema(
                    type=openapi.TYPE_ARRAY,
                    items=openapi.Items(type=openapi.TYPE_INTEGER),
                    description="Channel  IDs to assign"
                )
            }
        ),
        responses={200: "Channels have been auto-assigned!"}
    )
    @action(detail=False, methods=['post'], url_path='assign')
    def assign(self, request):
        with transaction.atomic():
            channel_ids = request.data.get('channel_ids', [])
            channel_num = request.data.get('starting_number', 1)
            for channel_id in channel_ids:
                Channel.objects.filter(id=channel_id).update(channel_number=channel_num)
                channel_num = channel_num + 1

        return Response({"message": "Channels have been auto-assigned!"}, status=status.HTTP_200_OK)

    @swagger_auto_schema(
        method='post',
        operation_description=(
            "Create a new channel from an existing stream. "
            "If 'channel_number' is provided, it will be used (if available); "
            "otherwise, the next available channel number is assigned."
        ),
        request_body=openapi.Schema(
            type=openapi.TYPE_OBJECT,
            required=["stream_id"],
            properties={
                "stream_id": openapi.Schema(
                    type=openapi.TYPE_INTEGER, description="ID of the stream to link"
                ),
                "channel_number": openapi.Schema(
                    type=openapi.TYPE_INTEGER,
                    description="(Optional) Desired channel number. Must not be in use."
                ),
                "name": openapi.Schema(
                    type=openapi.TYPE_STRING, description="Desired channel name"
                )
            }
        ),
        responses={201: ChannelSerializer()}
    )
    @action(detail=False, methods=['post'], url_path='from-stream')
    def from_stream(self, request):
        stream_id = request.data.get('stream_id')
        if not stream_id:
            return Response({"error": "Missing stream_id"}, status=status.HTTP_400_BAD_REQUEST)
        stream = get_object_or_404(Stream, pk=stream_id)
        channel_group = stream.channel_group

        name = request.data.get('name')
        if name is None:
            name = stream.name

        # Check if client provided a channel_number; if not, auto-assign one.
        stream_custom_props = json.loads(stream.custom_properties) if stream.custom_properties else {}

        channel_number = None
        if 'tvg-chno' in stream_custom_props:
            channel_number = int(stream_custom_props['tvg-chno'])
        elif 'channel-number' in stream_custom_props:
            channel_number = int(stream_custom_props['channel-number'])

        if channel_number is None:
            provided_number = request.data.get('channel_number')
            if provided_number is None:
                channel_number = Channel.get_next_available_channel_number()
            else:
                try:
                    channel_number = int(provided_number)
                except ValueError:
                    return Response({"error": "channel_number must be an integer."}, status=status.HTTP_400_BAD_REQUEST)
                # If the provided number is already used, return an error.
                if Channel.objects.filter(channel_number=channel_number).exists():
                    return Response(
                        {"error": f"Channel number {channel_number} is already in use. Please choose a different number."},
                        status=status.HTTP_400_BAD_REQUEST
                    )
        #Get the tvc_guide_stationid from custom properties if it exists
        tvc_guide_stationid = None
        if 'tvc-guide-stationid' in stream_custom_props:
            tvc_guide_stationid = stream_custom_props['tvc-guide-stationid']



        channel_data = {
            'channel_number': channel_number,
            'name': name,
            'tvg_id': stream.tvg_id,
            'tvc_guide_stationid': tvc_guide_stationid,
            'channel_group_id': channel_group.id,
            'streams': [stream_id],
        }

        if stream.logo_url:
            logo, _ = Logo.objects.get_or_create(url=stream.logo_url, defaults={
                "name": stream.name or stream.tvg_id
            })
            channel_data["logo_id"] = logo.id

        # Attempt to find existing EPGs with the same tvg-id
        epgs = EPGData.objects.filter(tvg_id=stream.tvg_id)
        if epgs:
            channel_data["epg_data_id"] = epgs.first().id

        serializer = self.get_serializer(data=channel_data)
        serializer.is_valid(raise_exception=True)
        channel = serializer.save()
        channel.streams.add(stream)
        return Response(serializer.data, status=status.HTTP_201_CREATED)

    @swagger_auto_schema(
        method='post',
        operation_description=(
            "Bulk create channels from existing streams. For each object, if 'channel_number' is provided, "
            "it is used (if available); otherwise, the next available number is auto-assigned. "
            "Each object must include 'stream_id' and 'name'."
        ),
        request_body=openapi.Schema(
            type=openapi.TYPE_ARRAY,
            items=openapi.Schema(
                type=openapi.TYPE_OBJECT,
                required=["stream_id"],
                properties={
                    "stream_id": openapi.Schema(
                        type=openapi.TYPE_INTEGER, description="ID of the stream to link"
                    ),
                    "channel_number": openapi.Schema(
                        type=openapi.TYPE_INTEGER,
                        description="(Optional) Desired channel number. Must not be in use."
                    ),
                    "name": openapi.Schema(
                        type=openapi.TYPE_STRING, description="Desired channel name"
                    )
                }
            )
        ),
        responses={201: "Bulk channels created"}
    )
    @action(detail=False, methods=['post'], url_path='from-stream/bulk')
    def from_stream_bulk(self, request):
        data_list = request.data
        if not isinstance(data_list, list):
            return Response({"error": "Expected a list of channel objects"}, status=status.HTTP_400_BAD_REQUEST)

        created_channels = []
        errors = []

        # Gather current used numbers once.
        used_numbers = set(Channel.objects.all().values_list('channel_number', flat=True))
        next_number = 1

        def get_auto_number():
            nonlocal next_number
            while next_number in used_numbers:
                next_number += 1
            used_numbers.add(next_number)
            return next_number

        logos_to_create = []
        channels_to_create = []
        streams_map = []
        logo_map = []
        for item in data_list:
            stream_id = item.get('stream_id')
            if not all([stream_id]):
                errors.append({"item": item, "error": "Missing required fields: stream_id and name are required."})
                continue

            try:
                stream = get_object_or_404(Stream, pk=stream_id)
            except Exception as e:
                errors.append({"item": item, "error": str(e)})
                continue

            name = item.get('name')
            if name is None:
                name = stream.name

            channel_group = stream.channel_group

            stream_custom_props = json.loads(stream.custom_properties) if stream.custom_properties else {}

            channel_number = None
            if 'tvg-chno' in stream_custom_props:
                channel_number = int(stream_custom_props['tvg-chno'])
            elif 'channel-number' in stream_custom_props:
                channel_number = int(stream_custom_props['channel-number'])

            # Determine channel number: if provided, use it (if free); else auto assign.
            if channel_number is None:
                provided_number = item.get('channel_number')
                if provided_number is None:
                    channel_number = get_auto_number()
                else:
                    try:
                        channel_number = int(provided_number)
                    except ValueError:
                        errors.append({"item": item, "error": "channel_number must be an integer."})
                        continue
                    if channel_number in used_numbers or Channel.objects.filter(channel_number=channel_number).exists():
                        errors.append({"item": item, "error": f"Channel number {channel_number} is already in use."})
                        continue
                    used_numbers.add(channel_number)

            channel_data = {
                "channel_number": channel_number,
                "name": name,
                "tvg_id": stream.tvg_id,
                "channel_group_id": channel_group.id,
            }

            # Attempt to find existing EPGs with the same tvg-id
            epgs = EPGData.objects.filter(tvg_id=stream.tvg_id)
            if epgs:
                channel_data["epg_data_id"] = epgs.first().id

            serializer = self.get_serializer(data=channel_data)
            if serializer.is_valid():
                validated_data = serializer.validated_data
                channel = Channel(**validated_data)
                channels_to_create.append(channel)

                streams_map.append([stream_id])
                if stream.logo_url:
                    logos_to_create.append(Logo(
                        url=stream.logo_url,
                        name=stream.name or stream.tvg_id,
                    ))
                    logo_map.append(stream.logo_url)
                else:
                    logo_map.append(None)

                # channel = serializer.save()
                # channel.streams.add(stream)
                # created_channels.append(serializer.data)
            else:
                errors.append({"item": item, "error": serializer.errors})

        if logos_to_create:
            Logo.objects.bulk_create(logos_to_create, ignore_conflicts=True)

        channel_logos = {logo.url: logo for logo in Logo.objects.filter(url__in=[url for url in logo_map if url is not None])}

        profiles = ChannelProfile.objects.all()
        channel_profile_memberships = []
        if channels_to_create:
            with transaction.atomic():
                created_channels = Channel.objects.bulk_create(channels_to_create)

                update = []
                for channel, stream_ids, logo_url in zip(created_channels, streams_map, logo_map):
                    if logo_url:
                        channel.logo = channel_logos[logo_url]
                    update.append(channel)
                    channel_profile_memberships = channel_profile_memberships + [
                       ChannelProfileMembership(channel_profile=profile, channel=channel)
                        for profile in profiles
                    ]

                ChannelProfileMembership.objects.bulk_create(channel_profile_memberships)
                Channel.objects.bulk_update(update, ['logo'])

                for channel, stream_ids in zip(created_channels, streams_map):
                    channel.streams.set(stream_ids)

        response_data = {"created": ChannelSerializer(created_channels, many=True).data}
        if errors:
            response_data["errors"] = errors

        return Response(response_data, status=status.HTTP_201_CREATED)

    # ─────────────────────────────────────────────────────────
    # 6) EPG Fuzzy Matching
    # ─────────────────────────────────────────────────────────
    @swagger_auto_schema(
        method='post',
        operation_description="Kick off a Celery task that tries to fuzzy-match channels with EPG data.",
        responses={202: "EPG matching task initiated"}
    )
    @action(detail=False, methods=['post'], url_path='match-epg')
    def match_epg(self, request):
        match_epg_channels.delay()
        return Response({"message": "EPG matching task initiated."}, status=status.HTTP_202_ACCEPTED)

    # ─────────────────────────────────────────────────────────
    # 7) Set EPG and Refresh
    # ─────────────────────────────────────────────────────────
    @swagger_auto_schema(
        method='post',
        operation_description="Set EPG data for a channel and refresh program data",
        request_body=openapi.Schema(
            type=openapi.TYPE_OBJECT,
            required=['epg_data_id'],
            properties={
                'epg_data_id': openapi.Schema(
                    type=openapi.TYPE_INTEGER, description="EPG data ID to link"
                )
            }
        ),
        responses={200: "EPG data linked and refresh triggered"}
    )
    @action(detail=True, methods=['post'], url_path='set-epg')
    def set_epg(self, request, pk=None):
        channel = self.get_object()
        epg_data_id = request.data.get('epg_data_id')

        # Handle removing EPG link
        if epg_data_id in (None, '', '0', 0):
            channel.epg_data = None
            channel.save(update_fields=['epg_data'])
            return Response({"message": f"EPG data removed from channel {channel.name}"})

        try:
            # Get the EPG data object
            from apps.epg.models import EPGData
            epg_data = EPGData.objects.get(pk=epg_data_id)

            # Set the EPG data and save
            channel.epg_data = epg_data
            channel.save(update_fields=['epg_data'])

            # Explicitly trigger program refresh for this EPG
            from apps.epg.tasks import parse_programs_for_tvg_id
            task_result = parse_programs_for_tvg_id.delay(epg_data.id)

            # Prepare response with task status info
            status_message = "EPG refresh queued"
            if task_result.result == "Task already running":
                status_message = "EPG refresh already in progress"

            return Response({
                "message": f"EPG data set to {epg_data.tvg_id} for channel {channel.name}. {status_message}.",
                "channel": self.get_serializer(channel).data,
                "task_status": status_message
            })
        except Exception as e:
            return Response({"error": str(e)}, status=400)

    @swagger_auto_schema(
        method='post',
        operation_description="Associate multiple channels with EPG data without triggering a full refresh",
        request_body=openapi.Schema(
            type=openapi.TYPE_OBJECT,
            properties={
                'associations': openapi.Schema(
                    type=openapi.TYPE_ARRAY,
                    items=openapi.Schema(
                        type=openapi.TYPE_OBJECT,
                        properties={
                            'channel_id': openapi.Schema(type=openapi.TYPE_INTEGER),
                            'epg_data_id': openapi.Schema(type=openapi.TYPE_INTEGER)
                        }
                    )
                )
            }
        ),
        responses={200: "EPG data linked for multiple channels"}
    )
    @action(detail=False, methods=['post'], url_path='batch-set-epg')
    def batch_set_epg(self, request):
        """Efficiently associate multiple channels with EPG data at once."""
        associations = request.data.get('associations', [])
        channels_updated = 0
        programs_refreshed = 0
        unique_epg_ids = set()

        for assoc in associations:
            channel_id = assoc.get('channel_id')
            epg_data_id = assoc.get('epg_data_id')

            if not channel_id:
                continue

            try:
                # Get the channel
                channel = Channel.objects.get(id=channel_id)

                # Set the EPG data
                channel.epg_data_id = epg_data_id
                channel.save(update_fields=['epg_data'])
                channels_updated += 1

                # Track unique EPG data IDs
                if epg_data_id:
                    unique_epg_ids.add(epg_data_id)

            except Channel.DoesNotExist:
                logger.error(f"Channel with ID {channel_id} not found")
            except Exception as e:
                logger.error(f"Error setting EPG data for channel {channel_id}: {str(e)}")

        # Trigger program refresh for unique EPG data IDs
        from apps.epg.tasks import parse_programs_for_tvg_id
        for epg_id in unique_epg_ids:
            parse_programs_for_tvg_id.delay(epg_id)
            programs_refreshed += 1



        return Response({
            'success': True,
            'channels_updated': channels_updated,
            'programs_refreshed': programs_refreshed
        })

# ─────────────────────────────────────────────────────────
# 4) Bulk Delete Streams
# ─────────────────────────────────────────────────────────
class BulkDeleteStreamsAPIView(APIView):
    permission_classes = [IsAuthenticated]

    @swagger_auto_schema(
        operation_description="Bulk delete streams by ID",
        request_body=openapi.Schema(
            type=openapi.TYPE_OBJECT,
            required=["stream_ids"],
            properties={
                "stream_ids": openapi.Schema(
                    type=openapi.TYPE_ARRAY,
                    items=openapi.Items(type=openapi.TYPE_INTEGER),
                    description="Stream IDs to delete"
                )
            },
        ),
        responses={204: "Streams deleted"}
    )
    def delete(self, request, *args, **kwargs):
        stream_ids = request.data.get('stream_ids', [])
        Stream.objects.filter(id__in=stream_ids).delete()
        return Response({"message": "Streams deleted successfully!"}, status=status.HTTP_204_NO_CONTENT)


# ─────────────────────────────────────────────────────────
# 5) Bulk Delete Channels
# ─────────────────────────────────────────────────────────
class BulkDeleteChannelsAPIView(APIView):
    permission_classes = [IsAuthenticated]

    @swagger_auto_schema(
        operation_description="Bulk delete channels by ID",
        request_body=openapi.Schema(
            type=openapi.TYPE_OBJECT,
            required=["channel_ids"],
            properties={
                "channel_ids": openapi.Schema(
                    type=openapi.TYPE_ARRAY,
                    items=openapi.Items(type=openapi.TYPE_INTEGER),
                    description="Channel IDs to delete"
                )
            },
        ),
        responses={204: "Channels deleted"}
    )
    def delete(self, request):
        channel_ids = request.data.get('channel_ids', [])
        Channel.objects.filter(id__in=channel_ids).delete()
        return Response({"message": "Channels deleted"}, status=status.HTTP_204_NO_CONTENT)

class LogoViewSet(viewsets.ModelViewSet):
    permission_classes = [IsAuthenticated]
    queryset = Logo.objects.all()
    serializer_class = LogoSerializer
    parser_classes = (MultiPartParser, FormParser)

    @action(detail=False, methods=['post'])
    def upload(self, request):
        if 'file' not in request.FILES:
            return Response({'error': 'No file uploaded'}, status=status.HTTP_400_BAD_REQUEST)

        file = request.FILES['file']
        file_name = file.name
        file_path = os.path.join('/data/logos', file_name)

        os.makedirs(os.path.dirname(file_path), exist_ok=True)
        with open(file_path, 'wb+') as destination:
            for chunk in file.chunks():
                destination.write(chunk)

        logo, _ = Logo.objects.get_or_create(url=file_path, defaults={
            "name": file_name,
        })

        return Response({'id': logo.id, 'name': logo.name, 'url': logo.url}, status=status.HTTP_201_CREATED)

    @action(detail=True, methods=['get'], permission_classes=[AllowAny])
    def cache(self, request, pk=None):
        """Streams the logo file, whether it's local or remote."""
        logo = self.get_object()
        logo_url = logo.url

        if logo_url.startswith("/data"):  # Local file
            if not os.path.exists(logo_url):
                raise Http404("Image not found")

            # Get proper mime type (first item of the tuple)
            content_type, _ = mimetypes.guess_type(logo_url)
            if not content_type:
                content_type = 'image/jpeg'  # Default to a common image type

            # Use context manager and set Content-Disposition to inline
            response = StreamingHttpResponse(open(logo_url, "rb"), content_type=content_type)
            response['Content-Disposition'] = 'inline; filename="{}"'.format(os.path.basename(logo_url))
            return response

        else:  # Remote image
            try:
                remote_response = requests.get(logo_url, stream=True)
                if remote_response.status_code == 200:
                    # Try to get content type from response headers first
                    content_type = remote_response.headers.get('Content-Type')

                    # If no content type in headers or it's empty, guess based on URL
                    if not content_type:
                        content_type, _ = mimetypes.guess_type(logo_url)

                    # If still no content type, default to common image type
                    if not content_type:
                        content_type = 'image/jpeg'

                    response = StreamingHttpResponse(remote_response.iter_content(chunk_size=8192), content_type=content_type)
                    response['Content-Disposition'] = 'inline; filename="{}"'.format(os.path.basename(logo_url))
                    return response
                raise Http404("Remote image not found")
            except requests.RequestException:
                raise Http404("Error fetching remote image")

class ChannelProfileViewSet(viewsets.ModelViewSet):
    queryset = ChannelProfile.objects.all()
    serializer_class = ChannelProfileSerializer
    permission_classes = [IsAuthenticated]

class GetChannelStreamsAPIView(APIView):
    def get(self, request, channel_id):
        channel = get_object_or_404(Channel, id=channel_id)
        # Order the streams by channelstream__order to match the order in the channel view
        streams = channel.streams.all().order_by('channelstream__order')
        serializer = StreamSerializer(streams, many=True)
        return Response(serializer.data)

class UpdateChannelMembershipAPIView(APIView):
    def patch(self, request, profile_id, channel_id):
        """Enable or disable a channel for a specific group"""
        channel_profile = get_object_or_404(ChannelProfile, id=profile_id)
        channel = get_object_or_404(Channel, id=channel_id)
        membership = get_object_or_404(ChannelProfileMembership, channel_profile=channel_profile, channel=channel)

        serializer = ChannelProfileMembershipSerializer(membership, data=request.data, partial=True)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_200_OK)

        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

class BulkUpdateChannelMembershipAPIView(APIView):
    def patch(self, request, profile_id):
        """Bulk enable or disable channels for a specific profile"""
        # Get the channel profile
        channel_profile = get_object_or_404(ChannelProfile, id=profile_id)

        # Validate the incoming data using the serializer
        serializer = BulkChannelProfileMembershipSerializer(data=request.data)

        if serializer.is_valid():
            updates = serializer.validated_data['channels']
            channel_ids = [entry['channel_id'] for entry in updates]


            memberships = ChannelProfileMembership.objects.filter(
                channel_profile=channel_profile,
                channel_id__in=channel_ids
            )

            membership_dict = {m.channel.id: m for m in memberships}

            for entry in updates:
                channel_id = entry['channel_id']
                enabled_status = entry['enabled']
                if channel_id in membership_dict:
                    membership_dict[channel_id].enabled = enabled_status

            ChannelProfileMembership.objects.bulk_update(memberships, ['enabled'])

            return Response({"status": "success"}, status=status.HTTP_200_OK)

        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

class RecordingViewSet(viewsets.ModelViewSet):
    queryset = Recording.objects.all()
    serializer_class = RecordingSerializer
    permission_classes = [IsAuthenticated]
