"""
ObjItem CRUD operations and related views.
"""
from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin
from django.core.paginator import Paginator
from django.db import models
from django.db.models import F
from django.http import JsonResponse
from django.shortcuts import redirect, render
from django.urls import reverse_lazy
from django.views import View
from django.views.generic import CreateView, DeleteView, UpdateView

from ..forms import GeoForm, ObjItemForm, ParameterForm
from ..mixins import CoordinateProcessingMixin, FormMessageMixin, RoleRequiredMixin
from ..models import Geo, Modulation, ObjItem, Polarization, Satellite
from ..utils import parse_pagination_params


class DeleteSelectedObjectsView(RoleRequiredMixin, View):
    """View for deleting multiple selected objects."""
    
    required_roles = ["admin", "moderator"]

    def post(self, request):
        ids = request.POST.get("ids", "")
        if not ids:
            return JsonResponse({"error": "Нет ID для удаления"}, status=400)

        try:
            id_list = [int(x) for x in ids.split(",") if x.isdigit()]
            deleted_count, _ = ObjItem.objects.filter(id__in=id_list).delete()

            return JsonResponse(
                {
                    "success": True,
                    "message": "Объект успешно удалён",
                    "deleted_count": deleted_count,
                }
            )
        except Exception as e:
            return JsonResponse({"error": f"Ошибка при удалении: {str(e)}"}, status=500)


