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

from ..models import ObjItem, Source, Satellite, Geo


class StatisticsView(TemplateView):
    """Страница статистики по данным геолокации."""
    
    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_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)
        
        # Сериализуем данные для 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'])
        
        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,
        })
        
        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)
        
        # Преобразуем даты в строки для 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,
        })
