"""Views for adspots app.

This module provides API views for:
- Adspot management (CRUD operations)
- File upload and processing
- Avail and Window management
- Pending creative processing
- Analytics and reporting
"""

import os
import hashlib
from django.shortcuts import get_object_or_404
from django.http import JsonResponse, Http404
from django.db.models import Q, Count, Sum
from django.utils import timezone
from django.core.files.storage import default_storage
from django.core.paginator import Paginator
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_http_methods
from django.utils.decorators import method_decorator
from django.views.generic import ListView, DetailView, CreateView, UpdateView, DeleteView
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib import messages
from django.urls import reverse_lazy
from rest_framework import viewsets, status, filters
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated
from rest_framework.parsers import MultiPartParser, FormParser
from django_filters.rest_framework import DjangoFilterBackend

from .models import Adspot, Avail, Window, AdspotInAvail, Pending
from .serializers import (
    AdspotSerializer, AdspotCreateSerializer, AdspotDetailSerializer,
    AvailSerializer, WindowSerializer, AdspotInAvailSerializer,
    PendingSerializer
)
from .filters import AdspotFilter, AvailFilter, WindowFilter
from .tasks import process_adspot_file


class AdspotViewSet(viewsets.ModelViewSet):
    """ViewSet for Adspot model.
    
    Provides CRUD operations for adspots with additional actions for
    file processing, analytics, and bulk operations.
    """
    
    queryset = Adspot.objects.all()
    permission_classes = [IsAuthenticated]
    parser_classes = [MultiPartParser, FormParser]
    filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
    filterset_class = AdspotFilter
    search_fields = ['name', 'creative_id', 'filename']
    ordering_fields = ['name', 'created_at', 'duration', 'file_size']
    ordering = ['-created_at']
    
    def get_serializer_class(self):
        """Return appropriate serializer based on action."""
        if self.action == 'create':
            return AdspotCreateSerializer
        elif self.action == 'retrieve':
            return AdspotDetailSerializer
        return AdspotSerializer
    
    def perform_create(self, serializer):
        """Create adspot and trigger file processing."""
        adspot = serializer.save(created_by=self.request.user)
        
        # Calculate file hash if file is provided
        if adspot.original_file:
            adspot.file_size = adspot.original_file.size
            adspot.filename = adspot.original_file.name
            
            # Calculate MD5 hash
            hash_md5 = hashlib.md5()
            for chunk in adspot.original_file.chunks():
                hash_md5.update(chunk)
            adspot.md5_hash = hash_md5.hexdigest()
            adspot.save()
            
            # Trigger async processing
            process_adspot_file.delay(adspot.id)
    
    def perform_update(self, serializer):
        """Update adspot and handle file changes."""
        old_file = None
        if self.get_object().original_file:
            old_file = self.get_object().original_file.path
        
        adspot = serializer.save(updated_by=self.request.user)
        
        # If file changed, reprocess
        if 'original_file' in serializer.validated_data:
            if adspot.original_file:
                adspot.file_size = adspot.original_file.size
                adspot.filename = adspot.original_file.name
                adspot.is_processed = False
                adspot.processing_status = 'pending'
                
                # Calculate MD5 hash
                hash_md5 = hashlib.md5()
                for chunk in adspot.original_file.chunks():
                    hash_md5.update(chunk)
                adspot.md5_hash = hash_md5.hexdigest()
                adspot.save()
                
                # Trigger async processing
                process_adspot_file.delay(adspot.id)
    
    @action(detail=True, methods=['post'])
    def reprocess(self, request, pk=None):
        """Reprocess adspot file."""
        adspot = self.get_object()
        
        if not adspot.original_file:
            return Response(
                {'error': 'No file to process'},
                status=status.HTTP_400_BAD_REQUEST
            )
        
        adspot.is_processed = False
        adspot.processing_status = 'pending'
        adspot.error_message = None
        adspot.save()
        
        # Trigger async processing
        process_adspot_file.delay(adspot.id)
        
        return Response({'message': 'Reprocessing started'})
    
    @action(detail=True, methods=['get'])
    def analytics(self, request, pk=None):
        """Get analytics data for adspot."""
        adspot = self.get_object()
        
        # Get date range from query params
        start_date = request.query_params.get('start_date')
        end_date = request.query_params.get('end_date')
        
        if start_date:
            start_date = timezone.datetime.fromisoformat(start_date)
        if end_date:
            end_date = timezone.datetime.fromisoformat(end_date)
        
        analytics_data = adspot.get_impressions(start_date, end_date)
        
        return Response(analytics_data)
    
    @action(detail=False, methods=['get'])
    def statistics(self, request):
        """Get overall adspot statistics."""
        queryset = self.filter_queryset(self.get_queryset())
        
        stats = {
            'total_adspots': queryset.count(),
            'processed': queryset.filter(is_processed=True).count(),
            'pending': queryset.filter(processing_status='pending').count(),
            'processing': queryset.filter(processing_status='processing').count(),
            'failed': queryset.filter(processing_status='failed').count(),
            'total_file_size': queryset.aggregate(
                total_size=Sum('file_size')
            )['total_size'] or 0,
            'by_format': queryset.values('format').annotate(
                count=Count('id')
            ).order_by('-count'),
            'by_campaign': queryset.values(
                'campaign__name'
            ).annotate(
                count=Count('id')
            ).order_by('-count')[:10],
            'by_brand': queryset.values(
                'brand__name'
            ).annotate(
                count=Count('id')
            ).order_by('-count')[:10],
        }
        
        return Response(stats)
    
    @action(detail=False, methods=['post'])
    def bulk_reprocess(self, request):
        """Bulk reprocess selected adspots."""
        adspot_ids = request.data.get('adspot_ids', [])
        
        if not adspot_ids:
            return Response(
                {'error': 'No adspot IDs provided'},
                status=status.HTTP_400_BAD_REQUEST
            )
        
        adspots = Adspot.objects.filter(id__in=adspot_ids)
        updated_count = 0
        
        for adspot in adspots:
            if adspot.original_file:
                adspot.is_processed = False
                adspot.processing_status = 'pending'
                adspot.error_message = None
                adspot.save()
                
                # Trigger async processing
                process_adspot_file.delay(adspot.id)
                updated_count += 1
        
        return Response({
            'message': f'{updated_count} adspots queued for reprocessing'
        })


