import os
import platform
import json
import csv
import calendar
import datetime
from django.shortcuts import render, redirect, get_object_or_404
from django.utils import timezone
from django.contrib.auth.decorators import login_required, user_passes_test
from django.db import transaction
from django.db.models import Sum, Q, Prefetch
from django.db.models.functions import TruncMonth
from django.core.mail import send_mail, EmailMultiAlternatives
from django.conf import settings
from django.contrib import messages
from django.http import JsonResponse, HttpResponse
from django.template.loader import render_to_string
from django.utils.html import strip_tags
from .models import Worker, Project, Team, WorkLog, PayrollRecord, Loan, PayrollAdjustment, ExpenseReceipt, ExpenseLineItem
from .forms import WorkLogForm, ExpenseReceiptForm, ExpenseLineItemFormSet
from datetime import timedelta
from decimal import Decimal
from core.utils import render_to_pdf


# === CONSTANTS ===

ADDITIVE_TYPES = ['BONUS', 'OVERTIME', 'LOAN']
DEDUCTIVE_TYPES = ['DEDUCTION', 'LOAN_REPAYMENT', 'ADVANCE']


# === PERMISSION HELPERS ===

def is_admin(user):
    """Check if user has admin-level access (staff, superuser, or in Admin group)."""
    return user.is_staff or user.is_superuser

def is_supervisor(user):
    """Check if user is a work logger (assigned to teams or projects, or in Work Logger group)."""
    if user.groups.filter(name='Work Logger').exists():
        return True
    return user.managed_teams.exists() or user.assigned_projects.exists()

def is_staff_or_supervisor(user):
    """Check if user has at least supervisor-level access."""
    return is_admin(user) or is_supervisor(user)


# === HOME DASHBOARD ===

@login_required
def home(request):
    """Render the landing screen with dashboard stats."""
    # If not staff or supervisor, redirect to log attendance
    if not is_staff_or_supervisor(request.user):
        return redirect('log_attendance')

    user_is_admin = is_admin(request.user)
    now = timezone.now()
    today = now.date()

    # Counts (used by both admin and non-admin)
    workers_count = Worker.objects.filter(is_active=True).count()
    projects_count = Project.objects.filter(is_active=True).count()
    teams_count = Team.objects.filter(is_active=True).count()

    # Recent logs with team info
    recent_logs = WorkLog.objects.select_related('team', 'project').prefetch_related('workers').order_by('-date', '-id')[:5]

    # --- Admin-only analytics ---
    outstanding_total = 0
    recent_payments_total = 0
    active_loans_count = 0
    active_loans_total = 0
    week_worker_days = 0
    week_projects = 0

    if user_is_admin:
        # 1. Outstanding Payments (prefetch all related data to avoid per-worker queries)
        active_workers = Worker.objects.filter(is_active=True).prefetch_related(
            Prefetch(
                'work_logs',
                queryset=WorkLog.objects.prefetch_related(
                    Prefetch('paid_in', queryset=PayrollRecord.objects.only('id', 'worker_id'))
                ),
            ),
            Prefetch(
                'adjustments',
                queryset=PayrollAdjustment.objects.filter(payroll_record__isnull=True),
                to_attr='pending_adjustments_list'
            ),
        )
        for worker in active_workers:
            # Unpaid logs (filter in Python using prefetch cache)
            unpaid_logs_count = 0
            for log in worker.work_logs.all():
                paid_worker_ids = {pr.worker_id for pr in log.paid_in.all()}
                if worker.id not in paid_worker_ids:
                    unpaid_logs_count += 1
            log_amount = unpaid_logs_count * worker.day_rate

            # Pending Adjustments (use prefetched list)
            adj_total = Decimal('0.00')
            for adj in worker.pending_adjustments_list:
                if adj.type in ADDITIVE_TYPES:
                    adj_total += adj.amount
                elif adj.type in DEDUCTIVE_TYPES:
                    adj_total -= adj.amount

            total_payable = log_amount + adj_total
            outstanding_total += max(total_payable, Decimal('0.00'))

        # 2. Paid This Month
        recent_payments_total = PayrollRecord.objects.filter(
            date__year=today.year, date__month=today.month
        ).aggregate(total=Sum('amount'))['total'] or 0

        # 3. Active Loans
        active_loans = Loan.objects.filter(is_active=True)
        active_loans_count = active_loans.count()
        active_loans_total = active_loans.aggregate(total=Sum('balance'))['total'] or 0

    # 4. This Week stats (Mon-Sun) - visible to all users
    week_start = today - timedelta(days=today.weekday())  # Monday
    week_end = week_start + timedelta(days=6)  # Sunday
    week_logs = WorkLog.objects.filter(date__range=(week_start, week_end)).prefetch_related('workers')
    week_projects = week_logs.values('project').distinct().count()
    for log in week_logs:
        week_worker_days += len(log.workers.all())

    # Manage Resources data (admin only)
    all_workers = Worker.objects.all().prefetch_related('teams').order_by('name') if user_is_admin else []
    all_projects = Project.objects.all().order_by('name').prefetch_related(
        Prefetch('logs', queryset=WorkLog.objects.prefetch_related('workers', 'paid_in')),
    ) if user_is_admin else []
    all_teams = Team.objects.all().prefetch_related('workers').order_by('name') if user_is_admin else []

    # Outstanding Project Costs (Admin only) - Added for Dashboard visibility
    outstanding_project_costs = []
    if user_is_admin:
        # Bulk-fetch all pending project-linked adjustments in one query
        pending_proj_adjs = {}
        for adj in PayrollAdjustment.objects.filter(
            work_log__project__isnull=False,
            payroll_record__isnull=True
        ).select_related('work_log'):
            pid = adj.work_log.project_id
            pending_proj_adjs.setdefault(pid, []).append(adj)

        for project in all_projects:
            outstanding_cost = 0

            # Unpaid WorkLogs (use prefetch cache, check paid_in in Python)
            for log in project.logs.all():
                if not list(log.paid_in.all()):
                    for worker in log.workers.all():
                        outstanding_cost += worker.day_rate

            # Unpaid Adjustments linked to this project (from bulk-fetched dict)
            for adj in pending_proj_adjs.get(project.id, []):
                if adj.type in ADDITIVE_TYPES:
                    outstanding_cost += adj.amount
                elif adj.type in DEDUCTIVE_TYPES:
                    outstanding_cost -= adj.amount

            if outstanding_cost > 0:
                outstanding_project_costs.append({'name': project.name, 'cost': outstanding_cost})

    context = {
        "is_admin_user": user_is_admin,
        "workers_count": workers_count,
        "projects_count": projects_count,
        "teams_count": teams_count,
        "recent_logs": recent_logs,
        "current_time": now,
        # Admin financials
        "outstanding_total": outstanding_total,
        "recent_payments_total": recent_payments_total,
        "active_loans_count": active_loans_count,
        "active_loans_total": active_loans_total,
        "outstanding_project_costs": outstanding_project_costs,
        # This week
        "week_worker_days": week_worker_days,
        "week_projects": week_projects,
        # Manage resources
        "all_workers": all_workers,
        "all_projects": all_projects,
        "all_teams": all_teams,
    }
    return render(request, "core/index.html", context)


