"""
Представления для управления заявками на источники.
"""
from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import JsonResponse
from django.shortcuts import render
from django.views import View
from django.views.generic import ListView, CreateView, UpdateView
from django.urls import reverse_lazy
from django.db.models import Q
from django.utils import timezone

from mainapp.models import SourceRequest, SourceRequestStatusHistory, Source, Satellite
from mainapp.forms import SourceRequestForm
from mainapp.permissions import PermissionRequiredMixin

import re
import pandas as pd
from datetime import datetime
from django.contrib.gis.geos import Point


class SourceRequestListView(LoginRequiredMixin, ListView):
    """Список заявок на источники."""
    model = SourceRequest
    template_name = 'mainapp/source_request_list.html'
    context_object_name = 'requests'
    paginate_by = 50
    
    def get_queryset(self):
        queryset = SourceRequest.objects.select_related(
            'source', 'source__info', 'source__ownership',
            'satellite',
            'created_by__user', 'updated_by__user'
        ).order_by('-created_at')
        
        # Фильтр по статусу
        status = self.request.GET.get('status')
        if status:
            queryset = queryset.filter(status=status)
        
        # Фильтр по приоритету
        priority = self.request.GET.get('priority')
        if priority:
            queryset = queryset.filter(priority=priority)
        
        # Фильтр по источнику
        source_id = self.request.GET.get('source_id')
        if source_id:
            queryset = queryset.filter(source_id=source_id)
        
        # Фильтр по ГСО успешно
        gso_success = self.request.GET.get('gso_success')
        if gso_success == 'true':
            queryset = queryset.filter(gso_success=True)
        elif gso_success == 'false':
            queryset = queryset.filter(gso_success=False)
        
        # Фильтр по Кубсат успешно
        kubsat_success = self.request.GET.get('kubsat_success')
        if kubsat_success == 'true':
            queryset = queryset.filter(kubsat_success=True)
        elif kubsat_success == 'false':
            queryset = queryset.filter(kubsat_success=False)
        
        # Поиск
        search = self.request.GET.get('search')
        if search:
            queryset = queryset.filter(
                Q(source__id__icontains=search) |
                Q(comment__icontains=search)
            )
        
        return queryset
    
    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['status_choices'] = SourceRequest.STATUS_CHOICES
        context['priority_choices'] = SourceRequest.PRIORITY_CHOICES
        context['current_status'] = self.request.GET.get('status', '')
        context['current_priority'] = self.request.GET.get('priority', '')
        context['search_query'] = self.request.GET.get('search', '')
        context['form'] = SourceRequestForm()
        return context


class SourceRequestCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
    """Создание заявки на источник."""
    permission_required = 'request_create'
    model = SourceRequest
    form_class = SourceRequestForm
    template_name = 'mainapp/source_request_form.html'
    success_url = reverse_lazy('mainapp:source_request_list')
    
    def get_form_kwargs(self):
        kwargs = super().get_form_kwargs()
        # Передаем source_id если он есть в GET параметрах
        source_id = self.request.GET.get('source_id')
        if source_id:
            kwargs['source_id'] = source_id
        return kwargs
    
    def form_valid(self, form):
        # Устанавливаем created_by
        form.instance.created_by = getattr(self.request.user, 'customuser', None)
        form.instance.updated_by = getattr(self.request.user, 'customuser', None)
        
        response = super().form_valid(form)
        
        # Создаем начальную запись в истории
        SourceRequestStatusHistory.objects.create(
            source_request=self.object,
            old_status='',
            new_status=self.object.status,
            changed_by=form.instance.created_by,
        )
        
        # Если это AJAX запрос, возвращаем JSON
        if self.request.headers.get('X-Requested-With') == 'XMLHttpRequest':
            return JsonResponse({
                'success': True,
                'message': 'Заявка успешно создана',
                'request_id': self.object.id
            })
        
        return response
    
    def form_invalid(self, form):
        if self.request.headers.get('X-Requested-With') == 'XMLHttpRequest':
            return JsonResponse({
                'success': False,
                'errors': form.errors
            }, status=400)
        return super().form_invalid(form)


class SourceRequestUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
    """Редактирование заявки на источник."""
    permission_required = 'request_edit'
    model = SourceRequest
    form_class = SourceRequestForm
    template_name = 'mainapp/source_request_form.html'
    success_url = reverse_lazy('mainapp:source_request_list')
    
    def form_valid(self, form):
        # Устанавливаем updated_by
        form.instance.updated_by = getattr(self.request.user, 'customuser', None)
        
        response = super().form_valid(form)
        
        # Если это AJAX запрос, возвращаем JSON
        if self.request.headers.get('X-Requested-With') == 'XMLHttpRequest':
            return JsonResponse({
                'success': True,
                'message': 'Заявка успешно обновлена',
                'request_id': self.object.id
            })
        
        return response
    
    def form_invalid(self, form):
        if self.request.headers.get('X-Requested-With') == 'XMLHttpRequest':
            return JsonResponse({
                'success': False,
                'errors': form.errors
            }, status=400)
        return super().form_invalid(form)


