"""
Представление для страницы статистики.
"""
import json
from datetime import timedelta
from django.db.models import Count, Q, Min, Sum, F, Subquery, OuterRef
from django.db.models.functions import TruncDate, Abs
from django.utils import timezone
from django.views.generic import TemplateView
from django.http import JsonResponse

from ..models import ObjItem, Source, Satellite, Geo, SourceRequest, SourceRequestStatusHistory
from ..permissions import PermissionRequiredMixin
from mapsapp.models import Transponders


class StatisticsView(PermissionRequiredMixin, TemplateView):
    """Страница статистики по данным геолокации."""
    permission_required = 'statistics_view'
    
    template_name = 'mainapp/statistics.html'
    
    def get_date_range(self):
        """Получает диапазон дат из параметров запроса."""
        date_from = self.request.GET.get('date_from')
        date_to = self.request.GET.get('date_to')
        preset = self.request.GET.get('preset')
        
        now = timezone.now()
        
        # Обработка пресетов
        if preset == 'week':
            date_from = (now - timedelta(days=7)).date()
            date_to = now.date()
        elif preset == 'month':
            date_from = (now - timedelta(days=30)).date()
            date_to = now.date()
        elif preset == '3months':
            date_from = (now - timedelta(days=90)).date()
            date_to = now.date()
        elif preset == '6months':
            date_from = (now - timedelta(days=180)).date()
            date_to = now.date()
        elif preset == 'all':
            date_from = None
            date_to = None
        else:
            # Парсинг дат из параметров
            from datetime import datetime
            if date_from:
                try:
                    date_from = datetime.strptime(date_from, '%Y-%m-%d').date()
                except ValueError:
                    date_from = None
            if date_to:
                try:
                    date_to = datetime.strptime(date_to, '%Y-%m-%d').date()
                except ValueError:
                    date_to = None
        
        return date_from, date_to, preset
    
    def get_selected_satellites(self):
        """Получает выбранные спутники из параметров запроса."""
        satellite_ids = self.request.GET.getlist('satellite_id')
        return [int(sid) for sid in satellite_ids if sid.isdigit()]
    
    def get_selected_location_places(self):
        """Получает выбранные комплексы из параметров запроса."""
        return self.request.GET.getlist('location_place')
    
    def get_base_queryset(self, date_from, date_to, satellite_ids, location_places=None):
        """Возвращает базовый queryset ObjItem с фильтрами."""
        qs = ObjItem.objects.filter(
            geo_obj__isnull=False,
            geo_obj__timestamp__isnull=False
        )
        
        if date_from:
            qs = qs.filter(geo_obj__timestamp__date__gte=date_from)
        if date_to:
            qs = qs.filter(geo_obj__timestamp__date__lte=date_to)
        if satellite_ids:
            qs = qs.filter(parameter_obj__id_satellite__id__in=satellite_ids)
        if location_places:
            qs = qs.filter(parameter_obj__id_satellite__location_place__in=location_places)
        
        return qs
    
    def get_statistics(self, date_from, date_to, satellite_ids, location_places=None):
        """Вычисляет основную статистику."""
        base_qs = self.get_base_queryset(date_from, date_to, satellite_ids, location_places)
        
        # Общее количество точек
        total_points = base_qs.count()
        
        # Количество уникальных объектов (Source)
        total_sources = base_qs.filter(source__isnull=False).values('source').distinct().count()
        
        # Новые излучения - объекты, у которых имя появилось впервые в выбранном периоде
        new_emissions_data = self._calculate_new_emissions(date_from, date_to, satellite_ids, location_places)
        
        # Статистика по спутникам
        satellite_stats = self._get_satellite_statistics(date_from, date_to, satellite_ids, location_places)
        
        # Данные для графика по дням
        daily_data = self._get_daily_statistics(date_from, date_to, satellite_ids, location_places)
        
        return {
            'total_points': total_points,
            'total_sources': total_sources,
            'new_emissions_count': new_emissions_data['count'],
            'new_emission_objects': new_emissions_data['objects'],
            'satellite_stats': satellite_stats,
            'daily_data': daily_data,
        }
    
    def _calculate_new_emissions(self, date_from, date_to, satellite_ids, location_places=None):
        """
        Вычисляет новые излучения - уникальные имена объектов, 
        которые появились впервые в выбранном периоде.
        
        Возвращает количество уникальных новых имён и данные об объектах.
        Оптимизировано для минимизации SQL запросов.
        """
        if not date_from:
            # Если нет начальной даты, берём все данные - новых излучений нет
            return {'count': 0, 'objects': []}
        
        # Получаем все имена объектов, которые появились ДО выбранного периода
        existing_names = set(
            ObjItem.objects.filter(
                geo_obj__isnull=False,
                geo_obj__timestamp__isnull=False,
                geo_obj__timestamp__date__lt=date_from,
                name__isnull=False
            ).exclude(name='').values_list('name', flat=True).distinct()
        )
        
        # Базовый queryset для выбранного периода
        period_qs = self.get_base_queryset(date_from, date_to, satellite_ids, location_places).filter(
            name__isnull=False
        ).exclude(name='')
        
        # Получаем уникальные имена в выбранном периоде
        period_names = set(period_qs.values_list('name', flat=True).distinct())
        
        # Новые имена = имена в периоде, которых не было раньше
        new_names = period_names - existing_names
        
        if not new_names:
            return {'count': 0, 'objects': []}
        
        # Оптимизация: получаем все данные одним запросом с группировкой по имени
        # Используем values() для получения уникальных комбинаций name + info + ownership
        objitems_data = period_qs.filter(
            name__in=new_names
        ).select_related(
            'source__info', 'source__ownership'
        ).values(
            'name',
            'source__info__name',
            'source__ownership__name'
        ).distinct()
        
        # Собираем данные, оставляя только первую запись для каждого имени
        seen_names = set()
        new_objects = []
        
        for item in objitems_data:
            name = item['name']
            if name not in seen_names:
                seen_names.add(name)
                new_objects.append({
                    'name': name,
                    'info': item['source__info__name'] or '-',
                    'ownership': item['source__ownership__name'] or '-',
                })
        
        # Сортируем по имени
        new_objects.sort(key=lambda x: x['name'])
        
        return {'count': len(new_names), 'objects': new_objects}
    
    def _get_satellite_statistics(self, date_from, date_to, satellite_ids, location_places=None):
        """Получает статистику по каждому спутнику."""
        base_qs = self.get_base_queryset(date_from, date_to, satellite_ids, location_places)
        
        # Группируем по спутникам
        stats = base_qs.filter(
            parameter_obj__id_satellite__isnull=False
        ).values(
            'parameter_obj__id_satellite__id',
            'parameter_obj__id_satellite__name'
        ).annotate(
            points_count=Count('id'),
            sources_count=Count('source', distinct=True)
        ).order_by('-points_count')
        
        return list(stats)
    
    def _get_daily_statistics(self, date_from, date_to, satellite_ids, location_places=None):
        """Получает статистику по дням для графика."""
        base_qs = self.get_base_queryset(date_from, date_to, satellite_ids, location_places)
        
        daily = base_qs.annotate(
            date=TruncDate('geo_obj__timestamp')
        ).values('date').annotate(
            points=Count('id'),
            sources=Count('source', distinct=True)
        ).order_by('date')
        
        return list(daily)
    
    def _get_zone_statistics(self, date_from, date_to, location_place):
        """
        Получает статистику по зоне (КР или ДВ).
        
        Возвращает:
        - total_coords: общее количество координат ГЛ
        - new_coords: количество новых координат ГЛ (уникальные имена, появившиеся впервые)
        - transfer_delta: сумма дельт переносов по новым транспондерам
        """
        # Базовый queryset для зоны
        zone_qs = ObjItem.objects.filter(
            geo_obj__isnull=False,
            geo_obj__timestamp__isnull=False,
            parameter_obj__id_satellite__location_place=location_place
        )
        
        if date_from:
            zone_qs = zone_qs.filter(geo_obj__timestamp__date__gte=date_from)
        if date_to:
            zone_qs = zone_qs.filter(geo_obj__timestamp__date__lte=date_to)
        
        # Общее количество координат ГЛ
        total_coords = zone_qs.count()
        
        # Новые координаты ГЛ (уникальные имена, появившиеся впервые в периоде)
        new_coords = 0
        if date_from:
            # Имена, которые были ДО периода
            existing_names = set(
                ObjItem.objects.filter(
                    geo_obj__isnull=False,
                    geo_obj__timestamp__isnull=False,
                    geo_obj__timestamp__date__lt=date_from,
                    parameter_obj__id_satellite__location_place=location_place,
                    name__isnull=False
                ).exclude(name='').values_list('name', flat=True).distinct()
            )
            
            # Имена в периоде
            period_names = set(
                zone_qs.filter(name__isnull=False).exclude(name='').values_list('name', flat=True).distinct()
            )
            
            new_coords = len(period_names - existing_names)
        
        # Расчёт дельты переносов по новым транспондерам
        transfer_delta = self._calculate_transfer_delta(date_from, date_to, location_place)
        
        return {
            'total_coords': total_coords,
            'new_coords': new_coords,
            'transfer_delta': transfer_delta,
        }
    
    def _calculate_transfer_delta(self, date_from, date_to, location_place):
        """
        Вычисляет сумму дельт по downlink для новых транспондеров.
        
        Логика:
        1. Берём все новые транспондеры за период (по created_at)
        2. Для каждого ищем предыдущий транспондер с таким же именем, спутником и зоной
        3. Вычисляем дельту по downlink
        4. Суммируем все дельты
        """
        if not date_from:
            return 0.0
        
        # Новые транспондеры за период для данной зоны
        new_transponders_qs = Transponders.objects.filter(
            sat_id__location_place=location_place,
            created_at__date__gte=date_from
        )
        if date_to:
            new_transponders_qs = new_transponders_qs.filter(created_at__date__lte=date_to)
        
        total_delta = 0.0
        
        for transponder in new_transponders_qs:
            if not transponder.name or not transponder.sat_id or transponder.downlink is None:
                continue
            
            # Ищем предыдущий транспондер с таким же именем, спутником и зоной
            previous = Transponders.objects.filter(
                name=transponder.name,
                sat_id=transponder.sat_id,
                zone_name=transponder.zone_name,
                created_at__lt=transponder.created_at,
                downlink__isnull=False
            ).order_by('-created_at').first()
            
            if previous and previous.downlink is not None:
                delta = abs(transponder.downlink - previous.downlink)
                total_delta += delta
        
        return round(total_delta, 2)
    
    def _get_kubsat_statistics(self, date_from, date_to):
        """
        Получает статистику по Кубсатам из SourceRequest.
        
        Возвращает:
        - planned_count: количество запланированных сеансов
        - conducted_count: количество проведённых
        - canceled_gso_count: количество отменённых ГСО
        - canceled_kub_count: количество отменённых МКА
        """
        # Базовый queryset для заявок
        requests_qs = SourceRequest.objects.all()
        
        # Фильтруем по дате создания или planned_at
        if date_from:
            requests_qs = requests_qs.filter(
                Q(created_at__date__gte=date_from) | Q(planned_at__date__gte=date_from)
            )
        if date_to:
            requests_qs = requests_qs.filter(
                Q(created_at__date__lte=date_to) | Q(planned_at__date__lte=date_to)
            )
        
        # Получаем ID заявок, у которых в истории был статус 'planned'
        # Это заявки, которые были запланированы в выбранном периоде
        history_qs = SourceRequestStatusHistory.objects.filter(
            new_status='planned'
        )
        if date_from:
            history_qs = history_qs.filter(changed_at__date__gte=date_from)
        if date_to:
            history_qs = history_qs.filter(changed_at__date__lte=date_to)
        
        planned_request_ids = set(history_qs.values_list('source_request_id', flat=True))
        
        # Также добавляем заявки, которые были созданы со статусом 'planned' в периоде
        created_planned_qs = SourceRequest.objects.filter(status='planned')
        if date_from:
            created_planned_qs = created_planned_qs.filter(created_at__date__gte=date_from)
        if date_to:
            created_planned_qs = created_planned_qs.filter(created_at__date__lte=date_to)
        
        planned_request_ids.update(created_planned_qs.values_list('id', flat=True))
        
        planned_count = len(planned_request_ids)
        
        # Считаем статусы из истории для запланированных заявок
        conducted_count = 0
        canceled_gso_count = 0
        canceled_kub_count = 0
        
        if planned_request_ids:
            # Получаем историю статусов для запланированных заявок
            status_history = SourceRequestStatusHistory.objects.filter(
                source_request_id__in=planned_request_ids
            )
            if date_from:
                status_history = status_history.filter(changed_at__date__gte=date_from)
            if date_to:
                status_history = status_history.filter(changed_at__date__lte=date_to)
            
            # Считаем уникальные заявки по каждому статусу
            conducted_ids = set(status_history.filter(new_status='conducted').values_list('source_request_id', flat=True))
            canceled_gso_ids = set(status_history.filter(new_status='canceled_gso').values_list('source_request_id', flat=True))
            canceled_kub_ids = set(status_history.filter(new_status='canceled_kub').values_list('source_request_id', flat=True))
            
            conducted_count = len(conducted_ids)
            canceled_gso_count = len(canceled_gso_ids)
            canceled_kub_count = len(canceled_kub_ids)
        
        return {
            'planned_count': planned_count,
            'conducted_count': conducted_count,
            'canceled_gso_count': canceled_gso_count,
            'canceled_kub_count': canceled_kub_count,
        }
    
    def get_extended_statistics(self, date_from, date_to):
        """Получает расширенную статистику по зонам и Кубсатам."""
        kr_stats = self._get_zone_statistics(date_from, date_to, 'kr')
        dv_stats = self._get_zone_statistics(date_from, date_to, 'dv')
        kubsat_stats = self._get_kubsat_statistics(date_from, date_to)
        
        return {
            'kr': kr_stats,
            'dv': dv_stats,
            'kubsat': kubsat_stats,
        }
    
    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        
        date_from, date_to, preset = self.get_date_range()
        satellite_ids = self.get_selected_satellites()
        location_places = self.get_selected_location_places()
        
        # Получаем только спутники, у которых есть точки ГЛ
        satellites_with_points = ObjItem.objects.filter(
            geo_obj__isnull=False,
            geo_obj__timestamp__isnull=False,
            parameter_obj__id_satellite__isnull=False
        ).values_list('parameter_obj__id_satellite__id', flat=True).distinct()
        
        satellites = Satellite.objects.filter(
            id__in=satellites_with_points
        ).order_by('name')
        
        # Получаем статистику
        stats = self.get_statistics(date_from, date_to, satellite_ids, location_places)
        
        # Получаем расширенную статистику
        extended_stats = self.get_extended_statistics(date_from, date_to)
        
        # Сериализуем данные для JavaScript
        daily_data_json = json.dumps([
            {
                'date': item['date'].isoformat() if item['date'] else None,
                'points': item['points'],
                'sources': item['sources'],
            }
            for item in stats['daily_data']
        ])
        
        satellite_stats_json = json.dumps(stats['satellite_stats'])
        extended_stats_json = json.dumps(extended_stats)
        
        context.update({
            'satellites': satellites,
            'selected_satellites': satellite_ids,
            'location_places': Satellite.PLACES,
            'selected_location_places': location_places,
            'date_from': date_from.isoformat() if date_from else '',
            'date_to': date_to.isoformat() if date_to else '',
            'preset': preset or '',
            'total_points': stats['total_points'],
            'total_sources': stats['total_sources'],
            'new_emissions_count': stats['new_emissions_count'],
            'new_emission_objects': stats['new_emission_objects'],
            'satellite_stats': stats['satellite_stats'],
            'daily_data': daily_data_json,
            'satellite_stats_json': satellite_stats_json,
            'extended_stats': extended_stats,
            'extended_stats_json': extended_stats_json,
        })
        
        return context


