def door_visits(request):
    """
    Manage door knocking visits. Groups unvisited targeted voters by household.
    """
    selected_tenant_id = request.session.get("tenant_id")
    if not selected_tenant_id:
        messages.warning(request, "Please select a campaign first.")
        return redirect("index")

    tenant = get_object_or_404(Tenant, id=selected_tenant_id)
    
    # Filters from GET parameters
    district_filter = request.GET.get('district', '').strip()
    neighborhood_filter = request.GET.get('neighborhood', '').strip()
    address_filter = request.GET.get('address', '').strip()

    # Initial queryset: unvisited targeted voters for this tenant
    voters = Voter.objects.filter(tenant=tenant, door_visit=False, is_targeted=True)

    # Apply filters if provided
    if district_filter:
        voters = voters.filter(district=district_filter)
    if neighborhood_filter:
        voters = voters.filter(neighborhood__icontains=neighborhood_filter)
    if address_filter:
        voters = voters.filter(Q(address_street__icontains=address_filter) | Q(address__icontains=address_filter))

    # Grouping by household (unique address)
    households_dict = {}
    for voter in voters:
        # Key for grouping is the unique address components
        key = (voter.address_street, voter.city, voter.state, voter.zip_code)
        if key not in households_dict:
            # Parse street name and number for sorting
            street_number = ""
            street_name = voter.address_street
            match = re.match(r'^(\d+)\s+(.*)$', voter.address_street)
            if match:
                street_number = match.group(1)
                street_name = match.group(2)
            
            try:
                street_number_sort = int(street_number)
            except ValueError:
                street_number_sort = 0

            households_dict[key] = {
                'address_street': voter.address_street,
                'city': voter.city,
                'state': voter.state,
                'zip_code': voter.zip_code,
                'neighborhood': voter.neighborhood,
                'district': voter.district,
                'latitude': float(voter.latitude) if voter.latitude else None,
                'longitude': float(voter.longitude) if voter.longitude else None,
                'street_name_sort': street_name.lower(),
                'street_number_sort': street_number_sort,
                'target_voters': [],
                'voters_json': []
            }
        households_dict[key]['target_voters'].append(voter)
        households_dict[key]['voters_json'].append({'id': voter.id, 'name': f"{voter.first_name} {voter.last_name}"})

    households_list = list(households_dict.values())
    for h in households_list:
        h['voters_json_str'] = json.dumps(h['voters_json'])

    households_list.sort(key=lambda x: (
        (x['neighborhood'] or '').lower(),
        x['street_name_sort'],
        x['street_number_sort']
    ))

    # Prepare data for Google Map (all filtered households with coordinates)
    map_data = [
        {
            'lat': h['latitude'],
            'lng': h['longitude'],
            'address': f"{h['address_street']}, {h['city']}, {h['state']}",
            'voters': ", ".join([f"{v.first_name} {v.last_name}" for v in h['target_voters']])
        }
        for h in households_list if h['latitude'] and h['longitude']
    ]

    paginator = Paginator(households_list, 50)
    page_number = request.GET.get('page')
    households_page = paginator.get_page(page_number)

    context = {
        'selected_tenant': tenant,
        'households': households_page,
        'district_filter': district_filter,
        'neighborhood_filter': neighborhood_filter,
        'address_filter': address_filter,
        'map_data_json': json.dumps(map_data),
        'google_maps_api_key': getattr(settings, 'GOOGLE_MAPS_API_KEY', ''),
        'visit_form': DoorVisitLogForm(),
    }
    return render(request, 'core/door_visits.html', context)

@role_required(['admin', 'campaign_manager', 'campaign_staff', 'system_admin', 'campaign_admin'], permission='core.view_voter')
def log_door_visit(request):
    """
    Mark all targeted voters at a specific address as visited, update their flags,
    and create interaction records.
    """
    selected_tenant_id = request.session.get("tenant_id")
    if not selected_tenant_id:
        return redirect("index")
    
    tenant = get_object_or_404(Tenant, id=selected_tenant_id)
    campaign_settings, _ = CampaignSettings.objects.get_or_create(tenant=tenant)
    
    # Capture query string for redirecting back with filters
    next_qs = request.POST.get("next_query_string", "")
    redirect_url = reverse("door_visits")
    if next_qs:
        redirect_url += f"?{next_qs}"
    
    # Get the volunteer linked to the current user
    volunteer = Volunteer.objects.filter(user=request.user, tenant=tenant).first()
    
    if request.method == "POST":
        form = DoorVisitLogForm(request.POST)
        if form.is_valid():
            address_street = request.POST.get("address_street")
            city = request.POST.get("city")
            state = request.POST.get("state")
            zip_code = request.POST.get("zip_code")
            
            outcome = form.cleaned_data["outcome"]
            notes = form.cleaned_data["notes"]
            wants_yard_sign = form.cleaned_data["wants_yard_sign"]
            candidate_support = form.cleaned_data["candidate_support"]
            follow_up = form.cleaned_data["follow_up"]
            follow_up_voter_id = form.cleaned_data.get("follow_up_voter")
            call_notes = form.cleaned_data["call_notes"]
            
            # Determine date/time in campaign timezone
            campaign_tz_name = campaign_settings.timezone or "America/Chicago"
            try:
                tz = zoneinfo.ZoneInfo(campaign_tz_name)
            except:
                tz = zoneinfo.ZoneInfo("America/Chicago")
            
            interaction_date = timezone.now().astimezone(tz)
            
            # Get or create InteractionType
            interaction_type, _ = InteractionType.objects.get_or_create(tenant=tenant, name="Door Visit")
            
            # Find targeted voters at this exact address
            voters = Voter.objects.filter(
                tenant=tenant,
                address_street=address_street,
                city=city,
                state=state,
                zip_code=zip_code,
                is_targeted=True
            )
            
            if not voters.exists():
                messages.warning(request, f"No targeted voters found at {address_street}.")
                return redirect(redirect_url)

            # Get default caller for follow-ups
            default_caller = None
            if follow_up:
                default_caller = Volunteer.objects.filter(tenant=tenant, is_default_caller=True).first()

            for voter in voters:
                # 1) Update voter flags
                voter.door_visit = True
                
                # 2) If "Wants a Yard Sign" checkbox is selected
                if wants_yard_sign:
                    voter.yard_sign = "wants"
                
                # 3) Update support status if Supporting or Not Supporting
                if candidate_support in ["supporting", "not_supporting"]:
                    voter.candidate_support = candidate_support
                
                voter.save()
                
                # 4) Create interaction
                Interaction.objects.create(
                    voter=voter,
                    volunteer=volunteer,
                    type=interaction_type,
                    date=interaction_date,
                    description=outcome,
                    notes=notes
                )

                # 5) Create ScheduledCall if follow_up is checked and this is the selected voter
                if follow_up and follow_up_voter_id and str(voter.id) == follow_up_voter_id:
                    ScheduledCall.objects.create(
                        tenant=tenant,
                        voter=voter,
                        volunteer=default_caller,
                        comments=call_notes,
                        status="pending"
                    )
            
            if follow_up:
                messages.success(request, f"Door visit logged and follow-up call scheduled for {address_street}.")
            else:
                messages.success(request, f"Door visit logged for {address_street}.")
        else:
            messages.error(request, "There was an error in the visit log form.")
    
    return redirect(redirect_url)