class SourceRequestDeleteView(LoginRequiredMixin, PermissionRequiredMixin, View):
    """Удаление заявки на источник."""
    permission_required = 'request_delete'
    
    def post(self, request, pk):
        try:
            source_request = SourceRequest.objects.get(pk=pk)
            source_request.delete()
            return JsonResponse({
                'success': True,
                'message': 'Заявка успешно удалена'
            })
        except SourceRequest.DoesNotExist:
            return JsonResponse({
                'success': False,
                'error': 'Заявка не найдена'
            }, status=404)


class SourceRequestBulkDeleteView(LoginRequiredMixin, PermissionRequiredMixin, View):
    """Массовое удаление заявок."""
    permission_required = 'request_delete'
    
    def post(self, request):
        import json
        try:
            data = json.loads(request.body)
            ids = data.get('ids', [])
            
            if not ids:
                return JsonResponse({
                    'success': False,
                    'error': 'Не выбраны заявки для удаления'
                }, status=400)
            
            deleted_count, _ = SourceRequest.objects.filter(pk__in=ids).delete()
            
            return JsonResponse({
                'success': True,
                'message': 'Заявки удалены',
                'deleted_count': deleted_count
            })
        except json.JSONDecodeError:
            return JsonResponse({
                'success': False,
                'error': 'Неверный формат данных'
            }, status=400)


class SourceRequestExportView(LoginRequiredMixin, View):
    """Экспорт заявок в Excel."""
    
    def get(self, request):
        from django.http import HttpResponse
        from openpyxl import Workbook
        from openpyxl.styles import Font, Alignment, PatternFill
        from io import BytesIO
        
        # Получаем заявки с фильтрами
        queryset = SourceRequest.objects.select_related(
            'satellite'
        ).order_by('-created_at')
        
        # Применяем фильтры
        status = request.GET.get('status')
        if status:
            queryset = queryset.filter(status=status)
        
        priority = request.GET.get('priority')
        if priority:
            queryset = queryset.filter(priority=priority)
        
        gso_success = request.GET.get('gso_success')
        if gso_success == 'true':
            queryset = queryset.filter(gso_success=True)
        elif gso_success == 'false':
            queryset = queryset.filter(gso_success=False)
        
        kubsat_success = request.GET.get('kubsat_success')
        if kubsat_success == 'true':
            queryset = queryset.filter(kubsat_success=True)
        elif kubsat_success == 'false':
            queryset = queryset.filter(kubsat_success=False)
        
        # Создаём Excel файл
        wb = Workbook()
        ws = wb.active
        ws.title = "Заявки"
        
        # Заголовки (как в импорте, но без источника + приоритет + статус + комментарий)
        headers = [
            'Дата постановки задачи',
            'Дата формирования карточки',
            'Дата проведения',
            'Спутник',
            'Частота Downlink',
            'Частота Uplink',
            'Перенос',
            'Координаты ГСО',
            'Район',
            'Приоритет',
            'Статус',
            'Результат ГСО',
            'Результат кубсата',
            'Координаты источника',
            'Координаты объекта',
            'Комментарий',
        ]
        
        # Стили
        header_font = Font(bold=True)
        header_fill = PatternFill(start_color="DDDDDD", end_color="DDDDDD", fill_type="solid")
        green_fill = PatternFill(start_color="90EE90", end_color="90EE90", fill_type="solid")
        red_fill = PatternFill(start_color="FF6B6B", end_color="FF6B6B", fill_type="solid")
        gray_fill = PatternFill(start_color="D3D3D3", end_color="D3D3D3", fill_type="solid")
        
        # Записываем заголовки
        for col_num, header in enumerate(headers, 1):
            cell = ws.cell(row=1, column=col_num, value=header)
            cell.font = header_font
            cell.fill = header_fill
            cell.alignment = Alignment(horizontal='center', vertical='center')
        
        # Записываем данные
        for row_num, req in enumerate(queryset, 2):
            # Дата постановки задачи
            ws.cell(row=row_num, column=1, value=req.request_date.strftime('%d.%m.%Y') if req.request_date else '')
            
            # Дата формирования карточки
            ws.cell(row=row_num, column=2, value=req.card_date.strftime('%d.%m.%Y') if req.card_date else '')
            
            # Дата проведения
            planned_at_local = timezone.localtime(req.planned_at) if req.planned_at else None
            planned_at_str = ''
            if planned_at_local:
                if planned_at_local.hour == 0 and planned_at_local.minute == 0:
                    planned_at_str = planned_at_local.strftime('%d.%m.%y')
                else:
                    planned_at_str = planned_at_local.strftime('%d.%m.%y %H:%M')
            ws.cell(row=row_num, column=3, value=planned_at_str)
            
            # Спутник
            satellite_str = ''
            if req.satellite:
                satellite_str = req.satellite.name
                if req.satellite.norad:
                    satellite_str += f' ({req.satellite.norad})'
            ws.cell(row=row_num, column=4, value=satellite_str)
            
            # Частота Downlink
            ws.cell(row=row_num, column=5, value=req.downlink if req.downlink else '')
            
            # Частота Uplink
            ws.cell(row=row_num, column=6, value=req.uplink if req.uplink else '')
            
            # Перенос
            ws.cell(row=row_num, column=7, value=req.transfer if req.transfer else '')
            
            # Координаты ГСО
            coords_gso = ''
            if req.coords:
                coords_gso = f'{req.coords.y:.6f} {req.coords.x:.6f}'
            ws.cell(row=row_num, column=8, value=coords_gso)
            
            # Район
            ws.cell(row=row_num, column=9, value=req.region or '')
            
            # Приоритет
            ws.cell(row=row_num, column=10, value=req.get_priority_display())
            
            # Статус (с цветом)
            status_cell = ws.cell(row=row_num, column=11, value=req.get_status_display())
            if req.status in ['successful', 'result_received']:
                status_cell.fill = green_fill
            elif req.status == 'unsuccessful':
                status_cell.fill = red_fill
            else:
                status_cell.fill = gray_fill
            
            # Результат ГСО (с цветом)
            gso_cell = ws.cell(row=row_num, column=12)
            if req.gso_success is True:
                gso_cell.value = 'Да'
                gso_cell.fill = green_fill
            elif req.gso_success is False:
                gso_cell.value = 'Нет'
                gso_cell.fill = red_fill
            else:
                gso_cell.value = ''
            
            # Результат кубсата (с цветом)
            kubsat_cell = ws.cell(row=row_num, column=13)
            if req.kubsat_success is True:
                kubsat_cell.value = 'Да'
                kubsat_cell.fill = green_fill
            elif req.kubsat_success is False:
                kubsat_cell.value = 'Нет'
                kubsat_cell.fill = red_fill
            else:
                kubsat_cell.value = ''
            
            # Координаты источника
            coords_source = ''
            if req.coords_source:
                coords_source = f'{req.coords_source.y:.6f} {req.coords_source.x:.6f}'
            ws.cell(row=row_num, column=14, value=coords_source)
            
            # Координаты объекта
            coords_object = ''
            if req.coords_object:
                coords_object = f'{req.coords_object.y:.6f} {req.coords_object.x:.6f}'
            ws.cell(row=row_num, column=15, value=coords_object)
            
            # Комментарий
            ws.cell(row=row_num, column=16, value=req.comment or '')
        
        # Автоширина колонок
        for column in ws.columns:
            max_length = 0
            column_letter = column[0].column_letter
            for cell in column:
                try:
                    if len(str(cell.value)) > max_length:
                        max_length = len(str(cell.value))
                except:
                    pass
            adjusted_width = min(max_length + 2, 40)
            ws.column_dimensions[column_letter].width = adjusted_width
        
        # Сохраняем в BytesIO
        output = BytesIO()
        wb.save(output)
        output.seek(0)
        
        # Возвращаем файл
        response = HttpResponse(
            output.read(),
            content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
        )
        filename = f'source_requests_{datetime.now().strftime("%Y%m%d_%H%M")}.xlsx'
        response['Content-Disposition'] = f'attachment; filename="{filename}"'
        
        return response