# === LOG ATTENDANCE ===

@login_required
def log_attendance(request):
    # Build team workers map for frontend JS (needed for both GET and POST if re-rendering)
    teams_qs = Team.objects.filter(is_active=True)
    if request.user.is_authenticated and not request.user.is_superuser:
        teams_qs = teams_qs.filter(supervisor=request.user)
    
    team_workers_map = {}
    for team in teams_qs:
        # Get active workers for the team
        active_workers = team.workers.filter(is_active=True).values_list('id', flat=True)
        team_workers_map[team.id] = list(active_workers)

    if request.method == 'POST':
        form = WorkLogForm(request.POST, user=request.user)
        if form.is_valid():
            start_date = form.cleaned_data['date']
            end_date = form.cleaned_data.get('end_date')
            include_sat = form.cleaned_data.get('include_saturday')
            include_sun = form.cleaned_data.get('include_sunday')
            selected_workers = form.cleaned_data['workers']
            project = form.cleaned_data['project']
            team = form.cleaned_data.get('team')
            notes = form.cleaned_data['notes']
            overtime = form.cleaned_data.get('overtime', 0) # Read overtime
            conflict_action = request.POST.get('conflict_action')

            # Generate Target Dates
            target_dates = []
            if end_date and end_date >= start_date:
                curr = start_date
                while curr <= end_date:
                    # 5 = Saturday, 6 = Sunday
                    if (curr.weekday() == 5 and not include_sat) or (curr.weekday() == 6 and not include_sun):
                        curr += timedelta(days=1)
                        continue
                    target_dates.append(curr)
                    curr += timedelta(days=1)
            else:
                target_dates = [start_date]

            if not target_dates:
                 messages.warning(request, "No valid dates selected (check weekends).")
                 return render(request, 'core/log_attendance.html', {
                     'form': form, 'team_workers_json': json.dumps(team_workers_map)
                 })

            # Check Conflicts - Scan all target dates
            if not conflict_action:
                conflicts = []
                for d in target_dates:
                    # Find workers who already have a log on this date
                    existing_logs = WorkLog.objects.filter(date=d, workers__in=selected_workers).distinct()
                    for log in existing_logs:
                        # Which of the selected workers are in this log?
                        for w in log.workers.all():
                            if w in selected_workers:
                                # Avoid adding duplicates if multiple logs exist for same worker/day (rare but possible)
                                conflict_entry = {'name': f"{w.name} ({d.strftime('%Y-%m-%d')})"}
                                if conflict_entry not in conflicts:
                                    conflicts.append(conflict_entry)
                
                if conflicts:
                    # Prepare worker rates for JS calculation
                    worker_qs = form.fields['workers'].queryset
                    worker_rates = {w.id: float(w.day_rate) for w in worker_qs}
                    
                    context = {
                        'form': form,
                        'team_workers_json': json.dumps(team_workers_map),
                        'conflicting_workers': conflicts,
                        'is_conflict': True,
                        'worker_rates_json': json.dumps(worker_rates),
                    }
                    return render(request, 'core/log_attendance.html', context)
            
            # Execution Phase
            created_count = 0
            skipped_count = 0
            overwritten_count = 0
            
            for d in target_dates:
                # Find conflicts for this specific day
                day_conflicts = Worker.objects.filter(
                    work_logs__date=d, 
                    id__in=selected_workers.values_list('id', flat=True)
                ).distinct()
                
                workers_to_save = list(selected_workers)
                
                if day_conflicts.exists():
                    if conflict_action == 'skip':
                        conflicting_ids = day_conflicts.values_list('id', flat=True)
                        workers_to_save = [w for w in selected_workers if w.id not in conflicting_ids]
                        skipped_count += day_conflicts.count()
                    
                    elif conflict_action == 'overwrite':
                        # Remove conflicting workers from their OLD logs
                        for worker in day_conflicts:
                            old_logs = WorkLog.objects.filter(date=d, workers=worker)
                            for log in old_logs:
                                log.workers.remove(worker)
                                if log.workers.count() == 0:
                                    log.delete()
                        overwritten_count += day_conflicts.count()
                        # workers_to_save remains full list
                
                if workers_to_save:
                    # Create Log
                    log = WorkLog.objects.create(
                        date=d,
                        project=project,
                        team=team,
                        notes=notes,
                        supervisor=request.user if request.user.is_authenticated else None,
                        overtime=overtime # Save overtime
                    )
                    log.workers.set(workers_to_save)
                    created_count += len(workers_to_save)

            msg = f"Logged {created_count} entries."
            if skipped_count:
                msg += f" Skipped {skipped_count} duplicates."
            if overwritten_count:
                msg += f" Overwrote {overwritten_count} previous entries."
            
            messages.success(request, msg)
            # Redirect to home, which will then redirect back to log_attendance if restricted
            return redirect('home')
    else:
        form = WorkLogForm(user=request.user if request.user.is_authenticated else None)
    
    # Pass worker rates for frontend total calculation (admin only)
    user_is_admin = is_admin(request.user)
    if user_is_admin:
        worker_qs = form.fields['workers'].queryset
        worker_rates = {w.id: float(w.day_rate) for w in worker_qs}
    else:
        worker_rates = {}

    context = {
        'form': form,
        'is_admin_user': user_is_admin,
        'team_workers_json': json.dumps(team_workers_map),
        'worker_rates_json': json.dumps(worker_rates)
    }

    return render(request, 'core/log_attendance.html', context)


# === WORK LOG LIST ===