class AvailViewSet(viewsets.ModelViewSet):
    """ViewSet for Avail model."""
    
    queryset = Avail.objects.all()
    serializer_class = AvailSerializer
    permission_classes = [IsAuthenticated]
    filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
    filterset_class = AvailFilter
    search_fields = ['avail_start', 'avail_in_window']
    ordering_fields = ['avail_start', 'created_at']
    ordering = ['avail_start']
    
    @action(detail=True, methods=['get'])
    def adspots(self, request, pk=None):
        """Get adspots in this avail."""
        avail = self.get_object()
        placements = AdspotInAvail.objects.filter(avail=avail).order_by('position_in_avail')
        serializer = AdspotInAvailSerializer(placements, many=True)
        return Response(serializer.data)
    
    @action(detail=True, methods=['post'])
    def add_adspot(self, request, pk=None):
        """Add adspot to this avail."""
        avail = self.get_object()
        adspot_id = request.data.get('adspot_id')
        position = request.data.get('position_in_avail')
        traffic_id = request.data.get('traffic_id')
        
        if not adspot_id:
            return Response(
                {'error': 'Adspot ID is required'},
                status=status.HTTP_400_BAD_REQUEST
            )
        
        try:
            adspot = Adspot.objects.get(id=adspot_id)
        except Adspot.DoesNotExist:
            return Response(
                {'error': 'Adspot not found'},
                status=status.HTTP_404_NOT_FOUND
            )
        
        # Check if position is already taken
        if position and AdspotInAvail.objects.filter(
            avail=avail, position_in_avail=position
        ).exists():
            return Response(
                {'error': 'Position already taken'},
                status=status.HTTP_400_BAD_REQUEST
            )
        
        placement = AdspotInAvail.objects.create(
            avail=avail,
            adspot=adspot,
            position_in_avail=position,
            traffic_id=traffic_id,
            created_by=request.user
        )
        
        serializer = AdspotInAvailSerializer(placement)
        return Response(serializer.data, status=status.HTTP_201_CREATED)