class SourceRequestAPIView(LoginRequiredMixin, View):
    """API для получения данных о заявках источника."""
    
    def get(self, request, source_id):
        try:
            source = Source.objects.get(pk=source_id)
        except Source.DoesNotExist:
            return JsonResponse({'error': 'Источник не найден'}, status=404)
        
        requests = SourceRequest.objects.filter(source=source).select_related(
            'created_by__user', 'updated_by__user'
        ).prefetch_related('status_history__changed_by__user').order_by('-created_at')
        
        data = []
        for req in requests:
            # Получаем историю статусов
            history = []
            for h in req.status_history.all().order_by('-changed_at'):
                history.append({
                    'old_status': h.get_old_status_display() if h.old_status else '-',
                    'new_status': h.get_new_status_display(),
                    'changed_at': timezone.localtime(h.changed_at).strftime('%d.%m.%Y %H:%M') if h.changed_at else '-',
                    'changed_by': str(h.changed_by) if h.changed_by else '-',
                })
            
            data.append({
                'id': req.id,
                'status': req.status,
                'status_display': req.get_status_display(),
                'priority': req.priority,
                'priority_display': req.get_priority_display(),
                'planned_at': (
                    timezone.localtime(req.planned_at).strftime('%d.%m.%Y') 
                    if req.planned_at and timezone.localtime(req.planned_at).hour == 0 and timezone.localtime(req.planned_at).minute == 0
                    else timezone.localtime(req.planned_at).strftime('%d.%m.%Y %H:%M') if req.planned_at
                    else '-'
                ),
                'request_date': req.request_date.strftime('%d.%m.%Y') if req.request_date else '-',
                'status_updated_at': timezone.localtime(req.status_updated_at).strftime('%d.%m.%Y %H:%M') if req.status_updated_at else '-',
                'gso_success': req.gso_success,
                'kubsat_success': req.kubsat_success,
                'comment': req.comment or '-',
                'created_at': timezone.localtime(req.created_at).strftime('%d.%m.%Y %H:%M') if req.created_at else '-',
                'created_by': str(req.created_by) if req.created_by else '-',
                'history': history,
            })
        
        return JsonResponse({
            'source_id': source_id,
            'requests': data,
            'count': len(data)
        })