@login_required
def work_log_list(request):
    """View work log history and payroll adjustments with advanced filtering."""
    if not is_staff_or_supervisor(request.user):
        return redirect('log_attendance')

    worker_id = request.GET.get('worker')
    team_id = request.GET.get('team')
    project_id = request.GET.get('project')
    payment_status = request.GET.get('payment_status') # 'paid', 'unpaid', 'all'
    view_mode = request.GET.get('view', 'list')

    # Validate numeric GET params to prevent 500 on bad input
    try:
        worker_id = str(int(worker_id)) if worker_id else None
    except (ValueError, TypeError):
        worker_id = None
    try:
        team_id = str(int(team_id)) if team_id else None
    except (ValueError, TypeError):
        team_id = None
    try:
        project_id = str(int(project_id)) if project_id else None
    except (ValueError, TypeError):
        project_id = None

    # --- 1. Fetch WorkLogs ---
    logs = WorkLog.objects.all().select_related('team', 'project', 'supervisor').prefetch_related('workers', 'paid_in').order_by('-date', '-id')

    target_worker = None
    if worker_id:
        logs = logs.filter(workers__id=worker_id)
        target_worker = Worker.objects.filter(id=worker_id).first()

    if team_id:
        logs = logs.filter(team_id=team_id)
        
    if project_id:
        logs = logs.filter(project_id=project_id)
        
    if payment_status == 'paid':
        logs = logs.filter(paid_in__isnull=False).distinct()
    elif payment_status == 'unpaid':
        if worker_id:
            worker = get_object_or_404(Worker, pk=worker_id)
            logs = logs.exclude(paid_in__worker=worker)
        else:
            logs = logs.filter(paid_in__isnull=True)

    # --- 2. Fetch Adjustments ---
    adjustments = PayrollAdjustment.objects.all().select_related('worker', 'payroll_record', 'work_log')

    if worker_id:
        adjustments = adjustments.filter(worker_id=worker_id)
    
    if project_id:
        # Include only adjustments linked to this project (via work_log)
        adjustments = adjustments.filter(work_log__project_id=project_id)
        
    if team_id:
        adjustments = adjustments.filter(work_log__team_id=team_id)

    if payment_status == 'paid':
        adjustments = adjustments.filter(payroll_record__isnull=False)
    elif payment_status == 'unpaid':
        adjustments = adjustments.filter(payroll_record__isnull=True)
            
    # --- 3. Date Filtering for Calendar View (Applied to both) ---
    start_date = None
    end_date = None
    curr_year = timezone.now().year
    curr_month = timezone.now().month

    if view_mode == 'calendar':
        today = timezone.now().date()
        try:
            curr_year = int(request.GET.get('year', today.year))
            curr_month = int(request.GET.get('month', today.month))
        except ValueError:
            curr_year = today.year
            curr_month = today.month
            
        if curr_month < 1: curr_month = 1; 
        if curr_month > 12: curr_month = 12;

        _, num_days = calendar.monthrange(curr_year, curr_month)
        start_date = datetime.date(curr_year, curr_month, 1)
        end_date = datetime.date(curr_year, curr_month, num_days)
        
        logs = logs.filter(date__range=(start_date, end_date))
        # No 'show_adjustments' check needed as query is already filtered
        adjustments = adjustments.filter(date__range=(start_date, end_date))

    # --- 4. Combine and Sort ---
    user_is_admin = is_admin(request.user)
    total_amount = 0
    combined_records = []

    # Prepare Chart Data (Overtime) - Admin only
    ot_chart_labels = []
    ot_chart_data = []

    if user_is_admin:
        from django.db.models.functions import TruncMonth
        ot_stats = adjustments.filter(type='OVERTIME') \
            .annotate(month=TruncMonth('date')) \
            .values('month') \
            .annotate(total=Sum('amount')) \
            .order_by('month')
        
        ot_chart_labels = [s['month'].strftime('%b %Y') for s in ot_stats]
        ot_chart_data = [float(s['total']) for s in ot_stats]

    # Process Logs
    for log in logs:
        record = {
            'type': 'WORK',
            'date': log.date,
            'obj': log,
            'project_name': log.project.name,
            'team_name': log.team.name if log.team else None,
            'workers': list(log.workers.all()),
            'supervisor': log.supervisor.username if log.supervisor else "System",
            'is_paid': log.paid_in.exists() if not worker_id else log.paid_in.filter(worker_id=worker_id).exists(),
            'paid_record': log.paid_in.first() if not worker_id else log.paid_in.filter(worker_id=worker_id).first(),
            'notes': log.notes,
            'sort_id': log.id
        }
        
        # Calculate amount
        if user_is_admin:
            if target_worker:
                amt = target_worker.day_rate
            else:
                amt = sum(w.day_rate for w in log.workers.all())
            record['amount'] = amt
            total_amount += amt
        else:
            record['amount'] = None
            
        combined_records.append(record)

    # Process Adjustments
    for adj in adjustments:
        # Determine signed amount for display/total
        amt = adj.amount
        if adj.type in DEDUCTIVE_TYPES:
            amt = -amt
        
        record = {
            'type': 'ADJ',
            'date': adj.date,
            'obj': adj,
            'project_name': f"{adj.get_type_display()}", # Use project column for Type
            'team_name': None,
            'workers': [adj.worker],
            'supervisor': "System",
            'is_paid': adj.payroll_record is not None,
            'paid_record': adj.payroll_record,
            'notes': adj.description,
            'amount': amt if user_is_admin else None,
            'sort_id': adj.id
        }
        
        if user_is_admin:
            total_amount += amt
            
        combined_records.append(record)

    # Sort combined list by Date Descending, then ID Descending
    combined_records.sort(key=lambda x: (x['date'], x['sort_id']), reverse=True)

    # Context for filters
    context = {
        'is_admin_user': user_is_admin,
        'total_amount': total_amount if user_is_admin else None,
        'workers': Worker.objects.filter(is_active=True).order_by('name'),
        'teams': Team.objects.filter(is_active=True).order_by('name'),
        'projects': Project.objects.filter(is_active=True).order_by('name'),
        'selected_worker': int(worker_id) if worker_id else None,
        'selected_team': int(team_id) if team_id else None,
        'selected_project': int(project_id) if project_id else None,
        'selected_payment_status': payment_status,
        'target_worker': target_worker,
        'view_mode': view_mode,
        'ot_chart_labels': json.dumps(ot_chart_labels),
        'ot_chart_data': json.dumps(ot_chart_data),
    }

    if view_mode == 'calendar':
        # Group by date for easy lookup in template
        records_map = {}
        for rec in combined_records:
            d = rec['date']
            if d not in records_map:
                records_map[d] = []
            records_map[d].append(rec)
            
        cal = calendar.Calendar(firstweekday=0) # Monday is 0
        month_dates = cal.monthdatescalendar(curr_year, curr_month)
        
        # Prepare structured data for template
        calendar_weeks = []
        for week in month_dates:
            week_data = []
            for d in week:
                week_data.append({
                    'date': d,
                    'day': d.day,
                    'is_current_month': d.month == curr_month,
                    'records': records_map.get(d, [])
                })
            calendar_weeks.append(week_data)
        
        # Build JSON lookup for day detail panel
        calendar_detail_data = {}
        for date_key, day_records in records_map.items():
            date_str = date_key.strftime('%Y-%m-%d')
            calendar_detail_data[date_str] = []
            for rec in day_records:
                workers_list = [w.name for w in rec['workers']]
                team_name = rec['team_name'] if rec['team_name'] else ''
                
                # Format for JS
                calendar_detail_data[date_str].append({
                    'project': rec['project_name'], # This holds Type for ADJ
                    'teams': [team_name] if team_name else [],
                    'workers': workers_list,
                    'supervisor': rec['supervisor'],
                    'notes': rec['notes'] or '',
                    'type': rec['type'],
                    'amount': float(rec['amount']) if rec['amount'] is not None else 0
                })

        # Nav Links
        prev_month_date = start_date - datetime.timedelta(days=1)
        next_month_date = end_date + datetime.timedelta(days=1)

        context.update({
            'calendar_detail_json': json.dumps(calendar_detail_data),
            'calendar_weeks': calendar_weeks,
            'curr_month': curr_month,
            'curr_year': curr_year,
            'month_name': calendar.month_name[curr_month],
            'prev_month': prev_month_date.month,
            'prev_year': prev_month_date.year,
            'next_month': next_month_date.month,
            'next_year': next_month_date.year,
        })
    else:
        context['records'] = combined_records
    
    return render(request, 'core/work_log_list.html', context)


