from django.http import HttpRequest, HttpResponse, JsonResponse
from django.views.generic import ListView
from django.shortcuts import render, get_object_or_404
from django.db.models import Count
from .models import Event, Occurrence
from .services.event_service import EventService
import json


def _is_htmx(request: HttpRequest) -> bool:
    """Check if request is an HTMX request."""
    return request.headers.get("HX-Request", "").lower() == "true"


class EventListView(ListView):
    """Show top-level events (events without a parent). Can filter by event_type via query param."""
    model = Event
    template_name = 'eventhub/event_list.html'
    context_object_name = 'events'

    def get_queryset(self):
        """Return top-level events (no parent). Can optionally filter by event_type via query param."""
        queryset = Event.objects.filter(parent__isnull=True).annotate(children_count=Count('children')).order_by('start_date', 'name')
        
        # Optional filter by event_type if provided
        event_type = self.request.GET.get('event_type')
        if event_type:
            queryset = queryset.filter(event_type=event_type)
        
        return queryset

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        events = context['events']
        
        # Prepare nodes for tree component
        nodes = []
        for event in events:
            nodes.append({
                'id': event.id,
                'label': event.name,
                'children_count': event.children_count,
                'event_type': event.event_type,
                'has_geometry': bool(event.footprintWKT),
            })
        
        context['nodes'] = nodes
        return context


def event_children(request: HttpRequest, pk: int) -> HttpResponse:
    """HTMX endpoint to fetch children of an event for the sidebar component."""
    event = get_object_or_404(Event, pk=pk)
    # Annotate with children_count to avoid N+1 queries
    children = event.children.annotate(children_count=Count('children')).order_by('start_date', 'name')
    
    # Prepare nodes for sidebar component
    nodes = []
    for child in children:
        nodes.append({
            'id': child.id,
            'label': child.name,
            'children_count': child.children_count,
            'event_type': child.event_type,
            'has_geometry': bool(child.footprintWKT),
        })
    
    context = {'nodes': nodes}
    # Render items directly using the sidebar item template
    # This replaces the need for _event_sidebar_children.html wrapper
    return render(request, 'eventhub/_event_sidebar_children.html', context)


def _get_project_id(event: Event) -> int:
    """Get the project ID for an event by traversing up the hierarchy."""
    current = event
    while current:
        if current.event_type == 'project':
            return current.id
        current = current.parent
    return None


def _get_all_descendant_ids(event: Event) -> set:
    """Get all descendant event IDs for an event using iterative approach to avoid N+1 queries and recursion limits."""
    descendant_ids = set()
    # Queue of event IDs to process (ordered for consistency)
    queue = list(event.children.order_by('start_date', 'name').values_list('id', flat=True))
    descendant_ids.update(queue)
    
    # Process queue iteratively
    while queue:
        current_ids = queue
        queue = []
        # Fetch all children of current level in one query
        children_ids = Event.objects.filter(parent_id__in=current_ids).values_list('id', flat=True)
        new_ids = list(children_ids)
        descendant_ids.update(new_ids)
        queue.extend(new_ids)
    
    return descendant_ids


def event_geometry(request: HttpRequest, pk: int) -> JsonResponse:
    """JSON endpoint to get event geometry data."""
    event = get_object_or_404(Event, pk=pk)
    project_id = _get_project_id(event)
    
    # If event has no geometry, return empty geometries list
    # (This typically applies to root events like projects, but could be any event)
    if not event.footprintWKT and not event.parent:
        return JsonResponse({
            'event_id': event.id,
            'event_name': event.name,
            'event_type': event.event_type,
            'project_id': project_id,
            'geometries': []
        })
    
    # Collect geometry from this event and all ancestors
    # This allows cumulative rendering: selecting a child shows parent geometries too
    geometries = []
    
    # Get all ancestors including self, in order from root to leaf
    ancestors = []
    current = event
    while current:
        ancestors.insert(0, current)  # Insert at beginning to maintain root-to-leaf order
        current = current.parent
    
    # Extract geometries from ancestors (only include events that have geometry)
    # Note: Geometries are assumed to be in WGS84 (EPSG:4326)
    for ancestor in ancestors:
        if ancestor.footprintWKT:
            geometries.append({
                'event_id': ancestor.id,
                'event_name': ancestor.name,
                'event_type': ancestor.event_type,
                'wkt': ancestor.footprintWKT,
            })
    
    return JsonResponse({
        'event_id': event.id,
        'event_name': event.name,
        'event_type': event.event_type,
        'project_id': project_id,
        'geometries': geometries
    })


def event_children_geometries(request: HttpRequest, pk: int) -> JsonResponse:
    """JSON endpoint to get immediate children geometries for an event."""
    event = get_object_or_404(Event, pk=pk)
    
    children = event.children.all().order_by('start_date', 'name')
    children_data = []
    
    for child in children:
        # Note: Geometries are assumed to be in WGS84 (EPSG:4326)
        children_data.append({
            'id': child.id,
            'name': child.name,
            'event_type': child.event_type,
            'footprintWKT': child.footprintWKT if child.footprintWKT else None,
        })
    
    return JsonResponse({
        'event_id': event.id,
        'event_name': event.name,
        'children': children_data
    })