class SourceRequestDetailAPIView(LoginRequiredMixin, View):
    """API для получения детальной информации о заявке."""
    
    def get(self, request, pk):
        try:
            req = SourceRequest.objects.select_related(
                'source', 'source__info', 'source__ownership',
                'satellite',
                'created_by__user', 'updated_by__user'
            ).prefetch_related(
                'status_history__changed_by__user',
                'source__source_objitems__parameter_obj__modulation',
                'source__source_objitems__geo_obj'
            ).get(pk=pk)
        except SourceRequest.DoesNotExist:
            return JsonResponse({'error': 'Заявка не найдена'}, status=404)
        
        # Получаем историю статусов
        history = []
        for h in req.status_history.all().order_by('-changed_at'):
            history.append({
                'old_status': h.get_old_status_display() if h.old_status else '-',
                'new_status': h.get_new_status_display(),
                'changed_at': timezone.localtime(h.changed_at).strftime('%d.%m.%Y %H:%M') if h.changed_at else '-',
                'changed_by': str(h.changed_by) if h.changed_by else '-',
            })
        
        # Получаем данные из первой точки источника (имя, модуляция, символьная скорость)
        source_data = _get_source_extra_data(req.source) if req.source else {
            'objitem_name': '-', 'modulation': '-', 'symbol_rate': '-'
        }
        
        # Координаты ГСО из заявки или из источника
        coords_lat = None
        coords_lon = None
        if req.coords:
            coords_lat = req.coords.y
            coords_lon = req.coords.x
        elif req.source and req.source.coords_average:
            coords_lat = req.source.coords_average.y
            coords_lon = req.source.coords_average.x
        
        # Координаты источника
        coords_source_lat = None
        coords_source_lon = None
        if req.coords_source:
            coords_source_lat = req.coords_source.y
            coords_source_lon = req.coords_source.x
        
        # Координаты объекта
        coords_object_lat = None
        coords_object_lon = None
        if req.coords_object:
            coords_object_lat = req.coords_object.y
            coords_object_lon = req.coords_object.x
        
        data = {
            'id': req.id,
            'source_id': req.source_id,
            'satellite_id': req.satellite_id,
            'satellite_name': req.satellite.name if req.satellite else '-',
            'status': req.status,
            'status_display': req.get_status_display(),
            'priority': req.priority,
            'priority_display': req.get_priority_display(),
            'planned_at': timezone.localtime(req.planned_at).strftime('%Y-%m-%dT%H:%M') if req.planned_at else '',
            'planned_at_display': (
                timezone.localtime(req.planned_at).strftime('%d.%m.%Y') 
                if req.planned_at and timezone.localtime(req.planned_at).hour == 0 and timezone.localtime(req.planned_at).minute == 0
                else timezone.localtime(req.planned_at).strftime('%d.%m.%Y %H:%M') if req.planned_at
                else '-'
            ),
            'request_date': req.request_date.isoformat() if req.request_date else None,
            'request_date_display': req.request_date.strftime('%d.%m.%Y') if req.request_date else '-',
            'card_date': req.card_date.isoformat() if req.card_date else None,
            'card_date_display': req.card_date.strftime('%d.%m.%Y') if req.card_date else '-',
            'status_updated_at': timezone.localtime(req.status_updated_at).strftime('%d.%m.%Y %H:%M') if req.status_updated_at else '-',
            'downlink': req.downlink,
            'uplink': req.uplink,
            'transfer': req.transfer,
            'region': req.region or '',
            'gso_success': req.gso_success,
            'kubsat_success': req.kubsat_success,
            'comment': req.comment or '',
            'created_at': timezone.localtime(req.created_at).strftime('%d.%m.%Y %H:%M') if req.created_at else '-',
            'created_by': str(req.created_by) if req.created_by else '-',
            'history': history,
            # Координаты ГСО
            'coords_lat': coords_lat,
            'coords_lon': coords_lon,
            # Координаты источника
            'coords_source_lat': coords_source_lat,
            'coords_source_lon': coords_source_lon,
            # Координаты объекта
            'coords_object_lat': coords_object_lat,
            'coords_object_lon': coords_object_lon,
            'points_count': req.points_count,
            'objitem_name': source_data['objitem_name'],
            'modulation': source_data['modulation'],
            'symbol_rate': source_data['symbol_rate'],
        }
        
        return JsonResponse(data)