# === EXPORT WORK LOG CSV ===

@login_required
def export_work_log_csv(request):
    """Export filtered work logs and adjustments to CSV."""
    if not is_staff_or_supervisor(request.user):
        return HttpResponse("Unauthorized", status=401)

    worker_id = request.GET.get('worker')
    team_id = request.GET.get('team')
    project_id = request.GET.get('project')
    payment_status = request.GET.get('payment_status')

    # --- 1. Fetch WorkLogs ---
    logs = WorkLog.objects.all().prefetch_related('workers', 'workers__teams', 'project', 'supervisor', 'paid_in').order_by('-date', '-id')

    target_worker = None
    if worker_id:
        logs = logs.filter(workers__id=worker_id)
        target_worker = Worker.objects.filter(id=worker_id).first()

    if team_id:
        team_workers = Worker.objects.filter(teams__id=team_id)
        logs = logs.filter(workers__in=team_workers).distinct()

    if project_id:
        logs = logs.filter(project_id=project_id)

    if payment_status == 'paid':
        logs = logs.filter(paid_in__isnull=False).distinct()
    elif payment_status == 'unpaid':
        if worker_id:
            worker = get_object_or_404(Worker, pk=worker_id)
            logs = logs.exclude(paid_in__worker=worker)
        else:
            logs = logs.filter(paid_in__isnull=True)

    # --- 2. Fetch Adjustments ---
    adjustments = PayrollAdjustment.objects.all().select_related('worker', 'payroll_record', 'work_log')

    if worker_id:
        adjustments = adjustments.filter(worker_id=worker_id)
    
    if project_id:
        adjustments = adjustments.filter(work_log__project_id=project_id)
        
    if team_id:
        adjustments = adjustments.filter(work_log__team_id=team_id)

    if payment_status == 'paid':
        adjustments = adjustments.filter(payroll_record__isnull=False)
    elif payment_status == 'unpaid':
        adjustments = adjustments.filter(payroll_record__isnull=True)
    
    adjustments = list(adjustments)

    user_is_admin = is_admin(request.user)

    response = HttpResponse(content_type='text/csv')
    response['Content-Disposition'] = 'attachment; filename="work_logs_and_adjustments.csv"'

    writer = csv.writer(response)
    if user_is_admin:
        writer.writerow(['Date', 'Description', 'Workers', 'Amount', 'Payment Status', 'Supervisor'])
    else:
        writer.writerow(['Date', 'Description', 'Workers', 'Supervisor'])

    # Combine and Sort
    combined = []
    
    for log in logs:
        if target_worker:
            workers_str = target_worker.name
        else:
            workers_str = ", ".join([w.name for w in log.workers.all()])
        
        amt = 0
        is_paid = False
        if user_is_admin:
            if target_worker:
                amt = target_worker.day_rate
                is_paid = log.paid_in.filter(worker=target_worker).exists()
            else:
                amt = sum(w.day_rate for w in log.workers.all())
                is_paid = log.paid_in.exists()

        combined.append({
            'date': log.date,
            'desc': log.project.name,
            'workers': workers_str,
            'amount': amt,
            'status': "Paid" if is_paid else "Pending",
            'supervisor': log.supervisor.username if log.supervisor else "System"
        })

    for adj in adjustments:
        amt = adj.amount
        if adj.type in DEDUCTIVE_TYPES:
            amt = -amt
        
        is_paid = adj.payroll_record is not None
        
        combined.append({
            'date': adj.date,
            'desc': f"{adj.get_type_display()} - {adj.description}",
            'workers': adj.worker.name,
            'amount': amt,
            'status': "Paid" if is_paid else "Pending",
            'supervisor': "System"
        })

    # Sort
    combined.sort(key=lambda x: x['date'], reverse=True)

    for row in combined:
        if user_is_admin:
            writer.writerow([
                row['date'], row['desc'], row['workers'],
                f"{row['amount']:.2f}", row['status'], row['supervisor']
            ])
        else:
            writer.writerow([
                row['date'], row['desc'], row['workers'], row['supervisor']
            ])

    return response


# === RESOURCE MANAGEMENT ===

@login_required
def manage_resources(request):
    """Redirect to dashboard which now includes manage resources."""
    return redirect('home')

@login_required
def toggle_resource_status(request, model_type, pk):
    """Toggle the is_active status of a resource."""
    if not is_admin(request.user):
        return redirect('log_attendance')

    if request.method == 'POST':
        model_map = {
            'worker': Worker,
            'project': Project,
            'team': Team,
        }
        
        model_class = model_map.get(model_type)
        if model_class:
            obj = get_object_or_404(model_class, pk=pk)
            obj.is_active = not obj.is_active
            obj.save()
            
            if request.headers.get('x-requested-with') == 'XMLHttpRequest':
                return JsonResponse({
                    'success': True, 
                    'is_active': obj.is_active, 
                    'message': f"{obj.name} is now {'Active' if obj.is_active else 'Inactive'}." 
                })
            
    return redirect('home')


# === PAYROLL DASHBOARD ===