class StatisticsAPIView(StatisticsView):
    """API endpoint для получения статистики в JSON формате."""
    
    def get(self, request, *args, **kwargs):
        date_from, date_to, preset = self.get_date_range()
        satellite_ids = self.get_selected_satellites()
        location_places = self.get_selected_location_places()
        stats = self.get_statistics(date_from, date_to, satellite_ids, location_places)
        extended_stats = self.get_extended_statistics(date_from, date_to)
        
        # Преобразуем даты в строки для JSON
        daily_data = []
        for item in stats['daily_data']:
            daily_data.append({
                'date': item['date'].isoformat() if item['date'] else None,
                'points': item['points'],
                'sources': item['sources'],
            })
        
        return JsonResponse({
            'total_points': stats['total_points'],
            'total_sources': stats['total_sources'],
            'new_emissions_count': stats['new_emissions_count'],
            'new_emission_objects': stats['new_emission_objects'],
            'satellite_stats': stats['satellite_stats'],
            'daily_data': daily_data,
            'extended_stats': extended_stats,
        })


class ExtendedStatisticsAPIView(StatisticsView):
    """API endpoint для получения расширенной статистики в JSON формате."""
    
    def get(self, request, *args, **kwargs):
        date_from, date_to, preset = self.get_date_range()
        extended_stats = self.get_extended_statistics(date_from, date_to)
        
        return JsonResponse({
            'extended_stats': extended_stats,
            'date_from': date_from.isoformat() if date_from else None,
            'date_to': date_to.isoformat() if date_to else None,
        })