def _get_source_extra_data(source):
    """Получает дополнительные данные из первой точки источника."""
    objitem_name = '-'
    modulation = '-'
    symbol_rate = '-'
    
    if source:
        # Получаем первую точку источника (сортируем по дате ГЛ)
        objitems = source.source_objitems.select_related(
            'parameter_obj__modulation', 'geo_obj'
        ).order_by('geo_obj__timestamp')
        
        first_objitem = objitems.first()
        if first_objitem:
            objitem_name = first_objitem.name or '-'
            if first_objitem.parameter_obj:
                if first_objitem.parameter_obj.modulation:
                    modulation = first_objitem.parameter_obj.modulation.name
                if first_objitem.parameter_obj.bod_velocity and first_objitem.parameter_obj.bod_velocity > 0:
                    symbol_rate = str(int(first_objitem.parameter_obj.bod_velocity))
    
    return {
        'objitem_name': objitem_name,
        'modulation': modulation,
        'symbol_rate': symbol_rate,
    }


class SourceDataAPIView(LoginRequiredMixin, View):
    """API для получения данных источника (координаты, имя точки, модуляция, символьная скорость, транспондер)."""
    
    def get(self, request, source_id):
        from mainapp.utils import calculate_mean_coords
        
        try:
            source = Source.objects.select_related('info', 'ownership').prefetch_related(
                'source_objitems__parameter_obj__modulation',
                'source_objitems__parameter_obj__id_satellite',
                'source_objitems__transponder__sat_id',
                'source_objitems__geo_obj'
            ).get(pk=source_id)
        except Source.DoesNotExist:
            return JsonResponse({'error': 'Источник не найден', 'found': False}, status=404)
        
        # Получаем данные из точек источника
        source_data = _get_source_extra_data(source)
        
        # Рассчитываем усреднённые координаты из всех точек (сортируем по дате ГЛ)
        objitems = source.source_objitems.select_related('geo_obj', 'transponder', 'transponder__sat_id').order_by('geo_obj__timestamp')
        
        avg_coords = None
        points_count = 0
        
        # Данные для заявки
        downlink = None  # Частота из первой точки источника
        uplink = None    # Частота + перенос
        transfer = None  # Перенос из транспондера
        satellite_id = None
        satellite_name = None
        
        for objitem in objitems:
            if hasattr(objitem, 'geo_obj') and objitem.geo_obj and objitem.geo_obj.coords:
                coord = (float(objitem.geo_obj.coords.x), float(objitem.geo_obj.coords.y))
                points_count += 1
                if avg_coords is None:
                    avg_coords = coord
                else:
                    avg_coords, _ = calculate_mean_coords(avg_coords, coord)
            
            # Берём частоту из первой точки источника (parameter_obj.frequency)
            if downlink is None and objitem.parameter_obj and objitem.parameter_obj.frequency:
                downlink = objitem.parameter_obj.frequency
            
            # Берём перенос из первого транспондера
            if transfer is None and objitem.transponder and objitem.transponder.transfer:
                transfer = objitem.transponder.transfer
            
            # Берём спутник из транспондера или параметров
            if satellite_id is None:
                if objitem.transponder and objitem.transponder.sat_id:
                    satellite_id = objitem.transponder.sat_id.pk
                    satellite_name = objitem.transponder.sat_id.name
                elif objitem.parameter_obj and objitem.parameter_obj.id_satellite:
                    satellite_id = objitem.parameter_obj.id_satellite.pk
                    satellite_name = objitem.parameter_obj.id_satellite.name
        
        # Вычисляем uplink = downlink + transfer
        if downlink is not None and transfer is not None:
            uplink = downlink + transfer
        
        # Если нет координат из точек, берём из источника
        coords_lat = None
        coords_lon = None
        if avg_coords:
            coords_lon = avg_coords[0]
            coords_lat = avg_coords[1]
        elif source.coords_average:
            coords_lat = source.coords_average.y
            coords_lon = source.coords_average.x
        
        data = {
            'found': True,
            'source_id': source_id,
            'coords_lat': coords_lat,
            'coords_lon': coords_lon,
            'points_count': points_count,
            'objitem_name': source_data['objitem_name'],
            'modulation': source_data['modulation'],
            'symbol_rate': source_data['symbol_rate'],
            'info': source.info.name if source.info else '-',
            'ownership': source.ownership.name if source.ownership else '-',
            # Данные из транспондера
            'downlink': downlink,
            'uplink': uplink,
            'transfer': transfer,
            'satellite_id': satellite_id,
            'satellite_name': satellite_name,
        }
        
        return JsonResponse(data)