@login_required
def payroll_dashboard(request):
    """Dashboard for payroll management with filtering."""
    if not is_admin(request.user):
        return redirect('log_attendance')

    status_filter = request.GET.get('status', 'pending') # pending, paid, all, loans

    # Common Analytics (prefetch all related data to avoid per-worker queries)
    outstanding_total = 0
    active_workers = Worker.objects.filter(is_active=True).order_by('name').prefetch_related(
        Prefetch(
            'work_logs',
            queryset=WorkLog.objects.select_related('project').prefetch_related(
                Prefetch('paid_in', queryset=PayrollRecord.objects.only('id', 'worker_id')),
                'overtime_paid_to',
            ),
        ),
        Prefetch(
            'adjustments',
            queryset=PayrollAdjustment.objects.filter(payroll_record__isnull=True),
            to_attr='pending_adjustments_list'
        ),
    )

    workers_data = [] # For pending payments
    all_ot_data = [] # For JSON context

    for worker in active_workers:
        # Unpaid Work Logs (filter in Python using prefetch cache)
        unpaid_logs = []
        for log in worker.work_logs.all():
            paid_worker_ids = {pr.worker_id for pr in log.paid_in.all()}
            if worker.id not in paid_worker_ids:
                unpaid_logs.append(log)
        log_count = len(unpaid_logs)
        log_amount = log_count * worker.day_rate

        # Overtime Logic (filter from unpaid logs using prefetch cache)
        ot_data_worker = []
        ot_hours_unpriced = Decimal('0.0')

        for log in unpaid_logs:
            if log.overtime > 0:
                ot_paid_ids = {w.id for w in log.overtime_paid_to.all()}
                if worker.id not in ot_paid_ids:
                    entry = {
                        'worker_id': worker.id,
                        'worker_name': worker.name,
                        'log_id': log.id,
                        'date': log.date.strftime('%Y-%m-%d'),
                        'project': log.project.name,
                        'overtime': float(log.overtime),
                        'ot_label': log.get_overtime_display(),
                    }
                    ot_data_worker.append(entry)
                    all_ot_data.append(entry)
                    ot_hours_unpriced += log.overtime

        # Pending Adjustments (use prefetched list — no extra queries)
        pending_adjustments = worker.pending_adjustments_list
        adj_total = Decimal('0.00')
        for adj in pending_adjustments:
            if adj.type in ADDITIVE_TYPES:
                adj_total += adj.amount
            elif adj.type in DEDUCTIVE_TYPES:
                adj_total -= adj.amount

        total_payable = log_amount + adj_total

        # Only show if there is something to pay or negative (e.g. loan repayment greater than work)
        # Note: If total_payable is negative, it implies they owe money.
        if log_count > 0 or len(pending_adjustments) > 0:
            outstanding_total += max(total_payable, Decimal('0.00')) # Only count positive payable for grand total

            if status_filter in ['pending', 'all']:
                workers_data.append({
                    'worker': worker,
                    'unpaid_count': log_count,
                    'unpaid_amount': log_amount,
                    'adj_amount': adj_total,
                    'total_payable': total_payable,
                    'adjustments': pending_adjustments,
                    'logs': unpaid_logs,
                    'ot_data': ot_data_worker,
                    'ot_hours_unpriced': float(ot_hours_unpriced),
                    'day_rate': float(worker.day_rate),
                    'has_pending_advances': any(a.type == 'ADVANCE' for a in pending_adjustments),
                })

    # Paid History
    paid_records = []
    if status_filter in ['paid', 'all']:
        paid_records = PayrollRecord.objects.select_related('worker').order_by('-date', '-id')

    # Analytics: Project Costs (prefetch all logs+workers in bulk)
    project_costs = []
    outstanding_project_costs = []
    active_projects = Project.objects.filter(is_active=True).prefetch_related(
        Prefetch('logs', queryset=WorkLog.objects.prefetch_related('workers', 'paid_in')),
    )

    # Bulk-fetch all pending project-linked adjustments in one query
    pending_proj_adjs = {}
    for adj in PayrollAdjustment.objects.filter(
        work_log__project__isnull=False,
        work_log__project__is_active=True,
        payroll_record__isnull=True,
    ).select_related('work_log'):
        pid = adj.work_log.project_id
        pending_proj_adjs.setdefault(pid, []).append(adj)

    for project in active_projects:
        # 1. Total Historical Cost
        cost = 0
        for log in project.logs.all():
            for worker in log.workers.all():
                 cost += worker.day_rate
        if cost > 0:
            project_costs.append({'name': project.name, 'cost': cost})

        # 2. Outstanding Cost (Unpaid)
        outstanding_cost = 0

        # Unpaid WorkLogs (check paid_in in Python using prefetch cache)
        for log in project.logs.all():
            if not list(log.paid_in.all()):
                for worker in log.workers.all():
                     outstanding_cost += worker.day_rate

        # Unpaid Adjustments linked to this project (from bulk-fetched dict)
        for adj in pending_proj_adjs.get(project.id, []):
            if adj.type in ADDITIVE_TYPES:
                outstanding_cost += adj.amount
            elif adj.type in DEDUCTIVE_TYPES:
                outstanding_cost -= adj.amount

        if outstanding_cost > 0:
            outstanding_project_costs.append({'name': project.name, 'cost': outstanding_cost})

    # Analytics: Previous 2 months payments
    two_months_ago = timezone.now().date() - timedelta(days=60)
    recent_payments_total = PayrollRecord.objects.filter(date__gte=two_months_ago).aggregate(total=Sum('amount'))['total'] or 0
    
    # Active Loans for dropdowns/modals
    all_workers = Worker.objects.filter(is_active=True).order_by('name')

    # Teams and team→worker mapping for adjustment modal
    all_teams = Team.objects.filter(is_active=True).order_by('name')
    team_workers_map = {}
    for team in all_teams:
        team_workers_map[team.id] = list(team.workers.filter(is_active=True).values_list('id', flat=True))

    # Loans data (for loans tab)
    loan_filter = request.GET.get('loan_status', 'active')
    if loan_filter == 'history':
        loans = Loan.objects.filter(is_active=False).order_by('-date')
    else:
        loans = Loan.objects.filter(is_active=True).order_by('-date')

    # --- Chart Data: Monthly payroll totals & per-project costs (last 6 months) ---
    today = timezone.now().date()
    chart_months = []  # list of (year, month) tuples, oldest first
    for i in range(5, -1, -1):
        m = today.month - i
        y = today.year
        while m <= 0:
            m += 12
            y -= 1
        chart_months.append((y, m))

    # Build a lookup: (year, month) → index in chart_months
    chart_month_index = {ym: idx for idx, ym in enumerate(chart_months)}
    chart_start = datetime.date(chart_months[0][0], chart_months[0][1], 1)

    chart_labels = [f"{calendar.month_abbr[m]} {y}" for y, m in chart_months]

    # 1 query: monthly payroll totals (grouped by month)
    paid_by_month = {}
    for row in PayrollRecord.objects.filter(date__gte=chart_start).annotate(
        month=TruncMonth('date')
    ).values('month').annotate(total=Sum('amount')):
        paid_by_month[(row['month'].year, row['month'].month)] = float(row['total'])

    chart_totals = [paid_by_month.get(ym, 0) for ym in chart_months]

    # 1 query: monthly overtime totals (grouped by month)
    ot_by_month = {}
    for row in PayrollAdjustment.objects.filter(
        type='OVERTIME', date__gte=chart_start
    ).annotate(month=TruncMonth('date')).values('month').annotate(total=Sum('amount')):
        ot_by_month[(row['month'].year, row['month'].month)] = float(row['total'])

    ot_history_totals = [ot_by_month.get(ym, 0) for ym in chart_months]

    # 1 query + prefetch: all work logs in the 6-month period for per-project costs
    all_project_names = list(Project.objects.values_list('name', flat=True).order_by('name'))
    project_monthly = {name: [0] * len(chart_months) for name in all_project_names}

    all_chart_logs = WorkLog.objects.filter(
        date__gte=chart_start
    ).select_related('project').prefetch_related('workers')

    for log in all_chart_logs:
        month_key = (log.date.year, log.date.month)
        idx = chart_month_index.get(month_key)
        if idx is not None:
            pname = log.project.name
            if pname in project_monthly:
                for worker in log.workers.all():
                    project_monthly[pname][idx] += float(worker.day_rate)

    # Filter out projects with zero cost across all months
    project_chart_data = [
        {'name': name, 'data': costs}
        for name, costs in project_monthly.items()
        if any(c > 0 for c in costs)
    ]

    context = {
        'workers_data': workers_data,
        'paid_records': paid_records,
        'outstanding_total': outstanding_total,
        'project_costs': project_costs,
        'outstanding_project_costs': outstanding_project_costs,
        'recent_payments_total': recent_payments_total,
        'active_tab': status_filter,
        'all_workers': all_workers,
        'all_teams': all_teams,
        'team_workers_map_json': json.dumps(team_workers_map),
        'adjustment_types': PayrollAdjustment.ADJUSTMENT_TYPES,
        'loans': loans,
        'loan_filter': loan_filter,
        'chart_labels_json': json.dumps(chart_labels),
        'chart_totals_json': json.dumps(chart_totals),
        'project_chart_json': json.dumps(project_chart_data),
        'overtime_data_json': json.dumps(all_ot_data),
        'ot_history_json': json.dumps(ot_history_totals),
    }
    return render(request, 'core/payroll_dashboard.html', context)