def event_details(request: HttpRequest, pk: int) -> HttpResponse:
    """HTMX endpoint to fetch event details (attributes, occurrences, measurements)."""
    event = get_object_or_404(Event, pk=pk)
    
    # Check if user wants to include descendant occurrences
    include_descendants = request.GET.get('include_descendants', 'false').lower() == 'true'
    
    # Get occurrences using service
    occurrences = EventService.get_occurrences_for_event(event, include_descendants)
    
    # Get related data
    measurements = event.measurements.all()
    children = event.children.all().order_by('start_date', 'name')
    
    # Get distinct taxa from occurrences
    taxa = EventService.get_taxa_for_occurrences(occurrences)
    
    # Get statistics using service (uses aggregate to avoid multiple QuerySet evaluations)
    stats = EventService.get_statistics(event)
    
    # Get breadcrumb using model method
    breadcrumb = event.get_breadcrumb()
    
    context = {
        'event': event,
        'occurrences': occurrences,
        'taxa': taxa,
        'measurements': measurements,
        'children': children,
        'direct_occurrences_count': stats.direct_occurrences_count,
        'direct_taxa_count': stats.direct_taxa_count,
        'child_occurrences_count': stats.child_occurrences_count,
        'child_taxa_count': stats.child_taxa_count,
        'children_count': stats.children_count,
        'breadcrumb': breadcrumb,
        'include_descendants': include_descendants,
    }
    
    return render(request, 'eventhub/_event_details.html', context)


def event_occurrences(request: HttpRequest, pk: int) -> HttpResponse:
    """HTMX endpoint to fetch occurrences content for an event."""
    event = get_object_or_404(Event, pk=pk)
    
    # Check if user wants to include descendant occurrences
    include_descendants = request.GET.get('include_descendants', 'false').lower() == 'true'
    
    # Get occurrences using service
    occurrences = EventService.get_occurrences_for_event(event, include_descendants)
    
    # Get statistics for display (only what we need for the occurrences tab)
    stats = EventService.get_statistics(event) #this method would be cached in prod
    
    # Serialize occurrences with coordinates for map
    occurrences_for_map = []
    if occurrences:
        for occ in occurrences:
            if occ.latitude is not None and occ.longitude is not None:
                occurrences_for_map.append({
                    'id': occ.id,
                    'taxon_name': occ.taxon_name,
                    'common_name': occ.common_name or '',
                    'latitude': float(occ.latitude),
                    'longitude': float(occ.longitude),
                    'occurrence_date': occ.occurrence_date.isoformat() if occ.occurrence_date else None,
                    'abundance': occ.abundance or '',
                    'grid_reference': occ.grid_reference or '',
                })
    
    context = {
        'event': event,
        'occurrences': occurrences,
        'occurrences_for_map_json': json.dumps(occurrences_for_map),
        'include_descendants': include_descendants,
        'children_count': stats.children_count,
        'child_occurrences_count': stats.child_occurrences_count,
    }
    
    return render(request, 'eventhub/_event_occurrences.html', context)


def event_occurrences_geojson(request: HttpRequest, pk: int) -> JsonResponse:
    """JSON endpoint to get occurrences as GeoJSON for an event."""
    event = get_object_or_404(Event, pk=pk)
    
    # Check if user wants to include descendant occurrences
    include_descendants = request.GET.get('include_descendants', 'false').lower() == 'true'
    
    # Get occurrences using service
    occurrences = EventService.get_occurrences_for_event(event, include_descendants)
    
    # Build GeoJSON FeatureCollection
    features = []
    for occ in occurrences:
        if occ.latitude is not None and occ.longitude is not None:
            # Create GeoJSON feature
            feature = {
                'type': 'Feature',
                'geometry': {
                    'type': 'Point',
                    'coordinates': [float(occ.longitude), float(occ.latitude)]  # GeoJSON uses [lon, lat]
                },
                'properties': {
                    'id': occ.id,
                    'occurrence_id': occ.occurrence_id or '',
                    'taxon_name': occ.taxon_name,
                    'common_name': occ.common_name or '',
                    'taxon_id': occ.taxon_id or '',
                    'occurrence_date': occ.occurrence_date.isoformat() if occ.occurrence_date else None,
                    'abundance': occ.abundance or '',
                    'grid_reference': occ.grid_reference or '',
                    'recorded_by': occ.recorded_by or '',
                }
            }
            features.append(feature)
    
    geojson = {
        'type': 'FeatureCollection',
        'features': features
    }
    
    return JsonResponse(geojson)


def event_taxa(request: HttpRequest, pk: int) -> HttpResponse:
    """HTMX endpoint to fetch taxa content for an event."""
    event = get_object_or_404(Event, pk=pk)
    
    # Check if user wants to include descendant taxa
    include_descendants = request.GET.get('include_descendants', 'false').lower() == 'true'
    
    # Get occurrences using service
    occurrences = EventService.get_occurrences_for_event(event, include_descendants)
    
    # Get distinct taxa from occurrences
    taxa = EventService.get_taxa_for_occurrences(occurrences)
    
    # Get statistics for display
    stats = EventService.get_statistics(event)
    
    context = {
        'event': event,
        'taxa': taxa,
        'include_descendants': include_descendants,
        'children_count': stats.children_count,
        'child_taxa_count': stats.child_taxa_count,
    }
    
    return render(request, 'eventhub/_event_taxa.html', context)