class SourceRequestImportView(LoginRequiredMixin, PermissionRequiredMixin, View):
    """Импорт заявок из Excel файла."""
    permission_required = 'request_import'
    
    def get(self, request):
        """Отображает форму загрузки файла."""
        return render(request, 'mainapp/source_request_import.html')
    
    def post(self, request):
        """Обрабатывает загруженный Excel файл."""
        from openpyxl import load_workbook
        from openpyxl.styles import PatternFill
        
        if 'file' not in request.FILES:
            return JsonResponse({'success': False, 'error': 'Файл не загружен'}, status=400)
        
        file = request.FILES['file']
        
        try:
            # Читаем Excel файл с openpyxl для доступа к цветам
            wb = load_workbook(file, data_only=True)
            ws = wb.worksheets[0]
            
            # Получаем заголовки (очищаем от пробелов)
            headers = []
            for cell in ws[1]:
                val = cell.value
                if val:
                    headers.append(str(val).strip())
                else:
                    headers.append(None)
            
            # Находим индекс столбца "Результат кубсата"
            kubsat_col_idx = None
            for i, h in enumerate(headers):
                if h and 'кубсат' in h.lower():
                    kubsat_col_idx = i
                    break
            
            results = {
                'created': 0,
                'errors': [],
                'skipped': 0,
                'headers': [h for h in headers if h],  # Для отладки
            }
            
            custom_user = getattr(request.user, 'customuser', None)
            
            # Обрабатываем строки начиная со второй (первая - заголовки)
            for row_idx, row in enumerate(ws.iter_rows(min_row=2, values_only=False), start=2):
                try:
                    # Получаем значения
                    row_values = {headers[i]: cell.value for i, cell in enumerate(row) if i < len(headers)}
                    
                    # Получаем цвет ячейки "Результат кубсата"
                    kubsat_is_red = False
                    kubsat_value = None
                    if kubsat_col_idx is not None and kubsat_col_idx < len(row):
                        kubsat_cell = row[kubsat_col_idx]
                        kubsat_value = kubsat_cell.value
                        # Проверяем цвет заливки
                        if kubsat_cell.fill and kubsat_cell.fill.fgColor:
                            color = kubsat_cell.fill.fgColor
                            if color.type == 'rgb' and color.rgb:
                                # Красный цвет: FF0000 или близкие оттенки
                                rgb = color.rgb
                                if isinstance(rgb, str) and len(rgb) >= 6:
                                    # Убираем альфа-канал если есть
                                    rgb = rgb[-6:]
                                    r = int(rgb[0:2], 16)
                                    g = int(rgb[2:4], 16)
                                    b = int(rgb[4:6], 16)
                                    # Считаем красным если R > 200 и G < 100 и B < 100
                                    if r > 180 and g < 120 and b < 120:
                                        kubsat_is_red = True
                    
                    self._process_row(row_values, row_idx, results, custom_user, kubsat_is_red, kubsat_value)
                except Exception as e:
                    results['errors'].append(f"Строка {row_idx}: {str(e)}")
            
            return JsonResponse({
                'success': True,
                'created': results['created'],
                'skipped': results['skipped'],
                'skipped_rows': results.get('skipped_rows', [])[:20],
                'errors': results['errors'][:20],
                'total_errors': len(results['errors']),
                'headers': results.get('headers', [])[:15],  # Для отладки
            })
            
        except Exception as e:
            return JsonResponse({'success': False, 'error': f'Ошибка чтения файла: {str(e)}'}, status=400)
    
    def _process_row(self, row, row_idx, results, custom_user, kubsat_is_red=False, kubsat_value=None):
        """Обрабатывает одну строку из Excel."""
        # Пропускаем полностью пустые строки (все значения None или пустые)
        has_any_data = any(v for v in row.values() if v is not None and str(v).strip())
        if not has_any_data:
            results['skipped'] += 1
            return
        
        # Парсим дату заявки (Дата постановки задачи)
        request_date = self._parse_date(row.get('Дата постановки задачи'))
        
        # Парсим дату формирования карточки
        card_date = self._parse_date(row.get('Дата формирования карточки'))
        
        # Парсим дату и время планирования (Дата проведения)
        planned_at = self._parse_datetime(row.get('Дата проведения'))
        
        # Ищем спутник по NORAD
        satellite = self._find_satellite(row.get('Спутник'))
        
        # Парсим частоты
        downlink = self._parse_float(row.get('Частота Downlink'))
        uplink = self._parse_float(row.get('Частота Uplink'))
        transfer = self._parse_float(row.get('Перенос'))
        
        # Парсим координаты ГСО
        coords = self._parse_coords(row.get('Координаты ГСО'))
        
        # Район
        region = str(row.get('Район', '')).strip() if row.get('Район') else None
        
        # Результат ГСО
        gso_result = row.get('Результат ГСО')
        gso_success = None
        comment_parts = []
        
        if gso_result:
            gso_str = str(gso_result).strip().lower()
            if gso_str in ('успешно', 'да', 'true', '1'):
                gso_success = True
            else:
                gso_success = False
                comment_parts.append(f"Результат ГСО: {str(gso_result).strip()}")
        
        # Результат кубсата - по цвету ячейки
        kubsat_success = None
        if kubsat_is_red:
            kubsat_success = False
        elif kubsat_value:
            kubsat_success = True
        
        # Добавляем значение кубсата в комментарий
        if kubsat_value:
            comment_parts.append(f"Результат кубсата: {str(kubsat_value).strip()}")
        
        # Координаты источника
        coords_source = self._parse_coords(row.get('Координаты источника'))
        
        # Координаты объекта
        coords_object = self._parse_coords(row.get('Координаты объекта'))
        
        # Проверяем дубликат по совокупности полей: спутник, downlink, uplink, перенос, координаты ГСО, дата проведения
        if self._is_duplicate(satellite, downlink, uplink, transfer, coords, planned_at):
            results['skipped'] += 1
            # Добавляем информацию о пропущенной строке
            sat_name = satellite.name if satellite else '-'
            planned_str = planned_at.strftime('%d.%m.%y %H:%M') if planned_at else '-'
            if 'skipped_rows' not in results:
                results['skipped_rows'] = []
            results['skipped_rows'].append(f"Строка {row_idx}: дубликат (спутник: {sat_name}, дата: {planned_str})")
            return
        
        # Определяем статус по логике:
        # - если есть координата источника -> result_received
        # - если нет координаты источника, но ГСО успешно -> successful
        # - если нет координаты источника и ГСО не успешно -> unsuccessful
        status = 'planned'
        if coords_source:
            status = 'result_received'
        elif gso_success is True:
            status = 'successful'
        elif gso_success is False:
            status = 'unsuccessful'
        
        # Собираем комментарий
        comment = '; '.join(comment_parts) if comment_parts else None
        
        # Создаём заявку
        source_request = SourceRequest(
            source=None,
            satellite=satellite,
            status=status,
            priority='medium',
            request_date=request_date,
            card_date=card_date,
            planned_at=planned_at,
            downlink=downlink,
            uplink=uplink,
            transfer=transfer,
            region=region,
            gso_success=gso_success,
            kubsat_success=kubsat_success,
            comment=comment,
            created_by=custom_user,
            updated_by=custom_user,
        )
        
        # Устанавливаем координаты
        if coords:
            source_request.coords = Point(coords[1], coords[0], srid=4326)
        
        if coords_source:
            source_request.coords_source = Point(coords_source[1], coords_source[0], srid=4326)
        
        if coords_object:
            source_request.coords_object = Point(coords_object[1], coords_object[0], srid=4326)
        
        source_request.save()
        
        # Создаём начальную запись в истории
        SourceRequestStatusHistory.objects.create(
            source_request=source_request,
            old_status='',
            new_status=source_request.status,
            changed_by=custom_user,
        )
        
        results['created'] += 1
    
    def _parse_date(self, value):
        """Парсит дату из различных форматов."""
        if pd.isna(value):
            return None
        
        if isinstance(value, datetime):
            return value.date()
        
        value_str = str(value).strip()
        
        # Пробуем разные форматы
        formats = ['%d.%m.%Y', '%d.%m.%y', '%Y-%m-%d', '%d/%m/%Y', '%d/%m/%y']
        for fmt in formats:
            try:
                return datetime.strptime(value_str, fmt).date()
            except ValueError:
                continue
        
        return None
    
    def _parse_datetime(self, value):
        """Парсит дату и время из различных форматов."""
        if pd.isna(value):
            return None
        
        if isinstance(value, datetime):
            return value
        
        value_str = str(value).strip()
        
        # Пробуем разные форматы
        formats = [
            '%d.%m.%y %H:%M', '%d.%m.%Y %H:%M', '%d.%m.%y %H:%M:%S', '%d.%m.%Y %H:%M:%S',
            '%Y-%m-%d %H:%M', '%Y-%m-%d %H:%M:%S', '%d/%m/%Y %H:%M', '%d/%m/%y %H:%M'
        ]
        for fmt in formats:
            try:
                return datetime.strptime(value_str, fmt)
            except ValueError:
                continue
        
        return None
    
    def _find_satellite(self, value):
        """Ищет спутник по названию с NORAD в скобках."""
        if pd.isna(value):
            return None
        
        value_str = str(value).strip()
        
        # Ищем NORAD в скобках: "NSS 12 (36032)"
        match = re.search(r'\((\d+)\)', value_str)
        if match:
            norad = int(match.group(1))
            try:
                return Satellite.objects.get(norad=norad)
            except Satellite.DoesNotExist:
                pass
        
        # Пробуем найти по имени
        name = re.sub(r'\s*\(\d+\)\s*', '', value_str).strip()
        if name:
            satellite = Satellite.objects.filter(name__icontains=name).first()
            if satellite:
                return satellite
        
        return None
    
    def _parse_float(self, value):
        """Парсит число с плавающей точкой."""
        if pd.isna(value):
            return None
        
        try:
            # Заменяем запятую на точку
            value_str = str(value).replace(',', '.').strip()
            return float(value_str)
        except (ValueError, TypeError):
            return None
    
    def _is_duplicate(self, satellite, downlink, uplink, transfer, coords, planned_at):
        """Проверяет, существует ли уже заявка с такими же параметрами.
        
        Проверка по совокупности полей: спутник, downlink, uplink, перенос, координаты ГСО, дата проведения.
        """
        from django.contrib.gis.measure import D
        
        # Базовый фильтр
        qs = SourceRequest.objects.filter(
            satellite=satellite,
        )
        
        # Фильтр по downlink (с допуском)
        if downlink is not None:
            qs = qs.filter(downlink__gte=downlink - 0.01, downlink__lte=downlink + 0.01)
        else:
            qs = qs.filter(downlink__isnull=True)
        
        # Фильтр по uplink (с допуском)
        if uplink is not None:
            qs = qs.filter(uplink__gte=uplink - 0.01, uplink__lte=uplink + 0.01)
        else:
            qs = qs.filter(uplink__isnull=True)
        
        # Фильтр по transfer (с допуском)
        if transfer is not None:
            qs = qs.filter(transfer__gte=transfer - 0.01, transfer__lte=transfer + 0.01)
        else:
            qs = qs.filter(transfer__isnull=True)
        
        # Фильтр по координатам ГСО
        if coords is not None:
            # Проверяем координаты с допуском ~100 метров
            coords_point = Point(coords[1], coords[0], srid=4326)
            qs = qs.filter(coords__distance_lte=(coords_point, D(m=100)))
        else:
            qs = qs.filter(coords__isnull=True)
        
        # Фильтр по дате проведения
        if planned_at is not None:
            qs = qs.filter(planned_at=planned_at)
        else:
            qs = qs.filter(planned_at__isnull=True)
        
        return qs.exists()
    
    def _parse_coords(self, value):
        """Парсит координаты из строки. Возвращает (lat, lon) или None.
        
        Поддерживаемые форматы:
        - "26.223, 33.969" (числа через запятую с пробелом)
        - "24.920695 46.733201" (точка как десятичный разделитель, пробел между координатами)
        - "24,920695 46,733201" (запятая как десятичный разделитель, пробел между координатами)
        - "24.920695, 46.733201" (точка как десятичный разделитель, запятая+пробел между координатами)
        - "21.763585. 39.158290" (точка с пробелом между координатами)
        
        Если значение содержит текст (не числа) - возвращает None.
        """
        if pd.isna(value):
            return None
        
        value_str = str(value).strip()
        if not value_str:
            return None
        
        # Пробуем извлечь два числа из строки с помощью регулярного выражения
        # Ищем числа в формате: целое или дробное (с точкой или запятой как десятичным разделителем)
        # Паттерн: -?[0-9]+[.,]?[0-9]*
        numbers = re.findall(r'-?\d+[.,]?\d*', value_str)
        
        if len(numbers) >= 2:
            try:
                lat = float(numbers[0].replace(',', '.'))
                lon = float(numbers[1].replace(',', '.'))
                # Проверяем, что координаты в разумных пределах
                if -90 <= lat <= 90 and -180 <= lon <= 180:
                    return (lat, lon)
                # Может быть перепутаны местами
                if -90 <= lon <= 90 and -180 <= lat <= 180:
                    return (lon, lat)
            except (ValueError, TypeError):
                pass
        
        # Формат "21.763585. 39.158290" - точка с пробелом как разделитель координат
        if re.search(r'\.\s+', value_str):
            parts = re.split(r'\.\s+', value_str)
            if len(parts) >= 2:
                try:
                    lat = float(parts[0].replace(',', '.'))
                    lon = float(parts[1].replace(',', '.'))
                    return (lat, lon)
                except (ValueError, TypeError):
                    pass
        
        # Формат "24.920695, 46.733201" - запятая с пробелом как разделитель координат
        if ', ' in value_str:
            parts = value_str.split(', ')
            if len(parts) >= 2:
                try:
                    lat = float(parts[0].replace(',', '.'))
                    lon = float(parts[1].replace(',', '.'))
                    return (lat, lon)
                except (ValueError, TypeError):
                    pass
        
        # Формат "24,920695 46,733201" или "24.920695 46.733201" - пробел как разделитель координат
        # Сначала разбиваем по пробелам
        parts = value_str.split()
        if len(parts) >= 2:
            try:
                # Заменяем запятую на точку в каждой части отдельно
                lat = float(parts[0].replace(',', '.'))
                lon = float(parts[1].replace(',', '.'))
                return (lat, lon)
            except (ValueError, TypeError):
                pass
        
        # Формат "24.920695;46.733201" - точка с запятой как разделитель
        if ';' in value_str:
            parts = value_str.split(';')
            if len(parts) >= 2:
                try:
                    lat = float(parts[0].strip().replace(',', '.'))
                    lon = float(parts[1].strip().replace(',', '.'))
                    return (lat, lon)
                except (ValueError, TypeError):
                    pass
        
        # Если ничего не подошло - возвращаем None (текст или некорректный формат)
        return None