class ObjItemListView(LoginRequiredMixin, View):
    """View for displaying a list of ObjItems with filtering and pagination."""
    
    def get(self, request):
        satellites = (
            Satellite.objects.filter(parameters__objitem__isnull=False)
            .distinct()
            .only("id", "name")
            .order_by("name")
        )

        selected_sat_id = request.GET.get("satellite_id")
        page_number, items_per_page = parse_pagination_params(request)
        sort_param = request.GET.get("sort", "")

        freq_min = request.GET.get("freq_min")
        freq_max = request.GET.get("freq_max")
        range_min = request.GET.get("range_min")
        range_max = request.GET.get("range_max")
        snr_min = request.GET.get("snr_min")
        snr_max = request.GET.get("snr_max")
        bod_min = request.GET.get("bod_min")
        bod_max = request.GET.get("bod_max")
        search_query = request.GET.get("search")
        selected_modulations = request.GET.getlist("modulation")
        selected_polarizations = request.GET.getlist("polarization")
        selected_satellites = request.GET.getlist("satellite_id")
        has_kupsat = request.GET.get("has_kupsat")
        has_valid = request.GET.get("has_valid")
        date_from = request.GET.get("date_from")
        date_to = request.GET.get("date_to")

        objects = ObjItem.objects.none()

        if selected_satellites or selected_sat_id:
            if selected_sat_id and not selected_satellites:
                try:
                    selected_sat_id_single = int(selected_sat_id)
                    selected_satellites = [selected_sat_id_single]
                except ValueError:
                    selected_satellites = []

            if selected_satellites:
                objects = (
                    ObjItem.objects.select_related(
                        "geo_obj",
                        "source",
                        "updated_by__user",
                        "created_by__user",
                        "lyngsat_source",
                        "parameter_obj",
                        "parameter_obj__id_satellite",
                        "parameter_obj__polarization",
                        "parameter_obj__modulation",
                        "parameter_obj__standard",
                    )
                    .prefetch_related(
                        "parameter_obj__sigma_parameter",
                        "parameter_obj__sigma_parameter__polarization",
                    )
                    .filter(parameter_obj__id_satellite_id__in=selected_satellites)
                )
            else:
                objects = ObjItem.objects.select_related(
                    "geo_obj",
                    "source",
                    "updated_by__user",
                    "created_by__user",
                    "lyngsat_source",
                    "parameter_obj",
                    "parameter_obj__id_satellite",
                    "parameter_obj__polarization",
                    "parameter_obj__modulation",
                    "parameter_obj__standard",
                ).prefetch_related(
                    "parameter_obj__sigma_parameter",
                    "parameter_obj__sigma_parameter__polarization",
                )

            if freq_min is not None and freq_min.strip() != "":
                try:
                    freq_min_val = float(freq_min)
                    objects = objects.filter(
                        parameter_obj__frequency__gte=freq_min_val
                    )
                except ValueError:
                    pass
            if freq_max is not None and freq_max.strip() != "":
                try:
                    freq_max_val = float(freq_max)
                    objects = objects.filter(
                        parameter_obj__frequency__lte=freq_max_val
                    )
                except ValueError:
                    pass

            if range_min is not None and range_min.strip() != "":
                try:
                    range_min_val = float(range_min)
                    objects = objects.filter(
                        parameter_obj__freq_range__gte=range_min_val
                    )
                except ValueError:
                    pass
            if range_max is not None and range_max.strip() != "":
                try:
                    range_max_val = float(range_max)
                    objects = objects.filter(
                        parameter_obj__freq_range__lte=range_max_val
                    )
                except ValueError:
                    pass

            if snr_min is not None and snr_min.strip() != "":
                try:
                    snr_min_val = float(snr_min)
                    objects = objects.filter(parameter_obj__snr__gte=snr_min_val)
                except ValueError:
                    pass
            if snr_max is not None and snr_max.strip() != "":
                try:
                    snr_max_val = float(snr_max)
                    objects = objects.filter(parameter_obj__snr__lte=snr_max_val)
                except ValueError:
                    pass

            if bod_min is not None and bod_min.strip() != "":
                try:
                    bod_min_val = float(bod_min)
                    objects = objects.filter(
                        parameter_obj__bod_velocity__gte=bod_min_val
                    )
                except ValueError:
                    pass
            if bod_max is not None and bod_max.strip() != "":
                try:
                    bod_max_val = float(bod_max)
                    objects = objects.filter(
                        parameter_obj__bod_velocity__lte=bod_max_val
                    )
                except ValueError:
                    pass

            if selected_modulations:
                objects = objects.filter(
                    parameter_obj__modulation__id__in=selected_modulations
                )

            if selected_polarizations:
                objects = objects.filter(
                    parameter_obj__polarization__id__in=selected_polarizations
                )

            if has_kupsat == "1":
                objects = objects.filter(source__coords_kupsat__isnull=False)
            elif has_kupsat == "0":
                objects = objects.filter(source__coords_kupsat__isnull=True)

            if has_valid == "1":
                objects = objects.filter(source__coords_valid__isnull=False)
            elif has_valid == "0":
                objects = objects.filter(source__coords_valid__isnull=True)

            # Date filter for geo_obj timestamp
            if date_from and date_from.strip():
                try:
                    from datetime import datetime
                    date_from_obj = datetime.strptime(date_from, "%Y-%m-%d")
                    objects = objects.filter(geo_obj__timestamp__gte=date_from_obj)
                except (ValueError, TypeError):
                    pass
            
            if date_to and date_to.strip():
                try:
                    from datetime import datetime, timedelta
                    date_to_obj = datetime.strptime(date_to, "%Y-%m-%d")
                    # Add one day to include the entire end date
                    date_to_obj = date_to_obj + timedelta(days=1)
                    objects = objects.filter(geo_obj__timestamp__lt=date_to_obj)
                except (ValueError, TypeError):
                    pass

            # Filter by source type (lyngsat_source)
            has_source_type = request.GET.get("has_source_type")
            if has_source_type == "1":
                objects = objects.filter(lyngsat_source__isnull=False)
            elif has_source_type == "0":
                objects = objects.filter(lyngsat_source__isnull=True)

            # Filter by sigma (sigma parameters)
            has_sigma = request.GET.get("has_sigma")
            if has_sigma == "1":
                objects = objects.filter(parameter_obj__sigma_parameter__isnull=False)
            elif has_sigma == "0":
                objects = objects.filter(parameter_obj__sigma_parameter__isnull=True)

            if search_query:
                search_query = search_query.strip()
                if search_query:
                    objects = objects.filter(
                        models.Q(name__icontains=search_query)
                        | models.Q(geo_obj__location__icontains=search_query)
                    )
        else:
            selected_sat_id = None

        objects = objects.annotate(
            first_param_freq=F("parameter_obj__frequency"),
            first_param_range=F("parameter_obj__freq_range"),
            first_param_snr=F("parameter_obj__snr"),
            first_param_bod=F("parameter_obj__bod_velocity"),
            first_param_sat_name=F("parameter_obj__id_satellite__name"),
            first_param_pol_name=F("parameter_obj__polarization__name"),
            first_param_mod_name=F("parameter_obj__modulation__name"),
        )

        valid_sort_fields = {
            "name": "name",
            "-name": "-name",
            "updated_at": "updated_at",
            "-updated_at": "-updated_at",
            "created_at": "created_at",
            "-created_at": "-created_at",
            "updated_by": "updated_by__user__username",
            "-updated_by": "-updated_by__user__username",
            "created_by": "created_by__user__username",
            "-created_by": "-created_by__user__username",
            "geo_timestamp": "geo_obj__timestamp",
            "-geo_timestamp": "-geo_obj__timestamp",
            "frequency": "first_param_freq",
            "-frequency": "-first_param_freq",
            "freq_range": "first_param_range",
            "-freq_range": "-first_param_range",
            "snr": "first_param_snr",
            "-snr": "-first_param_snr",
            "bod_velocity": "first_param_bod",
            "-bod_velocity": "-first_param_bod",
            "satellite": "first_param_sat_name",
            "-satellite": "-first_param_sat_name",
            "polarization": "first_param_pol_name",
            "-polarization": "-first_param_pol_name",
            "modulation": "first_param_mod_name",
            "-modulation": "-first_param_mod_name",
        }

        if sort_param in valid_sort_fields:
            objects = objects.order_by(valid_sort_fields[sort_param])

        paginator = Paginator(objects, items_per_page)
        page_obj = paginator.get_page(page_number)

        processed_objects = []
        for obj in page_obj:
            param = getattr(obj, 'parameter_obj', None)

            geo_coords = "-"
            geo_timestamp = "-"
            geo_location = "-"
            kupsat_coords = "-"
            valid_coords = "-"
            distance_geo_kup = "-"
            distance_geo_valid = "-"
            distance_kup_valid = "-"
            mirrors_list = []

            if hasattr(obj, "geo_obj") and obj.geo_obj:
                geo_timestamp = obj.geo_obj.timestamp
                geo_location = obj.geo_obj.location
                
                # Get mirrors
                mirrors_list = list(obj.geo_obj.mirrors.values_list('name', flat=True))

                if obj.geo_obj.coords:
                    longitude = obj.geo_obj.coords.coords[0]
                    latitude = obj.geo_obj.coords.coords[1]
                    lon = f"{longitude}E" if longitude > 0 else f"{abs(longitude)}W"
                    lat = f"{latitude}N" if latitude > 0 else f"{abs(latitude)}S"
                    geo_coords = f"{lat} {lon}"

            satellite_name = "-"
            frequency = "-"
            freq_range = "-"
            polarization_name = "-"
            bod_velocity = "-"
            modulation_name = "-"
            snr = "-"
            standard_name = "-"
            comment = "-"
            is_average = "-"

            if param:
                if hasattr(param, "id_satellite") and param.id_satellite:
                    satellite_name = (
                        param.id_satellite.name
                        if hasattr(param.id_satellite, "name")
                        else "-"
                    )

                frequency = (
                    f"{param.frequency:.3f}" if param.frequency is not None else "-"
                )
                freq_range = (
                    f"{param.freq_range:.3f}" if param.freq_range is not None else "-"
                )
                bod_velocity = (
                    f"{param.bod_velocity:.0f}"
                    if param.bod_velocity is not None
                    else "-"
                )
                snr = f"{param.snr:.0f}" if param.snr is not None else "-"

                if hasattr(param, "polarization") and param.polarization:
                    polarization_name = (
                        param.polarization.name
                        if hasattr(param.polarization, "name")
                        else "-"
                    )

                if hasattr(param, "modulation") and param.modulation:
                    modulation_name = (
                        param.modulation.name
                        if hasattr(param.modulation, "name")
                        else "-"
                    )

                if hasattr(param, "standard") and param.standard:
                    standard_name = (
                        param.standard.name
                        if hasattr(param.standard, "name")
                        else "-"
                    )

            if hasattr(obj, "geo_obj") and obj.geo_obj:
                comment = obj.geo_obj.comment or "-"
                is_average = "Да" if obj.geo_obj.is_average else "Нет" if obj.geo_obj.is_average is not None else "-"

            source_type = "ТВ" if obj.lyngsat_source else "-"

            has_sigma = False
            sigma_info = "-"
            if param:
                sigma_count = param.sigma_parameter.count()
                if sigma_count > 0:
                    has_sigma = True
                    first_sigma = param.sigma_parameter.first()
                    if first_sigma:
                        sigma_freq = f"{first_sigma.transfer_frequency:.3f}" if first_sigma.transfer_frequency else "-"
                        sigma_range = f"{first_sigma.freq_range:.3f}" if first_sigma.freq_range else "-"
                        sigma_pol = first_sigma.polarization.name if first_sigma.polarization else "-"
                        sigma_pol_short = sigma_pol[0] if sigma_pol and sigma_pol != "-" else "-"
                        sigma_info = f"{sigma_freq}/{sigma_range}/{sigma_pol_short}"

            processed_objects.append(
                {
                    "id": obj.id,
                    "name": obj.name or "-",
                    "satellite_name": satellite_name,
                    "frequency": frequency,
                    "freq_range": freq_range,
                    "polarization": polarization_name,
                    "bod_velocity": bod_velocity,
                    "modulation": modulation_name,
                    "snr": snr,
                    "geo_timestamp": geo_timestamp,
                    "geo_location": geo_location,
                    "geo_coords": geo_coords,
                    "kupsat_coords": kupsat_coords,
                    "valid_coords": valid_coords,
                    "distance_geo_kup": distance_geo_kup,
                    "distance_geo_valid": distance_geo_valid,
                    "distance_kup_valid": distance_kup_valid,
                    "updated_by": obj.updated_by if obj.updated_by else "-",
                    "comment": comment,
                    "is_average": is_average,
                    "source_type": source_type,
                    "standard": standard_name,
                    "has_sigma": has_sigma,
                    "sigma_info": sigma_info,
                    "mirrors": ", ".join(mirrors_list) if mirrors_list else "-",
                    "obj": obj,
                }
            )

        modulations = Modulation.objects.all()
        polarizations = Polarization.objects.all()

        # Get the new filter values
        has_source_type = request.GET.get("has_source_type")
        has_sigma = request.GET.get("has_sigma")

        context = {
            "satellites": satellites,
            "selected_satellite_id": selected_sat_id,
            "page_obj": page_obj,
            "processed_objects": processed_objects,
            "items_per_page": items_per_page,
            "available_items_per_page": [50, 100, 500, 1000],
            "freq_min": freq_min,
            "freq_max": freq_max,
            "range_min": range_min,
            "range_max": range_max,
            "snr_min": snr_min,
            "snr_max": snr_max,
            "bod_min": bod_min,
            "bod_max": bod_max,
            "search_query": search_query,
            "selected_modulations": [
                int(x) for x in selected_modulations if x.isdigit()
            ],
            "selected_polarizations": [
                int(x) for x in selected_polarizations if x.isdigit()
            ],
            "selected_satellites": [int(x) for x in selected_satellites if x.isdigit()],
            "has_kupsat": has_kupsat,
            "has_valid": has_valid,
            "date_from": date_from,
            "date_to": date_to,
            "has_source_type": has_source_type,
            "has_sigma": has_sigma,
            "modulations": modulations,
            "polarizations": polarizations,
            "full_width_page": True,
            "sort": sort_param,
        }

        return render(request, "mainapp/objitem_list.html", context)