# === PROCESS PAYMENT ===

@login_required
def process_payment(request, worker_id):
    """Process payment for a worker, mark logs as paid, link adjustments, and email receipt."""
    if not is_admin(request.user):
        return redirect('log_attendance')

    worker = get_object_or_404(Worker, pk=worker_id)
    
    if request.method == 'POST':
        # Find unpaid logs
        unpaid_logs = worker.work_logs.exclude(paid_in__worker=worker)
        log_count = unpaid_logs.count()
        logs_amount = log_count * worker.day_rate
        
        # Find pending adjustments
        pending_adjustments = worker.adjustments.filter(payroll_record__isnull=True)
        adj_amount = Decimal('0.00')
        
        for adj in pending_adjustments:
            if adj.type in ADDITIVE_TYPES:
                adj_amount += adj.amount
            elif adj.type in DEDUCTIVE_TYPES:
                adj_amount -= adj.amount
        
        total_amount = logs_amount + adj_amount
        
        if log_count > 0 or pending_adjustments.exists():
            with transaction.atomic():
                # Create Payroll Record
                payroll_record = PayrollRecord.objects.create(
                    worker=worker,
                    amount=total_amount,
                    date=timezone.now().date()
                )

                # Link logs
                payroll_record.work_logs.set(unpaid_logs)

                # Link Adjustments and Handle Loans
                for adj in pending_adjustments:
                    adj.payroll_record = payroll_record
                    adj.save()

                    # Update Loan Balance if it's a repayment
                    if adj.type == 'LOAN_REPAYMENT' and adj.loan:
                        adj.loan.balance -= adj.amount
                        if adj.loan.balance <= 0:
                            adj.loan.balance = 0
                            adj.loan.is_active = False
                        adj.loan.save()

                payroll_record.save()

            # Email Notification (outside transaction — failure should not roll back payment)
            subject = f"Payslip for {worker.name} - {payroll_record.date}"
            
            # Prepare Context
            context = {
                'record': payroll_record,
                'logs_count': log_count,
                'logs_amount': logs_amount,
                'adjustments': payroll_record.adjustments.all(),
            }
            
            # 1. Render HTML Body
            html_message = render_to_string('core/email/payslip_email.html', context)
            plain_message = strip_tags(html_message)
            
            # 2. Render PDF Attachment
            pdf_content = render_to_pdf('core/pdf/payslip_pdf.html', context)
            
            recipient_list = [settings.SPARK_RECEIPT_EMAIL]
            
            try:
                # Construct Email with Attachment
                email = EmailMultiAlternatives(
                    subject,
                    plain_message,
                    settings.DEFAULT_FROM_EMAIL,
                    recipient_list,
                )
                email.attach_alternative(html_message, "text/html")
                
                if pdf_content:
                    email.attach(f"Payslip_{worker.id}_{payroll_record.date}.pdf", pdf_content, 'application/pdf')
                
                email.send()
                messages.success(request, f"Payment processed for {worker.name}. Net Pay: R {payroll_record.amount}")
            except Exception as e:
                messages.warning(request, f"Payment processed, but email delivery failed: {str(e)}")
            
            return redirect('payroll_dashboard')
            
    return redirect('payroll_dashboard')


# === PREVIEW PAYSLIP ===

@login_required
def preview_payslip(request, worker_id):
    """Return payslip preview data as JSON (no DB changes, no email)."""
    if not is_admin(request.user):
        return JsonResponse({'error': 'Unauthorized'}, status=403)

    worker = get_object_or_404(Worker, pk=worker_id)

    # Calculate the same data as process_payment, but read-only
    unpaid_logs = worker.work_logs.exclude(paid_in__worker=worker)
    log_count = unpaid_logs.count()
    logs_amount = float(log_count * worker.day_rate)

    pending_adjustments = worker.adjustments.filter(payroll_record__isnull=True)
    adj_list = []
    for adj in pending_adjustments:
        adj_list.append({
            'type': adj.get_type_display(),
            'description': adj.description or '',
            'amount': float(adj.amount),
            'is_deduction': adj.type in DEDUCTIVE_TYPES,
        })

    adj_amount = Decimal('0.00')
    for adj in pending_adjustments:
        if adj.type in ADDITIVE_TYPES:
            adj_amount += adj.amount
        elif adj.type in DEDUCTIVE_TYPES:
            adj_amount -= adj.amount

    total_amount = float(logs_amount + float(adj_amount))

    return JsonResponse({
        'worker_name': worker.name,
        'worker_id_no': worker.id_no or '',
        'date': timezone.now().date().strftime('%Y-%m-%d'),
        'day_rate': float(worker.day_rate),
        'days_worked': log_count,
        'base_pay': logs_amount,
        'adjustments': adj_list,
        'net_pay': total_amount,
    })


# === PRICE OVERTIME ===

@login_required
def price_overtime(request):
    """Create OVERTIME PayrollAdjustments from unpriced overtime logs."""
    if not is_admin(request.user):
        return redirect('payroll_dashboard')
        
    if request.method != 'POST':
        return redirect('payroll_dashboard')
        
    log_ids = request.POST.getlist('log_id')
    worker_ids = request.POST.getlist('worker_id')
    rate_pcts = request.POST.getlist('rate_pct')
    
    created = 0
    
    for log_id, worker_id, rate_pct in zip(log_ids, worker_ids, rate_pcts):
        try:
            worklog = WorkLog.objects.get(pk=log_id)
            worker = Worker.objects.get(pk=worker_id)
            rate = Decimal(rate_pct)
            
            # Formula: Day Rate * Overtime Fraction * (Rate % / 100)
            amount = worker.day_rate * worklog.overtime * (rate / Decimal('100'))
            
            if amount > 0:
                PayrollAdjustment.objects.create(
                    worker=worker,
                    type='OVERTIME',
                    amount=amount,
                    date=worklog.date,
                    description=f"Overtime: {worklog.get_overtime_display()} @ {rate_pct}% - {worklog.date.strftime('%d %b %Y')}",
                    work_log=worklog
                )
                created += 1
                
                # Updated: Use M2M field
                worklog.overtime_paid_to.add(worker)
                
        except (WorkLog.DoesNotExist, Worker.DoesNotExist, Exception):
            continue
            
    messages.success(request, f"Created {created} overtime adjustment(s).")
    return redirect('payroll_dashboard')