class WindowViewSet(viewsets.ModelViewSet):
    """ViewSet for Window model."""
    
    queryset = Window.objects.all()
    serializer_class = WindowSerializer
    permission_classes = [IsAuthenticated]
    filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
    filterset_class = WindowFilter
    search_fields = ['window_start', 'window_end']
    ordering_fields = ['window_start', 'created_at']
    ordering = ['window_start']
    
    @action(detail=True, methods=['get'])
    def avails(self, request, pk=None):
        """Get avails in this window."""
        window = self.get_object()
        avails = window.avails.all().order_by('avail_start')
        serializer = AvailSerializer(avails, many=True)
        return Response(serializer.data)


class AdspotInAvailViewSet(viewsets.ModelViewSet):
    """ViewSet for AdspotInAvail model."""
    
    queryset = AdspotInAvail.objects.all()
    serializer_class = AdspotInAvailSerializer
    permission_classes = [IsAuthenticated]
    filter_backends = [DjangoFilterBackend, filters.OrderingFilter]
    ordering_fields = ['avail', 'position_in_avail', 'created_at']
    ordering = ['avail', 'position_in_avail']
    
    def get_queryset(self):
        """Filter queryset based on query parameters."""
        queryset = super().get_queryset()
        
        avail_id = self.request.query_params.get('avail_id')
        if avail_id:
            queryset = queryset.filter(avail_id=avail_id)
        
        adspot_id = self.request.query_params.get('adspot_id')
        if adspot_id:
            queryset = queryset.filter(adspot_id=adspot_id)
        
        return queryset


class PendingViewSet(viewsets.ModelViewSet):
    """ViewSet for Pending model."""
    
    queryset = Pending.objects.all()
    serializer_class = PendingSerializer
    permission_classes = [IsAuthenticated]
    filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
    search_fields = ['creative_id', 'url']
    ordering_fields = ['creative_id', 'status', 'created_at']
    ordering = ['-created_at']
    
    def get_queryset(self):
        """Filter queryset based on query parameters."""
        queryset = super().get_queryset()
        
        status_filter = self.request.query_params.get('status')
        if status_filter:
            queryset = queryset.filter(status=status_filter)
        
        return queryset
    
    @action(detail=True, methods=['post'])
    def approve(self, request, pk=None):
        """Approve pending creative."""
        pending = self.get_object()
        pending.status = 'approved'
        pending.save()
        
        return Response({'message': 'Creative approved'})
    
    @action(detail=True, methods=['post'])
    def reject(self, request, pk=None):
        """Reject pending creative."""
        pending = self.get_object()
        pending.status = 'rejected'
        pending.error_message = request.data.get('reason', 'Rejected by admin')
        pending.save()
        
        return Response({'message': 'Creative rejected'})
    
    @action(detail=False, methods=['post'])
    def bulk_approve(self, request):
        """Bulk approve pending creatives."""
        pending_ids = request.data.get('pending_ids', [])
        
        if not pending_ids:
            return Response(
                {'error': 'No pending IDs provided'},
                status=status.HTTP_400_BAD_REQUEST
            )
        
        updated_count = Pending.objects.filter(
            id__in=pending_ids,
            status='pending'
        ).update(status='approved')
        
        return Response({
            'message': f'{updated_count} pending creatives approved'
        })
    
    @action(detail=False, methods=['post'])
    def bulk_reject(self, request):
        """Bulk reject pending creatives."""
        pending_ids = request.data.get('pending_ids', [])
        reason = request.data.get('reason', 'Bulk rejected by admin')
        
        if not pending_ids:
            return Response(
                {'error': 'No pending IDs provided'},
                status=status.HTTP_400_BAD_REQUEST
            )
        
        updated_count = Pending.objects.filter(
            id__in=pending_ids,
            status='pending'
        ).update(status='rejected', error_message=reason)
        
        return Response({
            'message': f'{updated_count} pending creatives rejected'
        })