class ObjItemFormView(
    RoleRequiredMixin, CoordinateProcessingMixin, FormMessageMixin, UpdateView
):
    """
    Base class for creating and editing ObjItem.
    
    Contains common logic for form processing, coordinates, and parameters.
    """

    model = ObjItem
    form_class = ObjItemForm
    template_name = "mainapp/objitem_form.html"
    success_url = reverse_lazy("mainapp:home")
    required_roles = ["admin", "moderator"]

    def get_success_url(self):
        """Returns URL with saved filter parameters."""
        if self.request.GET:
            from urllib.parse import urlencode
            query_string = urlencode(self.request.GET)
            return reverse_lazy("mainapp:objitem_list") + '?' + query_string
        return reverse_lazy("mainapp:objitem_list")

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context["LEAFLET_CONFIG"] = {
            "DEFAULT_CENTER": (55.75, 37.62),
            "DEFAULT_ZOOM": 5,
        }

        # Save return parameters for "Back" button
        context["return_params"] = self.request.GET.get('return_params', '')

        # Work with single parameter form instead of formset
        if self.object and hasattr(self.object, "parameter_obj") and self.object.parameter_obj:
            context["parameter_form"] = ParameterForm(
                instance=self.object.parameter_obj, prefix="parameter"
            )
        else:
            context["parameter_form"] = ParameterForm(prefix="parameter")

        if self.object and hasattr(self.object, "geo_obj") and self.object.geo_obj:
            context["geo_form"] = GeoForm(
                instance=self.object.geo_obj, prefix="geo"
            )
        else:
            context["geo_form"] = GeoForm(prefix="geo")

        return context
    
    def get_object(self, queryset=None):
        """Override to add select_related for transponder."""
        obj = super().get_object(queryset)
        if obj and hasattr(obj, 'transponder'):
            # Prefetch transponder data
            obj = ObjItem.objects.select_related(
                'transponder',
                'transponder__sat_id',
                'transponder__polarization',
                'transponder__created_by__user',
            ).get(pk=obj.pk)
        return obj

    def form_valid(self, form):
        # Get parameter form
        if self.object and hasattr(self.object, "parameter_obj") and self.object.parameter_obj:
            parameter_form = ParameterForm(
                self.request.POST, 
                instance=self.object.parameter_obj, 
                prefix="parameter"
            )
        else:
            parameter_form = ParameterForm(self.request.POST, prefix="parameter")
        
        if self.object and hasattr(self.object, "geo_obj") and self.object.geo_obj:
            geo_form = GeoForm(self.request.POST, instance=self.object.geo_obj, prefix="geo")
        else:
            geo_form = GeoForm(self.request.POST, prefix="geo")

        # Save main object
        self.object = form.save(commit=False)
        self.set_user_fields()
        self.object.save()

        # Save related parameter
        if parameter_form.is_valid():
            self.save_parameter(parameter_form)
        else:
            context = self.get_context_data()
            context.update({
                'form': form,
                'parameter_form': parameter_form,
                'geo_form': geo_form,
            })
            return self.render_to_response(context)

        # Save geo data
        if geo_form.is_valid():
            self.save_geo_data(geo_form)
        else:
            context = self.get_context_data()
            context.update({
                'form': form,
                'parameter_form': parameter_form,
                'geo_form': geo_form,
            })
            return self.render_to_response(context)

        return super().form_valid(form)

    def set_user_fields(self):
        """Sets user fields for the object."""
        raise NotImplementedError("Subclasses must implement set_user_fields()")

    def save_parameter(self, parameter_form):
        """Saves object parameter through OneToOne relationship."""
        if parameter_form.is_valid():
            instance = parameter_form.save(commit=False)
            instance.objitem = self.object
            instance.save()

    def save_geo_data(self, geo_form):
        """Saves object geo data."""
        geo_instance = self.get_or_create_geo_instance()

        # Update fields from geo_form
        if geo_form.is_valid():
            geo_instance.location = geo_form.cleaned_data["location"]
            geo_instance.comment = geo_form.cleaned_data["comment"]
            geo_instance.is_average = geo_form.cleaned_data["is_average"]

        # Process date/time
        self.process_timestamp(geo_instance)

        geo_instance.save()
        
        # Save ManyToMany relationship for mirrors
        if geo_form.is_valid():
            geo_instance.mirrors.set(geo_form.cleaned_data["mirrors"])

    def get_or_create_geo_instance(self):
        """Gets or creates Geo instance."""
        if hasattr(self.object, "geo_obj") and self.object.geo_obj:
            return self.object.geo_obj
        return Geo(objitem=self.object)