# === PAYSLIP DETAIL ===

@login_required
def payslip_detail(request, pk):
    """Show details of a payslip (Payment Record)."""
    if not is_admin(request.user):
        return redirect('log_attendance')

    record = get_object_or_404(PayrollRecord, pk=pk)
    
    # Get the logs included in this payment
    logs = record.work_logs.all().order_by('date')
    adjustments = record.adjustments.all().order_by('type')
    
    # Calculate base pay from logs (re-verify logic)
    # The record.amount is the final NET. 
    # We can reconstruct the display.
    base_pay = sum(w.day_rate for l in logs for w in l.workers.all() if w == record.worker)
    adjustments_net = record.amount - base_pay
    
    context = {
        'record': record,
        'logs': logs,
        'adjustments': adjustments,
        'base_pay': base_pay,
        'adjustments_net': adjustments_net,
    }
    return render(request, 'core/payslip.html', context)


# === LOANS ===

@login_required
def loan_list(request):
    """Redirect to payroll dashboard loans tab."""
    return redirect('/payroll/?status=loans')

@login_required
def add_loan(request):
    """Create a new loan."""
    if not is_admin(request.user):
        return redirect('log_attendance')

    if request.method == 'POST':
        worker_id = request.POST.get('worker')
        amount_str = request.POST.get('amount')
        reason = request.POST.get('reason')
        date = request.POST.get('date') or timezone.now().date()

        try:
            amount = Decimal(amount_str) if amount_str else None
        except Exception:
            messages.error(request, "Invalid amount.")
            return redirect('/payroll/?status=loans')

        if worker_id and amount and amount > 0:
            worker = get_object_or_404(Worker, pk=worker_id)
            Loan.objects.create(
                worker=worker,
                amount=amount,
                date=date,
                reason=reason
            )
            messages.success(request, f"Loan of R{amount} recorded for {worker.name}.")
        elif amount is not None and amount <= 0:
            messages.error(request, "Amount must be greater than zero.")

    return redirect('/payroll/?status=loans')


# === PAYROLL ADJUSTMENTS ===

@login_required
def add_adjustment(request):
    """Add a payroll adjustment (Bonus, Overtime, Deduction, Repayment) for one or more workers."""
    if not is_admin(request.user):
        return redirect('log_attendance')

    if request.method == 'POST':
        worker_ids = request.POST.getlist('workers')
        adj_type = request.POST.get('type')
        amount_str = request.POST.get('amount')
        description = request.POST.get('description')
        date = request.POST.get('date') or timezone.now().date()
        loan_id = request.POST.get('loan_id')  # Optional, for repayments

        try:
            amount = Decimal(amount_str) if amount_str else None
        except Exception:
            messages.error(request, "Invalid amount.")
            return redirect('payroll_dashboard')

        if amount is not None and amount <= 0:
            messages.error(request, "Amount must be greater than zero.")
            return redirect('payroll_dashboard')

        if worker_ids and amount and adj_type:
            success_names = []
            skip_names = []

            for worker_id in worker_ids:
                worker = get_object_or_404(Worker, pk=worker_id)

                # --- ADVANCE: validate, create adjustment + standalone PayrollRecord, send payslip ---
                if adj_type == 'ADVANCE':
                    advance_amount = amount

                    # Must have unpaid work logs
                    unpaid_logs = worker.work_logs.exclude(paid_in__worker=worker)
                    log_count = unpaid_logs.count()
                    if log_count == 0:
                        skip_names.append(f"{worker.name} (no unpaid work)")
                        continue

                    # Calculate max available (earned + existing adjustments)
                    logs_amount = log_count * worker.day_rate
                    existing_pending = worker.adjustments.filter(payroll_record__isnull=True)
                    existing_adj_total = Decimal('0.00')
                    for existing_adj in existing_pending:
                        if existing_adj.type in ADDITIVE_TYPES:
                            existing_adj_total += existing_adj.amount
                        elif existing_adj.type in DEDUCTIVE_TYPES:
                            existing_adj_total -= existing_adj.amount
                    max_available = logs_amount + existing_adj_total

                    if advance_amount > max_available:
                        skip_names.append(f"{worker.name} (R{advance_amount} exceeds available R{max_available:.2f})")
                        continue

                    # Create ADVANCE adjustment + PayrollRecord atomically
                    with transaction.atomic():
                        PayrollAdjustment.objects.create(
                            worker=worker,
                            type='ADVANCE',
                            amount=advance_amount,
                            description=description or 'Advance payment',
                            date=date,
                        )

                        advance_date = date if isinstance(date, datetime.date) else timezone.now().date()
                        advance_record = PayrollRecord.objects.create(
                            worker=worker,
                            amount=advance_amount,
                            date=advance_date,
                        )

                    # Send advance payslip to Spark (outside transaction)
                    subject = f"Advance Payment for {worker.name} - {advance_record.date}"
                    email_context = {
                        'record': advance_record,
                        'logs_count': 0,
                        'logs_amount': Decimal('0.00'),
                        'adjustments': [],
                        'is_advance': True,
                        'advance_amount': advance_amount,
                        'advance_description': description or 'Advance payment',
                    }
                    html_message = render_to_string('core/email/payslip_email.html', email_context)
                    plain_message = strip_tags(html_message)
                    pdf_content = render_to_pdf('core/pdf/payslip_pdf.html', email_context)

                    try:
                        email_obj = EmailMultiAlternatives(
                            subject,
                            plain_message,
                            settings.DEFAULT_FROM_EMAIL,
                            [settings.SPARK_RECEIPT_EMAIL],
                        )
                        email_obj.attach_alternative(html_message, "text/html")
                        if pdf_content:
                            email_obj.attach(
                                f"Advance_{worker.id}_{advance_record.date}.pdf",
                                pdf_content,
                                'application/pdf'
                            )
                        email_obj.send()
                    except Exception as e:
                        messages.warning(request, f"Advance recorded for {worker.name}, but email failed: {e}")

                    success_names.append(worker.name)
                    continue

                # Validation for repayment OR Creation for New Loan
                loan = None
                if adj_type == 'LOAN_REPAYMENT':
                    if loan_id:
                        loan = get_object_or_404(Loan, pk=loan_id)
                    else:
                        loan = worker.loans.filter(is_active=True).first()
                        if not loan:
                            skip_names.append(worker.name)
                            continue
                elif adj_type == 'LOAN':
                    loan = Loan.objects.create(
                        worker=worker,
                        amount=amount,
                        date=date,
                        reason=description
                    )

                PayrollAdjustment.objects.create(
                    worker=worker,
                    type=adj_type,
                    amount=amount,
                    description=description,
                    date=date,
                    loan=loan
                )
                success_names.append(worker.name)

            if success_names:
                names = ', '.join(success_names)
                if adj_type == 'ADVANCE':
                    messages.success(request, f"Advance of R{amount} processed and payslip sent for {names}.")
                else:
                    messages.success(request, f"{adj_type} of R{amount} added for {names}.")
            if skip_names:
                names = ', '.join(skip_names)
                messages.warning(request, f"Skipped: {names}.")

    return redirect('payroll_dashboard')