# Django Template Views (for web interface)

class AdspotListView(LoginRequiredMixin, ListView):
    """List view for adspots."""
    model = Adspot
    template_name = 'adspots/adspot_list.html'
    context_object_name = 'adspots'
    paginate_by = 20
    
    def get_queryset(self):
        """Filter queryset based on search and filters."""
        queryset = super().get_queryset()
        
        search = self.request.GET.get('search')
        if search:
            queryset = queryset.filter(
                Q(name__icontains=search) |
                Q(creative_id__icontains=search) |
                Q(filename__icontains=search)
            )
        
        status_filter = self.request.GET.get('status')
        if status_filter:
            queryset = queryset.filter(processing_status=status_filter)
        
        campaign_filter = self.request.GET.get('campaign')
        if campaign_filter:
            queryset = queryset.filter(campaign_id=campaign_filter)
        
        return queryset.order_by('-created_at')


class AdspotDetailView(LoginRequiredMixin, DetailView):
    """Detail view for adspot."""
    model = Adspot
    template_name = 'adspots/adspot_detail.html'
    context_object_name = 'adspot'
    
    def get_context_data(self, **kwargs):
        """Add additional context data."""
        context = super().get_context_data(**kwargs)
        
        # Get analytics data
        context['analytics'] = self.object.get_impressions()
        
        # Get placements
        context['placements'] = self.object.avail_placements.all()
        
        return context


class AdspotCreateView(LoginRequiredMixin, CreateView):
    """Create view for adspot."""
    model = Adspot
    template_name = 'adspots/adspot_form.html'
    fields = [
        'name', 'campaign', 'brand', 'channel',
        'original_file', 'creative_id', 'vast_url'
    ]
    success_url = reverse_lazy('adspots:adspot-list')
    
    def form_valid(self, form):
        """Set created_by and trigger processing."""
        form.instance.created_by = self.request.user
        response = super().form_valid(form)
        
        # Trigger file processing if file is uploaded
        if self.object.original_file:
            self.object.file_size = self.object.original_file.size
            self.object.filename = self.object.original_file.name
            self.object.save()
            
            # Trigger async processing
            process_adspot_file.delay(self.object.id)
            
            messages.success(
                self.request,
                'Adspot created successfully. File processing started.'
            )
        
        return response


class AdspotUpdateView(LoginRequiredMixin, UpdateView):
    """Update view for adspot."""
    model = Adspot
    template_name = 'adspots/adspot_form.html'
    fields = [
        'name', 'campaign', 'brand', 'channel',
        'original_file', 'creative_id', 'vast_url', 'status'
    ]
    success_url = reverse_lazy('adspots:adspot-list')
    
    def form_valid(self, form):
        """Set updated_by and handle file changes."""
        form.instance.updated_by = self.request.user
        
        # Check if file changed
        file_changed = 'original_file' in form.changed_data
        
        response = super().form_valid(form)
        
        if file_changed and self.object.original_file:
            self.object.file_size = self.object.original_file.size
            self.object.filename = self.object.original_file.name
            self.object.is_processed = False
            self.object.processing_status = 'pending'
            self.object.save()
            
            # Trigger async processing
            process_adspot_file.delay(self.object.id)
            
            messages.success(
                self.request,
                'Adspot updated successfully. File reprocessing started.'
            )
        
        return response


class AdspotDeleteView(LoginRequiredMixin, DeleteView):
    """Delete view for adspot."""
    model = Adspot
    template_name = 'adspots/adspot_confirm_delete.html'
    success_url = reverse_lazy('adspots:adspot-list')
    
    def delete(self, request, *args, **kwargs):
        """Delete adspot and associated files."""
        self.object = self.get_object()
        
        # Delete associated files
        if self.object.original_file:
            default_storage.delete(self.object.original_file.name)
        if self.object.encoded_file:
            default_storage.delete(self.object.encoded_file.name)
        
        messages.success(request, 'Adspot deleted successfully.')
        return super().delete(request, *args, **kwargs)