class ObjItemUpdateView(ObjItemFormView):
    """View for editing ObjItem."""

    success_message = "Объект успешно сохранён!"

    def set_user_fields(self):
        self.object.updated_by = self.request.user.customuser


class ObjItemCreateView(ObjItemFormView, CreateView):
    """View for creating ObjItem."""

    success_message = "Объект успешно создан!"

    def set_user_fields(self):
        self.object.created_by = self.request.user.customuser
        self.object.updated_by = self.request.user.customuser


class ObjItemDeleteView(RoleRequiredMixin, FormMessageMixin, DeleteView):
    """View for deleting ObjItem."""
    
    model = ObjItem
    template_name = "mainapp/objitem_confirm_delete.html"
    success_url = reverse_lazy("mainapp:objitem_list")
    success_message = "Объект успешно удалён!"
    required_roles = ["admin", "moderator"]

    def get_success_url(self):
        """Returns URL with saved filter parameters."""
        if self.request.GET:
            from urllib.parse import urlencode
            query_string = urlencode(self.request.GET)
            return reverse_lazy("mainapp:objitem_list") + '?' + query_string
        return reverse_lazy("mainapp:objitem_list")


class ObjItemDetailView(LoginRequiredMixin, View):
    """
    View for displaying ObjItem details in read-only mode.
    
    Available to all authenticated users, displays data in read-only mode.
    """
    def get(self, request, pk):
        obj = ObjItem.objects.filter(pk=pk).select_related(
            'geo_obj',
            'updated_by__user',
            'created_by__user',
            'parameter_obj',
            'parameter_obj__id_satellite',
            'parameter_obj__polarization',
            'parameter_obj__modulation',
            'parameter_obj__standard',
            'transponder',
            'transponder__sat_id',
            'transponder__polarization',
            'transponder__created_by__user',
        ).first()
        
        if not obj:
            from django.http import Http404
            raise Http404("Объект не найден")
        
        # Save return parameters for "Back" button
        return_params = request.GET.get('return_params', '')
        
        context = {
            'object': obj,
            'return_params': return_params
        }
        
        return render(request, "mainapp/objitem_detail.html", context)