@login_required
def edit_adjustment(request, pk):
    """Edit an unpaid payroll adjustment. Admin only, POST only."""
    if not is_admin(request.user):
        return redirect('log_attendance')

    if request.method != 'POST':
        return redirect('payroll_dashboard')

    adj = get_object_or_404(PayrollAdjustment, pk=pk)

    # Only allow editing unpaid adjustments
    if adj.payroll_record is not None:
        messages.error(request, "Cannot edit a paid adjustment.")
        return redirect('payroll_dashboard')

    # Advance amounts cannot be edited — payslip was already sent
    if adj.type == 'ADVANCE':
        messages.warning(request, "Advance amounts cannot be edited after creation. Delete and re-create instead.")
        return redirect('payroll_dashboard')

    amount = request.POST.get('amount')
    description = request.POST.get('description')
    date = request.POST.get('date')
    new_type = request.POST.get('type')

    if amount:
        adj.amount = Decimal(amount)
    if description:
        adj.description = description
    if date:
        adj.date = date

    # Only allow type change for BONUS/DEDUCTION (others have linked objects)
    if new_type and adj.type in ('BONUS', 'DEDUCTION') and new_type in ('BONUS', 'DEDUCTION'):
        adj.type = new_type

    adj.save()

    # If LOAN type, sync the linked Loan object
    if adj.type == 'LOAN' and adj.loan:
        adj.loan.amount = adj.amount
        adj.loan.balance = adj.amount
        adj.loan.reason = adj.description
        adj.loan.save()

    messages.success(request, f"Updated {adj.get_type_display()} for {adj.worker.name}.")
    return redirect('payroll_dashboard')


@login_required
def delete_adjustment(request, pk):
    """Delete an unpaid payroll adjustment. Admin only, POST only."""
    if not is_admin(request.user):
        return redirect('log_attendance')

    if request.method != 'POST':
        return redirect('payroll_dashboard')

    adj = get_object_or_404(PayrollAdjustment, pk=pk)

    # Only allow deleting unpaid adjustments
    if adj.payroll_record is not None:
        messages.error(request, "Cannot delete a paid adjustment.")
        return redirect('payroll_dashboard')

    worker_name = adj.worker.name
    type_display = adj.get_type_display()

    if adj.type == 'LOAN' and adj.loan:
        loan = adj.loan
        # Check if any repayments for this loan have been paid
        paid_repayments = PayrollAdjustment.objects.filter(
            loan=loan, type='LOAN_REPAYMENT', payroll_record__isnull=False
        ).exists()
        if paid_repayments:
            messages.warning(request, f"Cannot delete loan for {worker_name} — it has paid repayments. Delete unpaid repayments manually first.")
            return redirect('payroll_dashboard')
        # Delete any unpaid repayment adjustments linked to this loan
        PayrollAdjustment.objects.filter(
            loan=loan, type='LOAN_REPAYMENT', payroll_record__isnull=True
        ).delete()
        # Delete the loan itself
        loan.delete()

    elif adj.type == 'OVERTIME' and adj.work_log:
        # Remove worker from overtime_paid_to so OT can be re-priced
        adj.work_log.overtime_paid_to.remove(adj.worker)

    adj.delete()
    messages.success(request, f"Deleted {type_display} for {worker_name}.")
    return redirect('payroll_dashboard')


# === EXPENSE RECEIPTS ===

@login_required
def create_receipt(request):
    """Create a new expense receipt and email it."""
    if not is_staff_or_supervisor(request.user):
        return redirect('log_attendance')

    if request.method == 'POST':
        form = ExpenseReceiptForm(request.POST)
        items = ExpenseLineItemFormSet(request.POST)
        
        if form.is_valid() and items.is_valid():
            receipt = form.save(commit=False)
            receipt.user = request.user
            receipt.save()
            
            items.instance = receipt
            line_items = items.save()
            
            # Backend Calculation for Consistency
            sum_amount = sum(item.amount for item in line_items)
            vat_type = receipt.vat_type
            
            if vat_type == 'INCLUDED':
                receipt.total_amount = sum_amount
                receipt.subtotal = sum_amount / Decimal('1.15')
                receipt.vat_amount = receipt.total_amount - receipt.subtotal
            elif vat_type == 'EXCLUDED':
                receipt.subtotal = sum_amount
                receipt.vat_amount = sum_amount * Decimal('0.15')
                receipt.total_amount = receipt.subtotal + receipt.vat_amount
            else: # NONE
                receipt.subtotal = sum_amount
                receipt.vat_amount = Decimal('0.00')
                receipt.total_amount = sum_amount
            
            receipt.save()
            
            # Email Generation
            subject = f"Receipt from {receipt.vendor} - {receipt.date}"
            recipient_list = [settings.SPARK_RECEIPT_EMAIL]
            
            # Prepare Context
            context = {
                'receipt': receipt,
                'items': line_items,
            }
            
            # 1. Render HTML Body
            html_message = render_to_string('core/email/receipt_email.html', context)
            plain_message = strip_tags(html_message)
            
            # 2. Render PDF Attachment
            pdf_content = render_to_pdf('core/pdf/receipt_pdf.html', context)
            
            try:
                # Construct Email with Attachment
                email = EmailMultiAlternatives(
                    subject,
                    plain_message,
                    settings.DEFAULT_FROM_EMAIL,
                    recipient_list,
                )
                email.attach_alternative(html_message, "text/html")
                
                if pdf_content:
                    email.attach(f"Receipt_{receipt.id}.pdf", pdf_content, 'application/pdf')
                
                email.send()
                messages.success(request, "Receipt created and sent to SparkReceipt.")
                return redirect('create_receipt')
            except Exception as e:
                messages.warning(request, f"Receipt saved, but email failed: {e}")
                
    else:
        form = ExpenseReceiptForm(initial={'date': timezone.now().date()})
        items = ExpenseLineItemFormSet()

    return render(request, 'core/create_receipt.html', {
        'form': form,
        'items': items
    })