"""
Complaints utility functions

Export and bulk operation utilities.
"""

import csv
import io
from datetime import datetime, timedelta
from typing import List

from django.db.models import Count, Q
from django.http import HttpResponse
from django.utils import timezone
from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
from openpyxl.utils import get_column_letter


def export_complaints_csv(queryset, filters=None):
    """
    Export complaints to CSV format.

    Args:
        queryset: Complaint queryset to export
        filters: Optional dict of applied filters

    Returns:
        HttpResponse with CSV file
    """
    response = HttpResponse(content_type="text/csv")
    response["Content-Disposition"] = (
        f'attachment; filename="complaints_{datetime.now().strftime("%Y%m%d_%H%M%S")}.csv"'
    )

    writer = csv.writer(response)

    # Write header
    writer.writerow(
        [
            "ID",
            "Title",
            "Patient Name",
            "Patient MRN",
            "Hospital",
            "Department",
            "Category",
            "Severity",
            "Priority",
            "Status",
            "Source",
            "Assigned To",
            "Created At",
            "Due At",
            "Is Overdue",
            "Resolved At",
            "Closed At",
            "Description",
        ]
    )

    # Write data
    for complaint in queryset:
        writer.writerow(
            [
                str(complaint.id)[:8],
                complaint.title,
                complaint.patient.get_full_name() if complaint.patient else "",
                complaint.patient.mrn if complaint.patient else "",
                complaint.hospital.name if complaint.hospital else "",
                complaint.department.name if complaint.department else "",
                str(complaint.category) if complaint.category else "",
                complaint.get_severity_display(),
                complaint.get_priority_display(),
                complaint.get_status_display(),
                complaint.get_complaint_source_type_display(),
                complaint.assigned_to.get_full_name() if complaint.assigned_to else "",
                complaint.created_at.strftime("%Y-%m-%d %H:%M:%S"),
                complaint.due_at.strftime("%Y-%m-%d %H:%M:%S") if complaint.due_at else "",
                "Yes" if complaint.is_overdue else "No",
                complaint.resolved_at.strftime("%Y-%m-%d %H:%M:%S") if complaint.resolved_at else "",
                complaint.closed_at.strftime("%Y-%m-%d %H:%M:%S") if complaint.closed_at else "",
                complaint.description[:500] if complaint.description else "",
            ]
        )

    return response


def export_complaints_excel(queryset, filters=None):
    """
    Export complaints to Excel format with formatting.

    Args:
        queryset: Complaint queryset to export
        filters: Optional dict of applied filters

    Returns:
        HttpResponse with Excel file
    """
    wb = Workbook()
    ws = wb.active
    ws.title = "Complaints"

    # Define styles
    header_font = Font(bold=True, color="FFFFFF")
    header_fill = PatternFill(start_color="4472C4", end_color="4472C4", fill_type="solid")
    header_alignment = Alignment(horizontal="center", vertical="center")

    # Write header
    headers = [
        "ID",
        "Title",
        "Patient Name",
        "Patient MRN",
        "Hospital",
        "Department",
        "Category",
        "Severity",
        "Priority",
        "Status",
        "Source",
        "Assigned To",
        "Created At",
        "Due At",
        "Is Overdue",
        "Resolved At",
        "Closed At",
        "Description",
    ]

    for col_num, header in enumerate(headers, 1):
        cell = ws.cell(row=1, column=col_num, value=header)
        cell.font = header_font
        cell.fill = header_fill
        cell.alignment = header_alignment

    # Write data
    for row_num, complaint in enumerate(queryset, 2):
        ws.cell(row=row_num, column=1, value=str(complaint.id)[:8])
        ws.cell(row=row_num, column=2, value=complaint.title)
        ws.cell(row=row_num, column=3, value=complaint.patient.get_full_name() if complaint.patient else "")
        ws.cell(row=row_num, column=4, value=complaint.patient.mrn if complaint.patient else "")
        ws.cell(row=row_num, column=5, value=complaint.hospital.name if complaint.hospital else "")
        ws.cell(row=row_num, column=6, value=complaint.department.name if complaint.department else "")
        ws.cell(row=row_num, column=7, value=str(complaint.category) if complaint.category else "")
        ws.cell(row=row_num, column=8, value=complaint.get_severity_display())
        ws.cell(row=row_num, column=9, value=complaint.get_priority_display())
        ws.cell(row=row_num, column=10, value=complaint.get_status_display())
        ws.cell(row=row_num, column=11, value=complaint.get_complaint_source_type_display())
        ws.cell(row=row_num, column=12, value=complaint.assigned_to.get_full_name() if complaint.assigned_to else "")
        ws.cell(row=row_num, column=13, value=complaint.created_at.strftime("%Y-%m-%d %H:%M:%S"))
        ws.cell(
            row=row_num, column=14, value=complaint.due_at.strftime("%Y-%m-%d %H:%M:%S") if complaint.due_at else ""
        )
        ws.cell(row=row_num, column=15, value="Yes" if complaint.is_overdue else "No")
        ws.cell(
            row=row_num,
            column=16,
            value=complaint.resolved_at.strftime("%Y-%m-%d %H:%M:%S") if complaint.resolved_at else "",
        )
        ws.cell(
            row=row_num,
            column=17,
            value=complaint.closed_at.strftime("%Y-%m-%d %H:%M:%S") if complaint.closed_at else "",
        )
        ws.cell(row=row_num, column=18, value=complaint.description[:500] if complaint.description else "")

    # Auto-adjust column widths
    for column in ws.columns:
        max_length = 0
        column_letter = column[0].column_letter
        for cell in column:
            try:
                if len(str(cell.value)) > max_length:
                    max_length = len(cell.value)
            except:
                pass
        adjusted_width = min(max_length + 2, 50)
        ws.column_dimensions[column_letter].width = adjusted_width

    # Save to response
    response = HttpResponse(content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
    response["Content-Disposition"] = (
        f'attachment; filename="complaints_{datetime.now().strftime("%Y%m%d_%H%M%S")}.xlsx"'
    )
    wb.save(response)

    return response


def bulk_assign_complaints(complaint_ids: List[str], user_id: str, current_user):
    """
    Bulk assign complaints to a user.

    Args:
        complaint_ids: List of complaint IDs
        user_id: ID of user to assign to
        current_user: User performing the action

    Returns:
        dict: Result with success count and errors
    """
    from apps.complaints.models import Complaint, ComplaintUpdate
    from apps.accounts.models import User
    from django.utils import timezone

    try:
        assignee = User.objects.get(id=user_id)
    except User.DoesNotExist:
        return {"success": False, "error": "User not found"}

    success_count = 0
    errors = []

    for complaint_id in complaint_ids:
        try:
            complaint = Complaint.objects.get(id=complaint_id)
            complaint.assigned_to = assignee
            complaint.assigned_at = timezone.now()
            complaint.save(update_fields=["assigned_to", "assigned_at"])

            # Create update
            ComplaintUpdate.objects.create(
                complaint=complaint,
                update_type="assignment",
                message=f"Bulk assigned to {assignee.get_full_name()}",
                created_by=current_user,
            )

            success_count += 1
        except Complaint.DoesNotExist:
            errors.append(f"Complaint {complaint_id} not found")
        except Exception as e:
            errors.append(f"Error assigning complaint {complaint_id}: {str(e)}")

    return {"success": True, "success_count": success_count, "total": len(complaint_ids), "errors": errors}


def bulk_change_status(complaint_ids: List[str], new_status: str, current_user, note: str = ""):
    """
    Bulk change status of complaints.

    Args:
        complaint_ids: List of complaint IDs
        new_status: New status to set
        current_user: User performing the action
        note: Optional note

    Returns:
        dict: Result with success count and errors
    """
    from apps.complaints.models import Complaint, ComplaintUpdate
    from django.utils import timezone

    success_count = 0
    errors = []

    for complaint_id in complaint_ids:
        try:
            complaint = Complaint.objects.get(id=complaint_id)
            old_status = complaint.status
            complaint.status = new_status

            # Handle status-specific logic
            if new_status == "resolved":
                complaint.resolved_at = timezone.now()
                complaint.resolved_by = current_user
            elif new_status == "closed":
                complaint.closed_at = timezone.now()
                complaint.closed_by = current_user

            complaint.save()

            # Create update
            ComplaintUpdate.objects.create(
                complaint=complaint,
                update_type="status_change",
                message=note or f"Bulk status change from {old_status} to {new_status}",
                created_by=current_user,
                old_status=old_status,
                new_status=new_status,
            )

            success_count += 1
        except Complaint.DoesNotExist:
            errors.append(f"Complaint {complaint_id} not found")
        except Exception as e:
            errors.append(f"Error changing status for complaint {complaint_id}: {str(e)}")

    return {"success": True, "success_count": success_count, "total": len(complaint_ids), "errors": errors}


def bulk_escalate_complaints(complaint_ids: List[str], current_user, reason: str = ""):
    """
    Bulk escalate complaints.

    Args:
        complaint_ids: List of complaint IDs
        current_user: User performing the action
        reason: Escalation reason

    Returns:
        dict: Result with success count and errors
    """
    from apps.complaints.models import Complaint, ComplaintUpdate
    from apps.complaints.services.complaint_service import ComplaintService
    from django.utils import timezone

    success_count = 0
    errors = []
    not_assigned = 0

    for complaint_id in complaint_ids:
        try:
            complaint = Complaint.objects.select_related("staff", "department", "hospital").get(id=complaint_id)
            complaint.escalated_at = timezone.now()

            target_user, fallback_path = ComplaintService.get_escalation_target(complaint, staff=complaint.staff)

            escalation_message = f"Bulk escalation. Reason: {reason or 'No reason provided'}"
            if target_user:
                complaint.assigned_to = target_user
                escalation_message += f" Escalated to: {target_user.get_full_name()} [via {fallback_path}]"
            else:
                not_assigned += 1
                escalation_message += " WARNING: No escalation target found."

            complaint.save(update_fields=["escalated_at", "assigned_to", "updated_at"])

            ComplaintUpdate.objects.create(
                complaint=complaint,
                update_type="escalation",
                message=escalation_message,
                created_by=current_user,
                metadata={
                    "reason": reason,
                    "escalated_to_user_id": str(target_user.id) if target_user else None,
                    "escalation_fallback_path": fallback_path,
                    "bulk": True,
                },
            )

            success_count += 1
        except Complaint.DoesNotExist:
            errors.append(f"Complaint {complaint_id} not found")
        except Exception as e:
            errors.append(f"Error escalating complaint {complaint_id}: {str(e)}")

    return {
        "success": True,
        "success_count": success_count,
        "total": len(complaint_ids),
        "errors": errors,
        "not_assigned": not_assigned,
    }


HEADER_FONT = Font(bold=True, color="FFFFFF", size=11)
HEADER_FILL = PatternFill(start_color="4472C4", end_color="4472C4", fill_type="solid")
HEADER_ALIGNMENT = Alignment(horizontal="center", vertical="center", wrap_text=True)
SECTION_FONT = Font(bold=True, size=11)
SECTION_FILL = PatternFill(start_color="D9E2F3", end_color="D9E2F3", fill_type="solid")
THIN_BORDER = Border(
    left=Side(style="thin"),
    right=Side(style="thin"),
    top=Side(style="thin"),
    bottom=Side(style="thin"),
)


def _write_header_row(ws, row, headers, font=HEADER_FONT, fill=HEADER_FILL, alignment=HEADER_ALIGNMENT):
    for col_num, header in enumerate(headers, 1):
        cell = ws.cell(row=row, column=col_num, value=header)
        cell.font = font
        cell.fill = fill
        cell.alignment = alignment
        cell.border = THIN_BORDER


def _auto_width(ws, max_width=40):
    for column in ws.columns:
        max_length = 0
        col_letter = get_column_letter(column[0].column)
        for cell in column:
            try:
                if cell.value:
                    max_length = max(max_length, len(str(cell.value)))
            except Exception:
                pass
        ws.column_dimensions[col_letter].width = min(max_length + 3, max_width)


def export_requests_report(queryset, year=None, month=None):
    """
    Step 0 — Requests Report Excel export.

    Generates a monthly Excel report matching the Step 0 template:
    - Timeline template sheet with 15 columns
    - Monthly data sheet with all request rows
    - Summary section with stats and staff breakdown
    """
    from apps.dashboard.models import ComplaintRequest

    wb = Workbook()

    ws_template = wb.active
    ws_template.title = "Time-Line Template"

    template_headers = [
        "#",
        "Request Date",
        "Patient Name",
        "File Number",
        "Complained Department",
        "Incident Date",
        "Entry (Staff)",
        "Time",
        "Form Sent Date",
        "Time",
        "Complaint Filed Date",
        "Time",
        "Time Between Send & File",
        "Non-Activation Reason",
        "PR Observations",
    ]
    _write_header_row(ws_template, 1, template_headers)
    for col in range(1, 16):
        ws_template.column_dimensions[get_column_letter(col)].width = 18

    qs = queryset.select_related("staff", "hospital", "complained_department", "complaint")

    month_label = f"{year}-{month:02d}" if year and month else datetime.now().strftime("%Y-%m")
    ws_data = wb.create_sheet(title=month_label)

    _write_header_row(ws_data, 1, template_headers)

    row_num = 2
    for idx, req in enumerate(qs, 1):
        filled_date = req.filled_at.date() if req.filled_at else ""
        filled_time = req.filled_at.strftime("%H:%M") if req.filled_at else ""
        form_sent_date = req.form_sent_at.date() if req.form_sent_at else ""
        form_sent_time = req.form_sent_time.strftime("%H:%M") if req.form_sent_time else ""

        ws_data.cell(row=row_num, column=1, value=idx)
        ws_data.cell(row=row_num, column=2, value=req.request_date)
        ws_data.cell(row=row_num, column=3, value=req.patient_name)
        ws_data.cell(row=row_num, column=4, value=req.file_number)
        ws_data.cell(row=row_num, column=5, value=req.complained_department.name if req.complained_department else "")
        ws_data.cell(row=row_num, column=6, value=req.incident_date or "")
        ws_data.cell(row=row_num, column=7, value=req.staff.get_full_name() if req.staff else "")
        ws_data.cell(row=row_num, column=8, value=req.request_time.strftime("%H:%M") if req.request_time else "")
        ws_data.cell(row=row_num, column=9, value=form_sent_date)
        ws_data.cell(row=row_num, column=10, value=form_sent_time)
        ws_data.cell(row=row_num, column=11, value=filled_date)
        ws_data.cell(row=row_num, column=12, value=filled_time)
        if req.form_sent_at and req.filled_at:
            delta = req.filled_at - req.form_sent_at
            ws_data.cell(row=row_num, column=13, value=str(delta))
        ws_data.cell(row=row_num, column=14, value=req.get_reason_non_activation_display() or "")
        ws_data.cell(row=row_num, column=15, value=req.pr_observations)
        for col in range(1, 16):
            ws_data.cell(row=row_num, column=col).border = THIN_BORDER
        row_num += 1

    summary_row = row_num + 2
    stats = qs.aggregate(
        on_hold_count=Count("pk", filter=Q(on_hold=True)),
        not_filled_count=Count("pk", filter=Q(not_filled=True)),
        filled_count=Count("pk", filter=Q(filled=True)),
        barcode_count=Count("pk", filter=Q(from_barcode=True)),
        same_time=Count("pk", filter=Q(filling_time_category="same_time")),
        within_6h=Count("pk", filter=Q(filling_time_category="within_6h")),
        six_to_24h=Count("pk", filter=Q(filling_time_category="6_to_24h")),
        after_1_day=Count("pk", filter=Q(filling_time_category="after_1_day")),
        not_mentioned=Count("pk", filter=Q(filling_time_category="not_mentioned")),
    )
    total = qs.count()

    summary_items = [
        ("Total Complaints on Hold", stats["on_hold_count"]),
        ("Total Not Filled", stats["not_filled_count"]),
        ("Total Filled", stats["filled_count"]),
        ("Total from Barcode (SELF)", stats["barcode_count"]),
        ("Filled at the same time", stats["same_time"]),
        ("Filled within 6 hours", stats["within_6h"]),
        ("Filled from 6 to 24 hours", stats["six_to_24h"]),
        ("Filled after 1 day", stats["after_1_day"]),
        ("Time not mentioned", stats["not_mentioned"]),
        ("TOTAL", total),
    ]
    ws_data.cell(row=summary_row, column=1, value="Summary").font = SECTION_FONT
    summary_row += 1
    for label, val in summary_items:
        ws_data.cell(row=summary_row, column=1, value=label).font = Font(bold=(label == "TOTAL"))
        ws_data.cell(row=summary_row, column=2, value=val)
        summary_row += 1

    summary_row += 1
    ws_data.cell(row=summary_row, column=1, value="Staff Breakdown").font = SECTION_FONT
    summary_row += 1
    _write_header_row(ws_data, summary_row, ["Staff", "Total", "Filled", "Not Filled"])
    summary_row += 1

    staff_stats = (
        qs.values("staff__first_name", "staff__last_name", "staff__id")
        .annotate(
            total=Count("pk"),
            filled=Count("pk", filter=Q(filled=True)),
            not_filled=Count("pk", filter=Q(not_filled=True)),
        )
        .order_by("-total")
    )

    for s in staff_stats:
        name = f"{s['staff__first_name'] or ''} {s['staff__last_name'] or ''}".strip() or "Unknown"
        ws_data.cell(row=summary_row, column=1, value=name)
        ws_data.cell(row=summary_row, column=2, value=s["total"])
        ws_data.cell(row=summary_row, column=3, value=s["filled"])
        ws_data.cell(row=summary_row, column=4, value=s["not_filled"])
        for col in range(1, 5):
            ws_data.cell(row=summary_row, column=col).border = THIN_BORDER
        summary_row += 1

    summary_row += 1
    ws_data.cell(row=summary_row, column=1, value="Non-Activation Reasons").font = SECTION_FONT
    summary_row += 1
    _write_header_row(ws_data, summary_row, ["Reason", "Count", "Percentage"])
    summary_row += 1

    reason_stats = (
        qs.exclude(reason_non_activation="")
        .values("reason_non_activation")
        .annotate(count=Count("pk"))
        .order_by("-count")
    )

    for r in reason_stats:
        display = dict(ComplaintRequest.NON_ACTIVATION_REASON_CHOICES).get(
            r["reason_non_activation"], r["reason_non_activation"]
        )
        pct = (r["count"] / total * 100) if total else 0
        ws_data.cell(row=summary_row, column=1, value=display)
        ws_data.cell(row=summary_row, column=2, value=r["count"])
        ws_data.cell(row=summary_row, column=3, value=f"{pct:.1f}%")
        for col in range(1, 4):
            ws_data.cell(row=summary_row, column=col).border = THIN_BORDER
        summary_row += 1

    _auto_width(ws_data, 50)

    response = HttpResponse(content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
    response["Content-Disposition"] = f'attachment; filename="requests_report_{month_label}.xlsx"'
    wb.save(response)
    return response


def export_monthly_calculations(queryset, year, month):
    """
    Step 1 — Monthly Calculations Excel export.

    Generates the full 108-column monthly calculations report
    matching the Step 1 template structure, including:
    - Main data sheet with all columns A-BN
    - Summary sections: weekly, source, location, department,
      sub-department, employee performance, activation delay
    """
    from apps.complaints.models import Complaint, ComplaintInvolvedDepartment

    wb = Workbook()
    ws = wb.active
    ws.title = month_label = f"{year}-{month:02d}"

    headers = [
        "Week",
        "No.",
        "Complaint ID",
        "File Number",
        "Source",
        "Location",
        "Main Department",
        "Sub-Department",
        "Date Received",
        "Entered By",
        "MOH Ref",
        "MOH Ref Date",
        "MOH Ref Time",
        "Phone Number",
        "Timeline SLA",
        "Form Sent Date",
        "Form Sent Time",
        "Employee (Form Sent)",
        "Complaint Filed Date",
        "Complaint Filed Time",
        "Activated",
        "Activation Date",
        "Activation Time",
        "Employee (Activation)",
        "Sent to Dept Date",
        "Sent to Dept Time",
        "Employee (Dept Send)",
        "Time: Filed to Sent",
        "1st Reminder Date",
        "1st Reminder Time",
        "Employee (1st Rem)",
        "Time: 1st Rem to Sent",
        "2nd Reminder Date",
        "2nd Reminder Time",
        "Employee (2nd Rem)",
        "Time: 2nd Rem to 1st Rem",
        "Escalated",
        "Escalation Date",
        "Escalation Time",
        "Employee (Escalation)",
        "Time: Escalation to Sent",
        "Closed",
        "Close Date",
        "Close Time",
        "Employee (Close)",
        "Time: Close to Sent",
        "Resolved",
        "Resolve Date",
        "Resolve Time",
        "Employee (Resolve)",
        "Time: Resolve to Sent",
        "Response Date",
        "Response Time",
        "Time: Response to Sent",
        "Complained Person Name",
        "Main Complaint Subject",
        "Summary (Arabic)",
        "Summary (English)",
        "Reminder Documentation",
        "Reminder Date",
        "Delay Reason (Dept)",
        "Delay Reason (Closure 72h)",
        "Person Responsible for Delay",
        "Satisfaction",
        "Action Taken by Dept",
        "Investigation Result",
        "Solutions & Suggestions",
        "Recommendation/Action Plan",
        "Responsible Department",
        "Rightful Side",
        "PR Observations",
    ]

    _write_header_row(ws, 1, headers)
    ws.freeze_panes = "D2"

    qs = queryset.select_related(
        "hospital",
        "department",
        "legacy_main_section",
        "legacy_subsection",
        "assigned_to",
        "resolved_by",
        "closed_by",
        "created_by",
        "source",
    ).prefetch_related("involved_departments__department", "updates")

    dept_cat_keywords = {
        "medical": [
            "doctor",
            "physician",
            "surgeon",
            "consultant",
            "specialist",
            "er",
            "emergency",
            "icu",
            "nicu",
            "pediatric",
            "ob/gyn",
            "obstetric",
            "gynecolog",
            "cardiology",
            "orthoped",
            "radiology",
            "dermatolog",
            "patholog",
            "lab",
            "laboratory",
            "pharmacy",
            "anesthesi",
            "nephrology",
            "urology",
            "dental",
            "dentist",
            "ophthalmol",
            "ent",
            "otorhinolaryng",
            "pulmonar",
            "respirator",
            "oncolog",
            "hematolog",
            "gastroenter",
            "endocrin",
            "neurolog",
            "psychiatry",
            "psychiatric",
            "internal medicine",
            "general surgery",
            "pediatrics",
            "neonat",
            "nutrition",
            "dietitian",
            "physiothera",
            "physical therapy",
            "rehab",
            "speech therap",
            "occupational",
            "medical report",
            "blood bank",
            "infection control",
        ],
        "admin": [
            "reception",
            "registration",
            "appointment",
            "approval",
            "insurance",
            "finance",
            "billing",
            "account",
            "hr",
            "human resource",
            "it ",
            "information technology",
            "medical record",
            "health information",
            "management",
            "admin",
            "security",
            "parking",
            "facility",
            "maintenance",
            "housekeep",
            "clean",
            "food",
            "kitchen",
            "cafeteria",
            "transport",
            "patient relation",
            "pr ",
            "public relation",
            "complaint",
            "quality",
            "risk",
            "credential",
            "medical approval",
            "pre-approval",
            "preapproval",
        ],
        "nursing": [
            "nurs",
            "nurse",
            "iv ",
            "injection",
            "medication admin",
            "wound care",
            "triage",
        ],
        "support": [
            "kitchen",
            "food service",
            "clean",
            "housekeep",
            "laundry",
            "security",
            "transport",
            "maintenance",
            "facility",
            "steriliz",
            "central supply",
        ],
    }

    def classify_department(dept_name):
        if not dept_name:
            return ""
        name_lower = dept_name.lower()
        for cat, keywords in dept_cat_keywords.items():
            for kw in keywords:
                if kw in name_lower:
                    return cat.title()
        return "Other"

    def get_timeline_sla(complaint):
        if not complaint.resolved_at or not complaint.activated_at:
            return "More than 72 hours"
        delta = complaint.resolved_at - complaint.activated_at
        hours = delta.total_seconds() / 3600
        if hours <= 24:
            return "24 Hours"
        elif hours <= 48:
            return "48 Hours"
        elif hours <= 72:
            return "72 Hours"
        return "More than 72 hours"

    def get_dept_primary(complaint):
        dept = complaint.involved_departments.filter(is_primary=True).first()
        if not dept:
            dept = complaint.involved_departments.first()
        return dept

    def fmt_date(dt):
        if not dt:
            return ""
        return dt.date() if hasattr(dt, "date") else dt

    def fmt_time(dt):
        if not dt:
            return ""
        return dt.strftime("%H:%M") if hasattr(dt, "strftime") else ""

    def time_diff(dt1, dt2):
        if dt1 and dt2:
            delta = dt1 - dt2
            return str(delta)
        return ""

    row_num = 2
    for idx, c in enumerate(qs, 1):
        cal = c.created_at
        week_of_month = (cal.day - 1) // 7 + 1

        dept_name = ""
        sub_dept_name = ""
        dept_inv = get_dept_primary(c)
        if dept_inv:
            dept_name = dept_inv.department.name if dept_inv.department else ""
            sub_dept_name = dept_name

        main_dept = classify_department(dept_name or (c.department.name if c.department else ""))

        source_display = ""
        if c.source:
            source_display = c.source.name if hasattr(c.source, "name") else str(c.source)
        if c.complaint_source_type == "internal":
            if c.source and c.source.code in ("moh", "chi"):
                pass
            else:
                source_display = "Patient"

        location_display = ""
        if c.legacy_main_section:
            location_display = c.legacy_main_section.name if hasattr(c.legacy_main_section, "name") else str(c.legacy_main_section)
        if c.legacy_location:
            loc_name = c.legacy_location.name if hasattr(c.legacy_location, "name") else str(c.legacy_location)
            if loc_name:
                location_display = f"{loc_name} - {location_display}" if location_display else loc_name

        entry_by = c.created_by.get_full_name() if c.created_by else ""

        activation_date = c.activated_at or c.assigned_at
        activation_employee = ""
        if c.assigned_to:
            activation_employee = c.assigned_to.get_full_name()

        forwarded_date = c.forwarded_to_dept_at
        forwarded_employee = ""
        if dept_inv and dept_inv.assigned_to:
            forwarded_employee = dept_inv.assigned_to.get_full_name()

        reminder1_date = c.reminder_sent_at
        reminder2_date = c.second_reminder_sent_at
        if dept_inv:
            if dept_inv.first_reminder_sent_at:
                reminder1_date = dept_inv.first_reminder_sent_at
            if dept_inv.second_reminder_sent_at:
                reminder2_date = dept_inv.second_reminder_sent_at

        escalated_date = c.escalated_at

        filed_date = cal
        time_filed_to_sent = time_diff(forwarded_date, filed_date)
        time_rem1_to_sent = time_diff(forwarded_date, reminder1_date) if reminder1_date and forwarded_date else ""
        time_rem2_to_rem1 = time_diff(reminder2_date, reminder1_date) if reminder2_date and reminder1_date else ""
        time_escal_to_sent = time_diff(forwarded_date, escalated_date) if escalated_date and forwarded_date else ""

        close_date = c.closed_at
        resolve_date = c.resolved_at

        summary_ar = c.short_description_ar
        summary_en = c.short_description_en

        complained_person = c.staff_name or ""

        delay_reason_dept = ""
        delayed_person = ""
        if dept_inv:
            delay_reason_dept = dept_inv.delay_reason
            delayed_person = dept_inv.delayed_person

        rightful_side = c.get_resolution_outcome_display() if c.resolution_outcome else ""

        row_data = [
            week_of_month,
            idx,
            c.reference_number or "",
            c.file_number or (c.patient.mrn if c.patient else ""),
            source_display,
            location_display,
            main_dept,
            sub_dept_name,
            fmt_date(cal),
            entry_by,
            c.moh_reference,
            fmt_date(c.moh_reference_date),
            "",
            c.contact_phone,
            get_timeline_sla(c),
            fmt_date(c.form_sent_at),
            fmt_time(c.form_sent_at),
            c.created_by.get_full_name() if c.created_by else "",
            fmt_date(filed_date),
            fmt_time(filed_date),
            "Yes" if activation_date else "No",
            fmt_date(activation_date),
            fmt_time(activation_date),
            activation_employee,
            fmt_date(forwarded_date),
            fmt_time(forwarded_date),
            forwarded_employee,
            time_filed_to_sent,
            fmt_date(reminder1_date),
            fmt_time(reminder1_date),
            "",
            time_rem1_to_sent,
            fmt_date(reminder2_date),
            fmt_time(reminder2_date),
            "",
            time_rem2_to_rem1,
            "Yes" if escalated_date else "No",
            fmt_date(escalated_date),
            fmt_time(escalated_date),
            "",
            time_escal_to_sent,
            "Yes" if close_date else "No",
            fmt_date(close_date),
            fmt_time(close_date),
            c.closed_by.get_full_name() if c.closed_by else "",
            time_diff(resolve_date, close_date) if resolve_date and close_date else "",
            "Yes" if resolve_date else "No",
            fmt_date(resolve_date),
            fmt_time(resolve_date),
            c.resolved_by.get_full_name() if c.resolved_by else "",
            time_diff(c.resolution_sent_at, resolve_date) if c.resolution_sent_at and resolve_date else "",
            fmt_date(c.response_date),
            "",
            time_diff(c.response_date, resolve_date) if c.response_date and resolve_date else "",
            complained_person,
            c.complaint_subject or c.title,
            summary_ar,
            summary_en,
            "",
            "",
            delay_reason_dept,
            c.delay_reason_closure,
            delayed_person,
            c.get_satisfaction_display() if c.satisfaction else "",
            c.action_taken_by_dept,
            c.action_result,
            c.recommendation_action_plan or "",
            c.recommendation_action_plan or "",
            dept_name or (c.department.name if c.department else ""),
            rightful_side,
            "",
        ]

        for col_num, val in enumerate(row_data, 1):
            cell = ws.cell(row=row_num, column=col_num, value=val)
            cell.border = THIN_BORDER
            cell.alignment = Alignment(vertical="center", wrap_text=True)
        row_num += 1

    summary_row = row_num + 2

    ws.cell(row=summary_row, column=1, value="Weekly Breakdown").font = SECTION_FONT
    summary_row += 1
    _write_header_row(ws, summary_row, ["Metric", "Week 1", "Week 2", "Week 3", "Week 4", "Week 5", "Total"])
    summary_row += 1
    for week in range(1, 6):
        start_day = (week - 1) * 7 + 1
        end_day = week * 7
        week_count = sum(
            1 for c in queryset if start_day <= c.created_at.day <= end_day and c.created_at.month == month
        )
        ws.cell(row=summary_row, column=week + 1, value=week_count)
    ws.cell(row=summary_row, column=7, value=queryset.count())
    summary_row += 2

    ws.cell(row=summary_row, column=1, value="Source Distribution").font = SECTION_FONT
    summary_row += 1
    _write_header_row(ws, summary_row, ["Source", "Count", "%"])
    summary_row += 1
    total = queryset.count()
    source_counts = {"MOH": 0, "CCHI": 0, "Patient": 0, "Patient's Relatives": 0, "Other": 0}
    for c in queryset:
        if c.source and c.source.code and c.source.code.upper() == "MOH":
            source_counts["MOH"] += 1
        elif c.source and c.source.code and c.source.code.upper() in ("CHI", "CCHI"):
            source_counts["CCHI"] += 1
        elif c.complaint_source_type == "internal":
            source_counts["Patient"] += 1
        else:
            source_counts["Other"] += 1
    for src, cnt in source_counts.items():
        if cnt > 0:
            pct = (cnt / total * 100) if total else 0
            ws.cell(row=summary_row, column=1, value=src)
            ws.cell(row=summary_row, column=2, value=cnt)
            ws.cell(row=summary_row, column=3, value=f"{pct:.1f}%")
            for col in range(1, 4):
                ws.cell(row=summary_row, column=col).border = THIN_BORDER
            summary_row += 1
    ws.cell(row=summary_row, column=1, value="Total").font = Font(bold=True)
    ws.cell(row=summary_row, column=2, value=total)
    summary_row += 2

    ws.cell(row=summary_row, column=1, value="Department Category Breakdown").font = SECTION_FONT
    summary_row += 1
    _write_header_row(ws, summary_row, ["Category", "Count", "%"])
    summary_row += 1
    dept_categories = {}
    for c in queryset:
        dept_name_raw = ""
        dept_inv = c.involved_departments.filter(is_primary=True).first() or c.involved_departments.first()
        if dept_inv and dept_inv.department:
            dept_name_raw = dept_inv.department.name
        elif c.department:
            dept_name_raw = c.department.name
        cat = classify_department(dept_name_raw) or "Other"
        dept_categories[cat] = dept_categories.get(cat, 0) + 1
    for cat, cnt in sorted(dept_categories.items(), key=lambda x: -x[1]):
        pct = (cnt / total * 100) if total else 0
        ws.cell(row=summary_row, column=1, value=cat)
        ws.cell(row=summary_row, column=2, value=cnt)
        ws.cell(row=summary_row, column=3, value=f"{pct:.1f}%")
        for col in range(1, 4):
            ws.cell(row=summary_row, column=col).border = THIN_BORDER
        summary_row += 1
    summary_row += 2

    ws.cell(row=summary_row, column=1, value="Employee Performance").font = SECTION_FONT
    summary_row += 1
    _write_header_row(ws, summary_row, ["Employee", "Count", "%", "24h", "48h", "72h", ">72h", "Total", "SELF"])
    summary_row += 1

    emp_stats = (
        queryset.values("created_by__first_name", "created_by__last_name")
        .annotate(
            total=Count("pk"),
        )
        .order_by("-total")
    )

    for emp in emp_stats:
        name = f"{emp['created_by__first_name'] or ''} {emp['created_by__last_name'] or ''}".strip() or "Unknown"
        emp_qs = queryset.filter(
            created_by__first_name=emp["created_by__first_name"], created_by__last_name=emp["created_by__last_name"]
        )
        h24 = sum(1 for c in emp_qs if c.resolved_at and c.activated_at and (c.resolved_at - c.activated_at).total_seconds() <= 86400)
        h48 = sum(
            1 for c in emp_qs if c.resolved_at and c.activated_at and 86400 < (c.resolved_at - c.activated_at).total_seconds() <= 172800
        )
        h72 = sum(
            1 for c in emp_qs if c.resolved_at and c.activated_at and 172800 < (c.resolved_at - c.activated_at).total_seconds() <= 259200
        )
        over72 = emp["total"] - h24 - h48 - h72
        self_count = sum(1 for c in emp_qs if c.created_by == c.assigned_to)
        pct = (emp["total"] / total * 100) if total else 0
        row_vals = [name, emp["total"], f"{pct:.1f}%", h24, h48, h72, max(0, over72), emp["total"], self_count]
        for col_num, val in enumerate(row_vals, 1):
            ws.cell(row=summary_row, column=col_num, value=val)
            ws.cell(row=summary_row, column=col_num).border = THIN_BORDER
        summary_row += 1

    response = HttpResponse(content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
    response["Content-Disposition"] = f'attachment; filename="monthly_calculations_{month_label}.xlsx"'
    wb.save(response)
    return response


def _build_quarterly_yearly_report(queryset, title, months_list, year=None):
    """
    Shared logic for quarterly and yearly calculations reports.

    Args:
        queryset: Complaint queryset (already filtered by date range)
        title: Sheet/report title (e.g., "Q1 2025" or "Yearly 2025")
        months_list: list of (month_num,) tuples for each month in the period
        year: year number (optional, for sheet naming)
    """
    from apps.complaints.models import Complaint, ComplaintInvolvedDepartment

    wb = Workbook()

    ws_kpi = wb.active
    ws_kpi.title = "1. KPI"

    dept_cat_keywords = {
        "medical": [
            "doctor",
            "physician",
            "surgeon",
            "consultant",
            "specialist",
            "er",
            "emergency",
            "icu",
            "nicu",
            "pediatric",
            "ob/gyn",
            "cardiology",
            "orthoped",
            "radiology",
            "dermatolog",
            "lab",
            "pharmacy",
            "anesthesi",
            "nephrology",
            "urology",
            "dental",
            "oncolog",
            "hematolog",
            "gastroenter",
            "endocrin",
            "neurolog",
            "psychiatry",
            "internal medicine",
            "general surgery",
            "pediatrics",
            "nutrition",
            "physiothera",
            "physical therapy",
            "rehab",
            "medical report",
            "blood bank",
            "infection control",
        ],
        "admin": [
            "reception",
            "appointment",
            "approval",
            "insurance",
            "finance",
            "billing",
            "hr",
            "medical record",
            "management",
            "admin",
            "security",
            "facility",
            "quality",
            "risk",
            "credential",
            "medical approval",
            "pre-approval",
            "preapproval",
            "it ",
        ],
        "nursing": ["nurs", "nurse", "iv ", "injection", "medication admin", "wound care", "triage"],
        "support": ["kitchen", "food service", "clean", "housekeep", "laundry", "steriliz", "central supply"],
    }

    def classify_department(dept_name):
        if not dept_name:
            return "Other"
        name_lower = dept_name.lower()
        for cat, keywords in dept_cat_keywords.items():
            for kw in keywords:
                if kw in name_lower:
                    return cat.title()
        return "Other"

    month_labels = [datetime.strptime(f"{year}-{m:02d}", "%Y-%m").strftime("%b") for _, m in months_list]

    complaints_list = list(
        queryset.select_related(
            "hospital",
            "department",
            "source",
            "created_by",
            "assigned_to",
            "resolved_by",
            "closed_by",
        ).prefetch_related("involved_departments__department")
    )

    month_complaints = {}
    for c in complaints_list:
        m_key = c.created_at.month
        if m_key not in month_complaints:
            month_complaints[m_key] = []
        month_complaints[m_key].append(c)

    def _month_qs(m):
        return month_complaints.get(m, [])

    row = 1
    ws_kpi.cell(row=row, column=1, value=title).font = Font(bold=True, size=14)
    row += 2

    def write_kpi_block(name, monthly_values, target=0.95, threshold=0.90):
        nonlocal row
        ws_kpi.cell(row=row, column=1, value=name).font = Font(bold=True, size=12)
        row += 1

        numerator_label_row = row
        ws_kpi.cell(row=row, column=1, value="Numerator")
        for i, (m_num, _) in enumerate(months_list):
            ws_kpi.cell(row=row, column=2 + i, value=monthly_values[i][0])
        total_num = sum(v[0] for v in monthly_values)
        ws_kpi.cell(row=row, column=2 + len(months_list), value=total_num)
        row += 1

        ws_kpi.cell(row=row, column=1, value="Denominator")
        for i, (m_num, _) in enumerate(months_list):
            ws_kpi.cell(row=row, column=2 + i, value=monthly_values[i][1])
        total_den = sum(v[1] for v in monthly_values)
        ws_kpi.cell(row=row, column=2 + len(months_list), value=total_den)
        row += 1

        ws_kpi.cell(row=row, column=1, value="Result (%)")
        for i, (m_num, _) in enumerate(months_list):
            pct = (monthly_values[i][0] / monthly_values[i][1] * 100) if monthly_values[i][1] else 0
            ws_kpi.cell(row=row, column=2 + i, value=f"{pct:.1f}%")
        total_pct = (total_num / total_den * 100) if total_den else 0
        ws_kpi.cell(row=row, column=2 + len(months_list), value=f"{total_pct:.1f}%")
        row += 2

    kpi1_data = []
    kpi2_data = []
    kpi4_data = []
    for m_num, _ in months_list:
        mc = _month_qs(m_num)
        total_m = len(mc)
        closed_m = sum(1 for c in mc if c.status in ("resolved", "closed"))
        kpi1_data.append((closed_m, total_m))

        resolved_72h = 0
        for c in mc:
            if c.resolved_at and c.activated_at:
                if (c.resolved_at - c.activated_at).total_seconds() <= 259200:
                    resolved_72h += 1
        kpi2_data.append((resolved_72h, total_m))

        resolved_48h = 0
        for c in mc:
            if c.resolved_at and c.activated_at:
                if (c.resolved_at - c.activated_at).total_seconds() <= 172800:
                    resolved_48h += 1
        kpi4_data.append((resolved_48h, total_m))

    write_kpi_block("KPI #1 — Closure Rate", kpi1_data)
    write_kpi_block("KPI #2 — Resolved Within 72 Hours", kpi2_data)
    write_kpi_block("KPI #4 — Responses Within 48 Hours", kpi4_data, target=0.50, threshold=0.40)

    kpi3_data = []
    for m_num, _ in months_list:
        mc = _month_qs(m_num)
        satisfied_m = sum(1 for c in mc if c.satisfaction == "satisfied")
        total_surveyed_m = sum(1 for c in mc if c.satisfaction in ("satisfied", "neutral", "dissatisfied"))
        kpi3_data.append((satisfied_m, total_surveyed_m))

    write_kpi_block("KPI #3 — Satisfaction Rate", kpi3_data, target=0.80, threshold=0.70)

    response_rate_call_data = []
    response_rate_survey_data = []
    for m_num, _ in months_list:
        mc = _month_qs(m_num)
        total_m = len(mc)
        responded_call = sum(1 for c in mc if c.satisfaction in ("satisfied", "neutral", "dissatisfied"))
        responded_survey = sum(1 for c in mc if c.resolution_survey_id is not None)
        response_rate_call_data.append((responded_call, total_m))
        response_rate_survey_data.append((responded_survey, total_m))

    write_kpi_block("Response Rate — Calls", response_rate_call_data, target=0.80, threshold=0.70)
    write_kpi_block("Response Rate — Survey", response_rate_survey_data, target=0.80, threshold=0.70)

    reopen_data = []
    for m_num, _ in months_list:
        mc = _month_qs(m_num)
        total_m = len(mc)
        reopened_m = sum(1 for c in mc if c.reopened_from_id is not None)
        reopen_data.append((reopened_m, total_m))
    write_kpi_block("Reopen Rate", reopen_data, target=0.10, threshold=0.20)

    reassign_data = []
    ComplaintUpdate = apps.get_model("complaints", "ComplaintUpdate")
    for m_num, _ in months_list:
        mc_ids = list(mc.values_list("id", flat=True)) if hasattr(mc, 'values_list') else []
        total_m = len(mc_ids) if mc_ids else len(_month_qs(m_num))
        if mc_ids:
            mc_qs = _month_qs(m_num)
        else:
            mc_qs = _month_qs(m_num)
        reassigned_m = ComplaintUpdate.objects.filter(
            complaint__in=mc_qs,
            update_type="assignment",
            created_at__month=m_num,
        ).count()
        reassign_data.append((reassigned_m, total_m))
    write_kpi_block("Reassign Count", reassign_data)

    for col in range(1, 3 + len(months_list)):
        ws_kpi.column_dimensions[get_column_letter(col)].width = 16

    ws_table = wb.create_sheet("2. First Table")
    row = 1
    ws_table.cell(row=row, column=1, value=f"{title} — Distribution Analysis").font = Font(bold=True, size=14)
    row += 2

    external = 0
    internal = 0
    source_breakdown = {"MOH": 0, "CCHI": 0, "Insurance": 0, "Internal": 0}
    location_breakdown = {"Inpatient": 0, "Outpatient": 0, "Emergency": 0}
    category_breakdown = {}

    for c in complaints_list:
        is_external = False
        if c.source and c.source.code and c.source.code.upper() == "MOH":
            source_breakdown["MOH"] += 1
            external += 1
            is_external = True
        elif c.source and c.source.code and c.source.code.upper() in ("CHI", "CCHI"):
            source_breakdown["CCHI"] += 1
            external += 1
            is_external = True
        else:
            source_breakdown["Internal"] += 1
            internal += 1

        loc = "Other"
        if c.legacy_main_section:
            loc_name = c.legacy_main_section.name.lower() if hasattr(c.legacy_main_section, "name") else str(c.legacy_main_section).lower()
            if "inpatient" in loc_name or "ip" in loc_name:
                loc = "Inpatient"
            elif "outpatient" in loc_name or "op" in loc_name or "clinic" in loc_name:
                loc = "Outpatient"
            elif "er" in loc_name or "emergency" in loc_name:
                loc = "Emergency"
        location_breakdown[loc] = location_breakdown.get(loc, 0) + 1

        dept_inv = c.involved_departments.filter(is_primary=True).first() or c.involved_departments.first()
        dept_name = ""
        if dept_inv and dept_inv.department:
            dept_name = dept_inv.department.name
        elif c.department:
            dept_name = c.department.name
        cat = classify_department(dept_name)
        category_breakdown[cat] = category_breakdown.get(cat, 0) + 1

    total = len(complaints_list)

    ws_table.cell(row=row, column=1, value="External vs Internal").font = SECTION_FONT
    row += 1
    _write_header_row(ws_table, row, ["Category", "Count", "%"])
    row += 1
    for label, count in [("External (MOH/CCHI/Insurance)", external), ("Internal (Patients/Relatives)", internal)]:
        pct = (count / total * 100) if total else 0
        ws_table.cell(row=row, column=1, value=label)
        ws_table.cell(row=row, column=2, value=count)
        ws_table.cell(row=row, column=3, value=f"{pct:.1f}%")
        for col in range(1, 4):
            ws_table.cell(row=row, column=col).border = THIN_BORDER
        row += 1
    ws_table.cell(row=row, column=1, value="Total").font = Font(bold=True)
    ws_table.cell(row=row, column=2, value=total).font = Font(bold=True)
    row += 2

    ws_table.cell(row=row, column=1, value="By Source").font = SECTION_FONT
    row += 1
    _write_header_row(ws_table, row, ["Source", "Count", "%"])
    row += 1
    for src, cnt in sorted(source_breakdown.items(), key=lambda x: -x[1]):
        pct = (cnt / total * 100) if total else 0
        ws_table.cell(row=row, column=1, value=src)
        ws_table.cell(row=row, column=2, value=cnt)
        ws_table.cell(row=row, column=3, value=f"{pct:.1f}%")
        for col in range(1, 4):
            ws_table.cell(row=row, column=col).border = THIN_BORDER
        row += 1
    row += 2

    ws_table.cell(row=row, column=1, value="By Location").font = SECTION_FONT
    row += 1
    _write_header_row(ws_table, row, ["Location", "Count", "%"])
    row += 1
    for loc, cnt in sorted(location_breakdown.items(), key=lambda x: -x[1]):
        pct = (cnt / total * 100) if total else 0
        ws_table.cell(row=row, column=1, value=loc)
        ws_table.cell(row=row, column=2, value=cnt)
        ws_table.cell(row=row, column=3, value=f"{pct:.1f}%")
        for col in range(1, 4):
            ws_table.cell(row=row, column=col).border = THIN_BORDER
        row += 1
    row += 2

    ws_table.cell(row=row, column=1, value="By Department Category").font = SECTION_FONT
    row += 1
    _write_header_row(ws_table, row, ["Category", "Count", "%"])
    row += 1
    for cat, cnt in sorted(category_breakdown.items(), key=lambda x: -x[1]):
        pct = (cnt / total * 100) if total else 0
        ws_table.cell(row=row, column=1, value=cat)
        ws_table.cell(row=row, column=2, value=cnt)
        ws_table.cell(row=row, column=3, value=f"{pct:.1f}%")
        for col in range(1, 4):
            ws_table.cell(row=row, column=col).border = THIN_BORDER
        row += 1

    for col in range(1, 5):
        ws_table.column_dimensions[get_column_letter(col)].width = 35

    ws_escalated = wb.create_sheet("3. Escalated Complaints")
    row = 1
    ws_escalated.cell(row=row, column=1, value=f"{title} — Escalated Complaints by Department").font = Font(
        bold=True, size=14
    )
    row += 2

    escalated_by_dept = {}
    escalated_by_cat = {}
    for c in complaints_list:
        if c.escalated_at:
            dept_inv = c.involved_departments.filter(is_primary=True).first() or c.involved_departments.first()
            dept_name = ""
            if dept_inv and dept_inv.department:
                dept_name = dept_inv.department.name
            elif c.department:
                dept_name = c.department.name
            if dept_name:
                escalated_by_dept[dept_name] = escalated_by_dept.get(dept_name, 0) + 1
            cat = classify_department(dept_name)
            if cat not in escalated_by_cat:
                escalated_by_cat[cat] = {"escalated": 0, "total": 0}
            escalated_by_cat[cat]["escalated"] += 1

    for c in complaints_list:
        dept_inv = c.involved_departments.filter(is_primary=True).first() or c.involved_departments.first()
        dept_name = ""
        if dept_inv and dept_inv.department:
            dept_name = dept_inv.department.name
        elif c.department:
            dept_name = c.department.name
        cat = classify_department(dept_name)
        if cat not in escalated_by_cat:
            escalated_by_cat[cat] = {"escalated": 0, "total": 0}
        escalated_by_cat[cat]["total"] += 1

    _write_header_row(ws_escalated, row, ["Department Category", "Escalated", "Total", "Escalation Rate"])
    row += 1
    for cat, data in sorted(escalated_by_cat.items()):
        rate = (data["escalated"] / data["total"] * 100) if data["total"] else 0
        ws_escalated.cell(row=row, column=1, value=cat)
        ws_escalated.cell(row=row, column=2, value=data["escalated"])
        ws_escalated.cell(row=row, column=3, value=data["total"])
        ws_escalated.cell(row=row, column=4, value=f"{rate:.1f}%")
        for col in range(1, 5):
            ws_escalated.cell(row=row, column=col).border = THIN_BORDER
        row += 1

    row += 2
    ws_escalated.cell(row=row, column=1, value="Escalated by Specific Department").font = SECTION_FONT
    row += 1
    _write_header_row(ws_escalated, row, ["Department", "Escalated Count"])
    row += 1
    for dept, cnt in sorted(escalated_by_dept.items(), key=lambda x: -x[1]):
        ws_escalated.cell(row=row, column=1, value=dept)
        ws_escalated.cell(row=row, column=2, value=cnt)
        for col in range(1, 3):
            ws_escalated.cell(row=row, column=col).border = THIN_BORDER
        row += 1

    for col in range(1, 5):
        ws_escalated.column_dimensions[get_column_letter(col)].width = 30

    response_rate_sheets = [
        ("5. Internal Response Rate", "internal"),
        ("6. CHI Response Rate", "chi"),
        ("7. MOH Response Rate", "moh"),
    ]
    for sheet_name, source_filter in response_rate_sheets:
        ws_rr = wb.create_sheet(sheet_name)
        row = 1
        ws_rr.cell(row=row, column=1, value=f"{title} — {sheet_name}").font = Font(bold=True, size=14)
        row += 2

        if source_filter == "internal":
            filtered = [c for c in complaints_list if not (c.source and c.source.code and c.source.code.upper() in ("MOH", "CHI", "CCHI"))]
        elif source_filter == "chi":
            filtered = [c for c in complaints_list if c.source and c.source.code and c.source.code.upper() in ("CHI", "CCHI")]
        else:
            filtered = [c for c in complaints_list if c.source and c.source.code and c.source.code.upper() == "MOH"]

        buckets = {"24 Hours": 0, "48 Hours": 0, "72 Hours": 0, "More than 72 Hours": 0}
        for c in filtered:
            if c.resolved_at and c.activated_at:
                hours = (c.resolved_at - c.activated_at).total_seconds() / 3600
                if hours <= 24:
                    buckets["24 Hours"] += 1
                elif hours <= 48:
                    buckets["48 Hours"] += 1
                elif hours <= 72:
                    buckets["72 Hours"] += 1
                else:
                    buckets["More than 72 Hours"] += 1
            else:
                buckets["More than 72 Hours"] += 1

        total_filtered = len(filtered)
        _write_header_row(ws_rr, row, ["Timeline", "Count", "%"])
        row += 1
        for label, cnt in buckets.items():
            pct = (cnt / total_filtered * 100) if total_filtered else 0
            ws_rr.cell(row=row, column=1, value=label)
            ws_rr.cell(row=row, column=2, value=cnt)
            ws_rr.cell(row=row, column=3, value=f"{pct:.1f}%")
            for col in range(1, 4):
                ws_rr.cell(row=row, column=col).border = THIN_BORDER
            row += 1
        ws_rr.cell(row=row, column=1, value="Total").font = Font(bold=True)
        ws_rr.cell(row=row, column=2, value=total_filtered).font = Font(bold=True)

        for col in range(1, 4):
            ws_rr.column_dimensions[get_column_letter(col)].width = 25

    return wb


def export_quarterly_calculations(queryset, year, quarter):
    """
    Step 2 — Quarterly Calculations Excel export.

    Args:
        queryset: Complaint queryset filtered to the quarter
        year: int
        quarter: int (1-4)
    """
    quarter_months = {
        1: [(1, "Jan"), (2, "Feb"), (3, "Mar")],
        2: [(4, "Apr"), (5, "May"), (6, "Jun")],
        3: [(7, "Jul"), (8, "Aug"), (9, "Sep")],
        4: [(10, "Oct"), (11, "Nov"), (12, "Dec")],
    }
    months = quarter_months[quarter]
    title = f"Q{quarter} {year}"

    wb = _build_quarterly_yearly_report(queryset, title, months, year=year)

    response = HttpResponse(content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
    response["Content-Disposition"] = f'attachment; filename="quarterly_calculations_Q{quarter}_{year}.xlsx"'
    wb.save(response)
    return response


def export_yearly_calculations(queryset, year):
    """
    Yearly Calculations Excel export.

    Args:
        queryset: Complaint queryset filtered to the year
        year: int
    """
    months = [(m, datetime.strptime(f"{m}", "%m").strftime("%b")) for m in range(1, 13)]
    title = f"Yearly {year}"

    wb = _build_quarterly_yearly_report(queryset, title, months, year=year)

    response = HttpResponse(content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
    response["Content-Disposition"] = f'attachment; filename="yearly_calculations_{year}.xlsx"'
    wb.save(response)
    return response


def _fmt_date(dt):
    if dt is None:
        return None
    dt = dt.replace(tzinfo=None) if hasattr(dt, "replace") and getattr(dt, "tzinfo", None) else dt
    return dt.date() if hasattr(dt, "date") else dt


def _fmt_time(dt):
    if dt is None:
        return None
    if hasattr(dt, "strftime"):
        return dt.strftime("%H:%M")
    return None


def _strip_tz(dt):
    if dt is None:
        return None
    if hasattr(dt, "tzinfo") and dt.tzinfo is not None:
        return dt.replace(tzinfo=None)
    return dt


def _fmt_duration(td):
    if td is None:
        return None
    if isinstance(td, timedelta):
        total_sec = int(td.total_seconds())
        h, rem = divmod(total_sec, 3600)
        m, s = divmod(rem, 60)
        return f"{h:02d}:{m:02d}:{s:02d}"
    return td


_INCOMING_MONTHS = [
    "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JULY", "AUG",
    "SEP", "OCT", "NOV", "Dec",
]


def export_inquiries_report(queryset, year, month, is_outgoing=False):
    """
    Reports — Incoming/Outgoing Inquiries Excel export.

    Layout matches the production Excel templates exactly:
    - Row 1: merged group headers (Portal / Time Line / PDF / رأيك الشخصي)
    - Row 2: Arabic sub-headers with merged timeline groups
    - Data rows starting at row 3
    - Calculations sheet with per-employee/per-department breakdowns
    - Drop-down sheet with timeline options, employee list, status options
    """
    wb = Workbook()
    month_idx = month - 1
    month_label = f"{_INCOMING_MONTHS[month_idx]}-{year % 100}"
    ws = wb.active
    ws.title = month_label

    CENTER = Alignment(horizontal="center", vertical="center")
    RIGHT_CENTER = Alignment(horizontal="right", vertical="center")
    WRAP_CENTER = Alignment(horizontal="center", vertical="center", wrap_text=True)

    R1_FONT = Font(name="Aptos Narrow", bold=True, size=14)
    R1_FONT_LG = Font(name="Aptos Narrow", bold=True, size=16)
    R1_FONT_XL = Font(name="Aptos Narrow", bold=True, size=18)
    R2_FONT = Font(bold=True)
    DATA_FONT = Font(name="Calibri", size=11)

    PORTAL_FILL = PatternFill("solid", fgColor="FFC7CE")
    PORTAL_FONT = Font(name="Aptos Narrow", bold=True, size=14, color="9C0006")
    TIMELINE_FILL = PatternFill("solid", fgColor="FFFFCC")
    TIMELINE_FONT = Font(name="Aptos Narrow", bold=True, size=16)
    PDF_FILL = PatternFill("solid", fgColor="C6EFCE")
    PDF_FONT = Font(name="Aptos Narrow", bold=True, size=18, color="006100")
    OPINION_FILL = PatternFill("solid", fgColor="FFCC99")
    OPINION_FONT = Font(name="Aptos Narrow", bold=True, size=18, color="3F3F76")
    R2_FILL = PatternFill("solid", fgColor="4472C4")
    R2_FONT_WHITE = Font(bold=True, color="FFFFFF", size=11)
    THIN = Border(
        left=Side("thin"), right=Side("thin"),
        top=Side("thin"), bottom=Side("thin"),
    )

    if is_outgoing:
        _build_outgoing_sheet(
            ws, queryset, year, month,
            CENTER, RIGHT_CENTER, WRAP_CENTER,
            PORTAL_FILL, PORTAL_FONT, TIMELINE_FILL, TIMELINE_FONT,
            PDF_FILL, PDF_FONT, OPINION_FILL, OPINION_FONT,
            R2_FILL, R2_FONT_WHITE, THIN, DATA_FONT,
        )
        _build_outgoing_calculations(wb, queryset, year, month)
        _build_outgoing_dropdown(wb, queryset)
    else:
        _build_incoming_sheet(
            ws, queryset, year, month,
            CENTER, RIGHT_CENTER, WRAP_CENTER,
            PORTAL_FILL, PORTAL_FONT, TIMELINE_FILL, TIMELINE_FONT,
            PDF_FILL, PDF_FONT, OPINION_FILL, OPINION_FONT,
            R2_FILL, R2_FONT_WHITE, THIN, DATA_FONT,
        )
        _build_incoming_calculations(wb, queryset, year, month)
        _build_incoming_dropdown(wb, queryset)

    prefix = "Outgoing" if is_outgoing else "Incoming"
    file_label = f"{year}-{month:02d}"
    response = HttpResponse(content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
    response["Content-Disposition"] = f'attachment; filename="{prefix.lower()}_inquiries_{file_label}.xlsx"'
    wb.save(response)
    return response


def _build_incoming_sheet(
    ws, queryset, year, month,
    center, right_center, wrap_center,
    portal_fill, portal_font, timeline_fill, timeline_font,
    pdf_fill, pdf_font, opinion_fill, opinion_font,
    r2_fill, r2_font, border, data_font,
):
    ws.merge_cells("A1:B1")
    ws.merge_cells("C1:F1")
    ws.merge_cells("G1:V1")
    ws.merge_cells("X1:Y1")
    ws.merge_cells("Z1:AA1")

    for col, val, fill, font in [
        (3, "Portal", portal_fill, portal_font),
        (7, "Time Line", timeline_fill, timeline_font),
        (24, "PDF", pdf_fill, pdf_font),
        (26, "رأيك الشخصي", opinion_fill, opinion_font),
    ]:
        c = ws.cell(row=1, column=col, value=val)
        c.font = font
        c.fill = fill
        c.alignment = center

    ws.merge_cells("K2:N2")
    ws.merge_cells("O2:R2")
    ws.merge_cells("S2:V2")

    r2_headers = {
        1: "week", 2: "No.", 3: "التاريخ", 4: "اسم المراجع",
        5: "رقم الجوال", 6: "القسم المرسل", 7: "ID NO.",
        8: "Time line", 9: "تاريخ الاستفسار", 10: "وقت الاستفسار",
        11: "تم التواصل ولم يتم الرد",
        15: "تحت الإجراء",
        19: "تم التواصل ",
        24: "الاستفسار (من الPDF)", 25: "حالة الاستفسار",
        26: "ملاحظات الموظف", 27: "ملاحظات المشرف",
    }
    for col, val in r2_headers.items():
        c = ws.cell(row=2, column=col, value=val)
        c.font = r2_font
        c.fill = r2_fill
        c.alignment = wrap_center
        c.border = border

    for col in range(1, 28):
        ws.cell(row=2, column=col).border = border
        if col not in r2_headers:
            c = ws.cell(row=2, column=col)
            c.fill = r2_fill
            c.border = border

    ws.freeze_panes = "C3"

    qs = queryset.select_related(
        "hospital", "department", "assigned_to", "created_by",
        "outgoing_department", "responded_by",
        "contacted_nr_by", "under_process_by", "contacted_by",
        "source",
    ).order_by("created_at")

    first_half_end = 15
    row_num = 3
    first_half_start_row = 3
    second_half_start_row = None
    idx = 0

    for inq in qs:
        idx += 1
        week_of_month = (inq.created_at.day - 1) // first_half_end + 1
        is_first_half = inq.created_at.day <= first_half_end

        if is_first_half:
            week_label = "1st half of month, 1 to 15"
        else:
            week_label = "2nd half of month, 16 to end"
            if second_half_start_row is None:
                second_half_start_row = row_num

        dept_name = inq.source.name_ar if inq.source else ""
        emp_id = inq.created_by.employee_id if inq.created_by and inq.created_by.employee_id else ""

        timeline = inq.get_timeline_sla_display() if inq.timeline_sla else ""
        if not timeline and inq.due_at and inq.created_at:
            hours = (inq.due_at - inq.created_at).total_seconds() / 3600
            if hours <= 24:
                timeline = "24 Hours"
            elif hours <= 48:
                timeline = "48 Hours"
            elif hours <= 72:
                timeline = "72 Hours"
            else:
                timeline = "More than 72 hours"

        row_data = [None] * 27
        row_data[0] = week_label
        row_data[1] = idx
        row_data[2] = _strip_tz(inq.created_at)
        row_data[3] = inq.contact_name or ""
        row_data[4] = inq.contact_phone or ""
        row_data[5] = dept_name
        row_data[6] = emp_id
        row_data[7] = timeline
        row_data[8] = _fmt_date(inq.created_at)
        row_data[9] = _fmt_time(inq.created_at)

        if inq.contacted_nr_at:
            row_data[10] = _fmt_date(inq.contacted_nr_at)
            row_data[11] = inq.contacted_nr_time or _fmt_time(inq.contacted_nr_at)
            row_data[12] = inq.contacted_nr_by.get_full_name() if inq.contacted_nr_by else ""
            row_data[13] = _fmt_duration(inq.contacted_nr_duration)

        if inq.under_process_at:
            row_data[14] = _fmt_date(inq.under_process_at)
            row_data[15] = inq.under_process_time or _fmt_time(inq.under_process_at)
            row_data[16] = inq.under_process_by.get_full_name() if inq.under_process_by else ""
            row_data[17] = _fmt_duration(inq.under_process_duration)

        if inq.contacted_at:
            row_data[18] = _fmt_date(inq.contacted_at)
            row_data[19] = inq.contacted_time or _fmt_time(inq.contacted_at)
            row_data[20] = inq.contacted_by.get_full_name() if inq.contacted_by else ""
            row_data[21] = _fmt_duration(inq.contacted_duration)

        row_data[22] = None
        row_data[23] = inq.subject
        status_ar = {
            "open": "جديد", "in_progress": "تحت الإجراء",
            "resolved": "تم التواصل", "closed": "تم التواصل",
            "contacted": "تم التواصل",
            "contacted_no_response": "تم التواصل ولم يتم الرد",
        }
        row_data[24] = status_ar.get(inq.status, inq.get_status_display())
        row_data[25] = inq.staff_notes
        row_data[26] = inq.supervisor_notes

        for col_idx, val in enumerate(row_data):
            c = ws.cell(row=row_num, column=col_idx + 1, value=val)
            c.font = data_font
            c.border = border
            if col_idx in (2, 8):
                c.number_format = "YYYY-MM-DD"
            elif col_idx in (9, 11, 15, 19):
                c.number_format = "HH:MM"
            elif col_idx in (10, 14, 18):
                c.number_format = "YYYY-MM-DD"

        row_num += 1

    last_data_row = row_num - 1

    if first_half_start_row and first_half_start_row <= last_data_row:
        first_half_end_row = (second_half_start_row or (last_data_row + 1)) - 1
        if first_half_start_row < first_half_end_row:
            ws.merge_cells(
                start_row=first_half_start_row, start_column=1,
                end_row=first_half_end_row, end_column=1,
            )
    if second_half_start_row and second_half_start_row <= last_data_row:
        if second_half_start_row < last_data_row:
            ws.merge_cells(
                start_row=second_half_start_row, start_column=1,
                end_row=last_data_row, end_column=1,
            )

    _set_incoming_widths(ws)
    ws.sheet_view.rightToLeft = True


def _set_incoming_widths(ws):
    widths = {
        "A": 9.13, "B": 8.75, "C": 15.13, "D": 24.25,
        "E": 20.38, "F": 11.38, "G": 13.38, "H": 16.63,
        "I": 26.5, "J": 16.63, "K": 23.0, "L": 20.38,
        "M": 20.63, "N": 10.75, "O": 23.25, "P": 16.88,
        "Q": 26.5, "R": 15.38, "S": 23.5, "T": 16.88,
        "U": 18.63, "V": 18.25, "W": 0.25, "X": 25.5,
        "Y": 22.88, "Z": 57.88, "AA": 14.75,
    }
    for col_letter, w in widths.items():
        ws.column_dimensions[col_letter].width = w


def _build_outgoing_sheet(
    ws, queryset, year, month,
    center, right_center, wrap_center,
    portal_fill, portal_font, timeline_fill, timeline_font,
    pdf_fill, pdf_font, opinion_fill, opinion_font,
    r2_fill, r2_font, border, data_font,
):
    ws.merge_cells("A1:B1")
    ws.merge_cells("C1:G1")
    ws.merge_cells("H1:U1")
    ws.merge_cells("V1:W1")
    ws.merge_cells("X1:Y1")

    for col, val, fill, font in [
        (3, "Portal", portal_fill, portal_font),
        (8, "Time Line", timeline_fill, timeline_font),
        (22, "PDF", pdf_fill, pdf_font),
        (24, "رأيك الشخصي", opinion_fill, opinion_font),
    ]:
        c = ws.cell(row=1, column=col, value=val)
        c.font = font
        c.fill = fill
        c.alignment = center

    ws.merge_cells("J2:M2")
    ws.merge_cells("N2:Q2")
    ws.merge_cells("R2:U2")

    r2_headers = {
        1: "week", 2: "No.", 3: "التاريخ", 4: "اسم المراجع",
        5: "رقم الجوال", 6: "القسم المرسل اليه",
        7: "time line", 8: "تاريخ الاستفسار", 9: "وقت الاستفسار",
        10: "تم التواصل ولم يتم الرد",
        14: "تحت الإجراء",
        18: "تم التواصل ",
        22: "الاستفسار (من الPDF)", 23: "حالة الاستفسار",
        24: "ملاحظات الموظف", 25: "ملاحظات المشرف",
    }
    for col, val in r2_headers.items():
        c = ws.cell(row=2, column=col, value=val)
        c.font = r2_font
        c.fill = r2_fill
        c.alignment = wrap_center
        c.border = border

    for col in range(1, 26):
        ws.cell(row=2, column=col).border = border
        if col not in r2_headers:
            c = ws.cell(row=2, column=col)
            c.fill = r2_fill
            c.border = border

    ws.freeze_panes = "C3"

    qs = queryset.select_related(
        "hospital", "department", "assigned_to", "created_by",
        "outgoing_department", "responded_by",
        "contacted_nr_by", "under_process_by", "contacted_by",
        "source",
    ).order_by("created_at")

    first_half_end = 15
    row_num = 3
    first_half_start_row = 3
    second_half_start_row = None
    idx = 0

    for inq in qs:
        idx += 1
        is_first_half = inq.created_at.day <= first_half_end

        if is_first_half:
            week_label = "1st Half of the month, From 1 to 15"
        else:
            week_label = "2nd Half of the month, From 16 to end"
            if second_half_start_row is None:
                second_half_start_row = row_num

        dept_name = inq.outgoing_department.name if inq.outgoing_department else ""

        timeline = inq.get_timeline_sla_display() if inq.timeline_sla else ""
        if not timeline and inq.due_at and inq.created_at:
            hours = (inq.due_at - inq.created_at).total_seconds() / 3600
            if hours <= 24:
                timeline = "24 Hours"
            elif hours <= 48:
                timeline = "48 Hours"
            elif hours <= 72:
                timeline = "72 Hours"
            else:
                timeline = "More than 72 hours"

        row_data = [None] * 25
        row_data[0] = week_label
        row_data[1] = idx
        row_data[2] = _strip_tz(inq.created_at)
        row_data[3] = inq.contact_name or ""
        row_data[4] = inq.contact_phone or ""
        row_data[5] = dept_name
        row_data[6] = timeline
        row_data[7] = _fmt_date(inq.created_at)
        row_data[8] = _fmt_time(inq.created_at)

        if inq.contacted_nr_at:
            row_data[9] = _fmt_date(inq.contacted_nr_at)
            row_data[10] = inq.contacted_nr_time or _fmt_time(inq.contacted_nr_at)
            row_data[11] = inq.contacted_nr_by.get_full_name() if inq.contacted_nr_by else ""
            row_data[12] = _fmt_duration(inq.contacted_nr_duration)

        if inq.under_process_at:
            row_data[13] = _fmt_date(inq.under_process_at)
            row_data[14] = inq.under_process_time or _fmt_time(inq.under_process_at)
            row_data[15] = inq.under_process_by.get_full_name() if inq.under_process_by else ""
            row_data[16] = _fmt_duration(inq.under_process_duration)

        if inq.contacted_at:
            row_data[17] = _fmt_date(inq.contacted_at)
            row_data[18] = inq.contacted_time or _fmt_time(inq.contacted_at)
            row_data[19] = inq.contacted_by.get_full_name() if inq.contacted_by else ""
            row_data[20] = _fmt_duration(inq.contacted_duration)

        row_data[21] = inq.subject
        status_ar = {
            "open": "جديد", "in_progress": "تحت الإجراء",
            "resolved": "تم التواصل", "closed": "تم التواصل",
            "contacted": "تم التواصل",
            "contacted_no_response": "تم التواصل ولم يتم الرد",
        }
        row_data[22] = status_ar.get(inq.status, inq.get_status_display())
        row_data[23] = inq.staff_notes
        row_data[24] = inq.supervisor_notes

        for col_idx, val in enumerate(row_data):
            c = ws.cell(row=row_num, column=col_idx + 1, value=val)
            c.font = data_font
            c.border = border
            if col_idx == 2:
                c.number_format = "YYYY-MM-DD"
            elif col_idx in (8, 10, 14, 18):
                c.number_format = "HH:MM"
            elif col_idx in (7, 9, 13, 17):
                c.number_format = "YYYY-MM-DD"

        row_num += 1

    last_data_row = row_num - 1
    if first_half_start_row and first_half_start_row <= last_data_row:
        first_half_end_row = (second_half_start_row or (last_data_row + 1)) - 1
        if first_half_start_row < first_half_end_row:
            ws.merge_cells(
                start_row=first_half_start_row, start_column=1,
                end_row=first_half_end_row, end_column=1,
            )
    if second_half_start_row and second_half_start_row <= last_data_row:
        if second_half_start_row < last_data_row:
            ws.merge_cells(
                start_row=second_half_start_row, start_column=1,
                end_row=last_data_row, end_column=1,
            )

    _set_outgoing_widths(ws)
    ws.sheet_view.rightToLeft = True


def _set_outgoing_widths(ws):
    widths = {
        "A": 9.13, "B": 8.75, "C": 15.13, "D": 24.25,
        "E": 20.38, "F": 30.0, "G": 16.63,
        "H": 26.5, "I": 16.63,
        "J": 23.0, "K": 20.38, "L": 20.63, "M": 10.75,
        "N": 23.25, "O": 16.88, "P": 26.5, "Q": 15.38,
        "R": 23.5, "S": 16.88, "T": 18.63, "U": 18.25,
        "V": 25.5, "W": 22.88, "X": 57.88, "Y": 14.75,
    }
    for col_letter, w in widths.items():
        ws.column_dimensions[col_letter].width = w


def _get_employee_names(queryset):
    creator_ids = queryset.values_list("created_by_id", flat=True).distinct()
    from apps.accounts.models import User
    users = User.objects.filter(id__in=[uid for uid in creator_ids if uid])
    return [(u.get_full_name(), u.employee_id) for u in users if u.get_full_name()]


def _build_incoming_calculations(wb, queryset, year, month):
    ws = wb.create_sheet("Calculations tamplete")
    BOLD = Font(bold=True)
    CENTER = Alignment(horizontal="center", vertical="center")

    ws.cell(row=3, column=1, value="1st Half").font = BOLD
    ws.cell(row=4, column=1, value="2nd Half").font = BOLD
    ws.cell(row=5, column=1, value="TOTAL").font = BOLD

    headers = {4: "Employees", 5: "تم التواصل ولم يتم الرد", 6: "تحت الاجراء",
               7: "تم التواصل", 8: "مجموع الاستفسارات للموظف"}
    for col, val in headers.items():
        ws.cell(row=3, column=col, value=val).font = BOLD

    employees = _get_employee_names(queryset)
    for i, (name, _eid) in enumerate(employees):
        row = 4 + i
        ws.cell(row=row, column=4, value=name)
        for col in (5, 6, 7, 8):
            ws.cell(row=row, column=col, value=0)

    emp_count = len(employees)
    total_row = 4 + emp_count
    ws.cell(row=total_row, column=4, value="Total").font = BOLD
    for col in (5, 6, 7, 8):
        if emp_count > 0:
            first_data_row = 4
            last_data_row = 4 + emp_count - 1
            col_letter = get_column_letter(col)
            ws.cell(row=total_row, column=col,
                    value=f"=SUM({col_letter}{first_data_row}:{col_letter}{last_data_row})")

    pct_row = total_row + 1
    for col in (5, 6, 7, 8):
        col_letter = get_column_letter(col)
        ws.cell(row=pct_row, column=col,
                value=f"=IF({col_letter}{total_row}=0,0,{col_letter}{total_row}/B{5})")
        ws.cell(row=pct_row, column=col).number_format = "0.00%"

    ws.cell(row=3, column=11, value="Number of incoming inquiries").font = BOLD
    for col, val in {12: "24 Hours", 13: "48 Hours", 14: "72 Hours", 15: "More than 72 hours"}.items():
        ws.cell(row=3, column=col, value=val).font = BOLD

    ws.cell(row=4, column=10, value="Total ").font = BOLD
    ws.cell(row=4, column=11, value=0)
    ws.cell(row=4, column=11,
            value=f"=SUM({get_column_letter(12)}4:{get_column_letter(15)}4)")

    ws.cell(row=5, column=10, value="Percentage ").font = BOLD
    for col in range(11, 16):
        col_letter = get_column_letter(col)
        ws.cell(row=5, column=col,
                value=f"=IF({col_letter}4=0,0,{col_letter}4/K4)")
        ws.cell(row=5, column=col).number_format = "0.00%"

    status_labels = {18: "تم التواصل ولم يتم الرد", 19: "تحت الاجراء ",
                     20: "تم التواصل ", 21: "جديد", 22: "مجموع الاستفسارات خلال الشهر "}
    for col, val in status_labels.items():
        ws.cell(row=3, column=col, value=val).font = BOLD

    ws.cell(row=4, column=17, value="Total ").font = BOLD
    for col in range(18, 23):
        ws.cell(row=4, column=col, value=0)

    ws.cell(row=5, column=17, value="Percentage ").font = BOLD
    for col in range(18, 22):
        col_letter = get_column_letter(col)
        ws.cell(row=5, column=col,
                value=f"=IF({col_letter}4=0,0,{col_letter}4/V4)")
        ws.cell(row=5, column=col).number_format = "0.00%"
    ws.cell(row=5, column=22,
            value=f"=SUM({get_column_letter(18)}5:{get_column_letter(21)}5)")
    ws.cell(row=5, column=22).number_format = "0.00%"


def _build_outgoing_calculations(wb, queryset, year, month):
    ws = wb.create_sheet("Calculations tamplete")
    BOLD = Font(bold=True)

    ws.cell(row=6, column=1, value="1st Half").font = BOLD
    ws.cell(row=7, column=1, value="2nd Half").font = BOLD
    ws.cell(row=8, column=1, value="TOTAL").font = BOLD

    ws.cell(row=6, column=4, value="Department").font = BOLD
    ws.cell(row=6, column=5, value="Status").font = BOLD
    ws.cell(row=6, column=8, value="Percentage").font = BOLD
    ws.cell(row=6, column=11, value="Total Percentage").font = BOLD
    ws.cell(row=6, column=12, value="Total").font = BOLD

    status_headers = {5: "تحت الإجراء ", 6: "تم التواصل ولم يتم الرد", 7: "تم التواصل"}
    for col, val in status_headers.items():
        ws.cell(row=7, column=col, value=val).font = BOLD
    pct_headers = {8: "تحت الإجراء ", 9: "تم التواصل ولم يتم الرد", 10: "تم التواصل"}
    for col, val in pct_headers.items():
        ws.cell(row=7, column=col, value=val).font = BOLD

    outgoing_depts = queryset.values_list(
        "outgoing_department__name", flat=True
    ).distinct().order_by("outgoing_department__name")

    dept_names = [d for d in outgoing_depts if d]
    for i, dept in enumerate(dept_names):
        row = 8 + i
        ws.cell(row=row, column=4, value=dept)
        for col in (5, 6, 7, 12):
            ws.cell(row=row, column=col, value=0)
        for col in (8, 9, 10, 11):
            ws.cell(row=row, column=col, value="#DIV/0!")

    total_row = 8 + len(dept_names)
    ws.cell(row=total_row, column=4, value="Total").font = BOLD
    for col in (5, 6, 7):
        col_letter = get_column_letter(col)
        ws.cell(row=total_row, column=col,
                value=f"=SUM({col_letter}8:{col_letter}{total_row - 1})")

    ws.cell(row=7, column=14, value="Total ").font = BOLD
    ws.cell(row=8, column=14, value="Percentage ").font = BOLD

    ws.cell(row=6, column=15, value="Number of Outgoing inquiries").font = BOLD
    for col, val in {16: "24 Hours", 17: "48 Hours", 18: "72 Hours", 19: "More than 72 hours"}.items():
        ws.cell(row=6, column=col, value=val).font = BOLD

    ws.cell(row=7, column=15, value=0)
    ws.cell(row=8, column=15,
            value=f"=IF(O7=0,0,O7/O7)")

    status_labels = {22: "تم التواصل ولم يتم الرد", 23: "تحت الاجراء ",
                     24: "تم التواصل ", 25: "جديد", 26: "مجموع الاستفسارات الصادرة خلال الشهر "}
    for col, val in status_labels.items():
        ws.cell(row=6, column=col, value=val).font = BOLD
    ws.cell(row=7, column=21, value="Total ").font = BOLD
    ws.cell(row=8, column=21, value="Percentage ").font = BOLD


def _build_incoming_dropdown(wb, queryset):
    ws = wb.create_sheet("Drop-down")
    timeline_options = ["24 Hours", "48 Hours", "72 Hours", "More than 72 hours"]
    for i, opt in enumerate(timeline_options):
        ws.cell(row=i + 1, column=1, value=opt)
    ws.cell(row=1, column=2, value="More than 72 hours")

    employees = _get_employee_names(queryset)
    for i, (name, eid) in enumerate(employees):
        ws.cell(row=7 + i, column=1, value=eid or "")
        ws.cell(row=7 + i, column=2, value=name)

    status_options = ["تم التواصل", "تحت الإجراء ", "تم التواصل ولم يتم الرد"]
    for i, opt in enumerate(status_options):
        ws.cell(row=17 + i, column=1, value=opt)


def _build_outgoing_dropdown(wb, queryset):
    ws = wb.create_sheet("Drop-down")
    timeline_options = ["24 Hours", "48 Hours", "72 Hours", "More than 72 hours"]
    for i, opt in enumerate(timeline_options):
        ws.cell(row=i + 1, column=1, value=opt)
    ws.cell(row=1, column=2, value="More than 72 hours")

    employees = _get_employee_names(queryset)
    for i, (name, _eid) in enumerate(employees):
        ws.cell(row=7 + i, column=1, value=name)

    status_options = ["تم التواصل ", "تحت الإجراء ", "تم التواصل ولم يتم الرد"]
    for i, opt in enumerate(status_options):
        ws.cell(row=16 + i, column=1, value=opt)


def export_observations_report(queryset, year, month):
    """
    Reports — Observations Excel export.

    Matches the 22-column template from the Observations Excel document.
    """
    wb = Workbook()
    month_label = f"{year}-{month:02d}"
    ws = wb.active
    ws.title = month_label

    headers = [
        "Portal",
        "Note No.",
        "Send Date",
        "Send Time",
        "Recipient Mobile",
        "File Number",
        "Sender Employee ID",
        "Observation Source",
        "Main Category",
        "Sub-Category",
        "",
        "Topic",
        "Details (Arabic)",
        "Details (English)",
        "Person Notified",
        "Department Notified",
        "Communication Method",
        "Communication Date",
        "Communication Time",
        "Action Plan / Action Taken",
        "Follow-Up Resolved?",
        "Solutions & Suggestions",
    ]
    _write_header(ws, 1, headers)
    ws.freeze_panes = "C2"

    row_num = 2
    for idx, obs in enumerate(queryset, 1):
        action_taken = ""
        resolved = ""
        solutions = ""

        for note in obs.notes.all():
            text = note.note.lower() if note.note else ""
            if not action_taken and ("action" in text or "taken" in text):
                action_taken = note.note
            if "resolved" in text or "done" in text:
                resolved = "Yes"
            if "suggestion" in text or "solution" in text:
                solutions = note.note

        obs_source = ""
        if obs.source:
            source_map = {
                "staff_portal": "Portal",
                "web_form": "Portal",
                "mobile_app": "Barcode",
                "email": "Referral",
                "call_center": "In-person",
            }
            obs_source = source_map.get(obs.source, obs.source)

        status_map = {
            "resolved": "done",
            "closed": "resolved",
            "in_progress": "under process",
            "new": "",
        }
        resolved_display = status_map.get(obs.status, "")

        _write_row(
            ws,
            row_num,
            [
                obs_source,
                idx,
                obs.incident_datetime.date() if obs.incident_datetime else "",
                obs.incident_datetime.strftime("%H:%M") if obs.incident_datetime else "",
                obs.reporter_phone or "",
                obs.tracking_code or "",
                obs.reporter_staff_id or "",
                obs_source,
                obs.category.name if obs.category else "",
                obs.assigned_department.name if obs.assigned_department else "",
                "",
                obs.title or "",
                obs.description or "",
                obs.resolution_notes or "",
                obs.assigned_to.get_full_name() if obs.assigned_to else "",
                obs.assigned_department.name if obs.assigned_department else "",
                "",
                obs.activated_at.date() if obs.activated_at else "",
                obs.activated_at.strftime("%H:%M") if obs.activated_at else "",
                action_taken,
                resolved_display,
                solutions,
            ],
        )
        row_num += 1

    for col in range(1, 23):
        ws.column_dimensions[get_column_letter(col)].width = 20
    ws.column_dimensions[get_column_letter(13)].width = 40
    ws.column_dimensions[get_column_letter(14)].width = 40
    ws.column_dimensions[get_column_letter(20)].width = 35

    response = HttpResponse(content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
    response["Content-Disposition"] = f'attachment; filename="observations_{month_label}.xlsx"'
    wb.save(response)
    return response
def export_historical_excel(queryset, date_start=None, date_end=None):
    """
    Export complaints to historical Excel format with Arabic headers.

    Creates a single sheet with all 105 columns matching the historical
    complaint report format used in the original Excel files.

    Args:
        queryset: Complaint queryset to export
        date_start: Optional start date filter
        date_end: Optional end date filter

    Returns:
        HttpResponse with Excel file
    """
    wb = Workbook()
    ws = wb.active

    # Set RTL direction for Arabic
    ws.sheet_view.rightToLeft = True

    # Define styles
    header_font = Font(bold=True, color="FFFFFF", size=11)
    header_fill = PatternFill(start_color="4472C4", end_color="4472C4", fill_type="solid")
    header_alignment = Alignment(horizontal="center", vertical="center", wrap_text=True)
    thin_border = Border(
        left=Side(style="thin"), right=Side(style="thin"), top=Side(style="thin"), bottom=Side(style="thin")
    )

    # All 105 headers in Arabic (matching historical Excel format)
    headers = [
        "Week",  # 1
        "No.",  # 2
        "رقم الشكوى",  # 3 - Complaint Number
        "رقم الملف",  # 4 - File Number (MRN)
        "جهة الشكوى",  # 5 - Complaint Source
        "الموقع",  # 6 - Location
        "القسم الرئيس",  # 7 - Main Department
        "القسم الفرعي",  # 8 - Sub Department
        "تاريخ إستلام الشكوى",  # 9 - Date Received
        "المدخل",  # 10 - Entered By
        "Time line",  # 11
        "إرسال نموذج الشكوى",  # 12 - Form Sent
        "الوقت",  # 13 - Time (Form Sent)
        "الموظف",  # 14 - Employee
        "تحرير الشكوى",  # 15 - Complaint Filed
        "الوقت",  # 16 - Time (Complaint Filed)
        "تفعيل الشكوى",  # 17 - Activated
        "الوقت",  # 18 - Time (Activated)
        "الموظف .1",  # 19 - Employee
        "تم ارسال الشكوى",  # 20 - Sent
        "الوقت",  # 21 - Time (Sent)
        "الموظف .2",  # 22 - Employee
        "الوقت بين التحرير والارسال",  # 23 - Time: Filed to Sent
        "First Reminder Sent",  # 24
        "الوقت",  # 25 - Time (First Reminder)
        "الموظف .3",  # 26 - Employee
        "الوقت بين اول ايميل والارسال",  # 27 - Time: 1st Rem to Sent
        "Second Reminder Sent",  # 28
        "الوقت",  # 29 - Time (Second Reminder)
        "الموظف .4",  # 30 - Employee
        "الوقت بين ثاني ايميل والاول",  # 31 - Time: 2nd Rem to 1st
        "Escalated",  # 32
        "الوقت",  # 33 - Time (Escalation)
        "الموظف .5",  # 34 - Employee
        "Reason of Escalation ",  # 35
        "الوقت بين التصعيد والارسال",  # 36 - Time: Escalation to Sent
        "Closed",  # 37
        "الوقت",  # 38 - Time (Closure)
        "الموظف .6",  # 39 - Employee
        "الوقت بين الاغلاق والارسال",  # 40 - Time: Close to Sent
        "تاريخ الرد",  # 41 - Response Date
        "الوقت",  # 42 - Time (Response)
        "الوقت بين الرد والارسال",  # 43 - Time: Response to Sent
        "Resolved",  # 44
        "الوقت",  # 45 - Time (Resolution)
        "الموظف .7",  # 46 - Employee
        "الوقت بين المعالجة و الارسال",  # 47 - Time: Resolve to Sent
        "ID",  # 48
        "اسم الشخص المشتكى عليه - ان وجد",  # 49 - Accused Staff Name
        "Domain",  # 50
        "Category",  # 51
        "Sub-Category",  # 52
        "Classification",  # 53
        "محتوى الشكوى (عربي)",  # 54 - Content Arabic
        "محتوى الشكوى (English)",  # 55 - Content English
        "Satisfied/Dissatisfied",  # 56
        "The Rightful Side",  # 57
        "Recommendation/Action plan",  # 58
        "رقم الشكوى.1",  # 59 - Duplicate columns (for formulas)
        "رقم الملف.1",  # 60
        "جهة الشكوى.1",  # 61
        "الموقع.1",  # 62
        "القسم الرئيس.1",  # 63
        "القسم الفرعي.1",  # 64
        "تاريخ إستلام الشكوى.1",  # 65
        "المدخل.1",  # 66
        "Time line.1",  # 67
        "الوقت",  # 68 - Time (Duplicate)
        "رقم الشكوى.2",  # 69
        "رقم الملف.2",  # 70
        "جهة الشكوى.2",  # 71
        "الموقع.2",  # 72
        "القسم الرئيس.2",  # 73
        "القسم الفرعي.2",  # 74
        "تاريخ إستلام الشكوى.2",  # 75
        "المدخل.2",  # 76
        "Time line.2",  # 77
        "الوقت",  # 78 - Time (Duplicate)
        "رقم الشكوى.3",  # 79
        "رقم الملف.3",  # 80
        "جهة الشكوى.3",  # 81
        "الموقع.3",  # 82
        "القسم الرئيس.3",  # 83
        "القسم الفرعي.3",  # 84
        "تاريخ إستلام الشكوى.3",  # 85
        "المدخل.3",  # 86
        "Time line.3",  # 87
        "الوقت",  # 88 - Time (Duplicate)
        "رقم الشكوى.4",  # 89
        "رقم الملف.4",  # 90
        "جهة الشكوى.4",  # 91
        "الموقع.4",  # 92
        "القسم الرئيس.4",  # 93
        "القسم الفرعي.4",  # 94
        "تاريخ إستلام الشكوى.4",  # 95
        "المدخل.4",  # 96
        "Time line.4",  # 97
    ]

    # Write headers
    for col_num, header in enumerate(headers, 1):
        cell = ws.cell(row=1, column=col_num, value=header)
        cell.font = header_font
        cell.fill = header_fill
        cell.alignment = header_alignment
        cell.border = thin_border

    # Freeze top row
    ws.freeze_panes = "A2"

    # Prepare queryset with related fields
    qs = queryset.select_related(
        "patient",
        "hospital",
        "department",
        "legacy_main_section",
        "legacy_subsection",
        "assigned_to",
        "resolved_by",
        "closed_by",
        "created_by",
        "source",
        "domain",
        "category",
        "subcategory_obj",
        "classification_obj",
    ).prefetch_related("updates")

    row_num = 2
    for idx, c in enumerate(qs, 1):
        # Get week number from created_at
        cal = c.created_at
        week_of_month = (cal.day - 1) // 7 + 1 if cal else ""

        # Helper to get name with fallback
        def get_name(obj, attr="name"):
            if not obj:
                return ""
            if hasattr(obj, attr):
                val = getattr(obj, attr)
                if hasattr(val, "name_ar"):
                    return val.name_ar
                elif hasattr(val, "name"):
                    return val.name
                return str(val)
            return ""

        def get_name_ar(obj):
            if not obj:
                return ""
            if hasattr(obj, "name_ar"):
                return obj.name_ar
            elif hasattr(obj, "name"):
                return obj.name
            return str(obj)

        def time_diff(dt1, dt2):
            if dt1 and dt2:
                delta = dt1 - dt2
                return str(delta)
            return ""

        # Source name
        source_name = ""
        if c.source:
            source_name = c.source.name_ar or c.source.name_en or str(c.source)

        # Location (main_section or location)
        location_name = ""
        if c.legacy_main_section:
            location_name = get_name_ar(c.legacy_main_section)
        elif c.legacy_location:
            location_name = get_name_ar(c.legacy_location)

        # Departments
        main_dept = ""
        if c.legacy_main_section:
            main_dept = get_name_ar(c.legacy_main_section)
        elif c.department:
            main_dept = get_name_ar(c.department)

        sub_dept = ""
        if c.legacy_subsection:
            sub_dept = get_name_ar(c.legacy_subsection)

        # Entered by (assigned_to or created_by)
        entered_by = ""
        if c.assigned_to:
            entered_by = c.assigned_to.get_full_name() or c.assigned_to.username
        elif c.created_by:
            entered_by = c.created_by.get_full_name() or c.created_by.username

        # Taxonomy fields
        domain_name = ""
        if c.domain:
            domain_name = c.domain.name_ar or c.domain.name_en or str(c.domain)

        category_name = ""
        if c.category:
            category_name = c.category.name_ar or c.category.name_en or str(c.category)

        subcategory_name = ""
        if c.subcategory_obj:
            subcategory_name = c.subcategory_obj.name_ar or c.subcategory_obj.name_en or str(c.subcategory_obj)

        classification_name = ""
        if c.classification_obj:
            classification_name = (
                c.classification_obj.name_ar or c.classification_obj.name_en or str(c.classification_obj)
            )

        # Description - use as is (not splitting)
        description = c.description or ""

        # Satisfaction
        satisfaction = ""
        if c.satisfaction:
            satisfaction = "Satisfied" if c.satisfaction == "satisfied" else "Dissatisfied"

        # Rightful side
        rightful_side = ""
        if c.resolution_outcome:
            rightful_side = c.get_resolution_outcome_display() or c.resolution_outcome

        # Staff name
        staff_name = c.staff_name or ""
        if not staff_name and c.staff:
            staff_name = c.staff.get_full_name() or str(c.staff)

        # Reference number components
        ref_parts = c.reference_number.split("-") if c.reference_number else []
        complaint_num = ref_parts[-1] if len(ref_parts) >= 4 else ""

        # Time difference calculations
        time_filed_to_sent = time_diff(c.forwarded_to_dept_at, c.created_at)
        time_rem1_to_sent = time_diff(c.forwarded_to_dept_at, c.reminder_sent_at) if c.reminder_sent_at and c.forwarded_to_dept_at else ""
        time_rem2_to_rem1 = time_diff(c.second_reminder_sent_at, c.reminder_sent_at) if c.second_reminder_sent_at and c.reminder_sent_at else ""
        time_escal_to_sent = time_diff(c.forwarded_to_dept_at, c.escalated_at) if c.escalated_at and c.forwarded_to_dept_at else ""
        time_close_to_sent = time_diff(c.resolved_at, c.closed_at) if c.resolved_at and c.closed_at else ""
        time_response_to_sent = time_diff(c.response_date, c.resolved_at) if c.response_date and c.resolved_at else ""
        time_resolve_to_sent = time_diff(c.resolution_sent_at, c.resolved_at) if c.resolution_sent_at and c.resolved_at else ""

        # Build row data (only first ~60 columns have meaningful data)
        row_data = [""] * 97
        row_data[0] = week_of_month  # Week
        row_data[1] = idx  # No.
        row_data[2] = complaint_num  # رقم الشكوى
        row_data[3] = c.patient.mrn if c.patient else ""  # رقم الملف
        row_data[4] = source_name  # جهة الشكوى
        row_data[5] = location_name  # الموقع
        row_data[6] = main_dept  # القسم الرئيس
        row_data[7] = sub_dept  # القسم الفرعي
        row_data[8] = cal.strftime("%Y-%m-%d %H:%M:%S") if cal else ""  # تاريخ إستلام الشكوى
        row_data[9] = entered_by  # المدخل
        # Time difference calculations (timeline columns)
        row_data[22] = time_filed_to_sent
        row_data[26] = time_rem1_to_sent
        row_data[30] = time_rem2_to_rem1
        row_data[35] = time_escal_to_sent
        row_data[39] = time_close_to_sent
        row_data[42] = time_response_to_sent
        row_data[46] = time_resolve_to_sent
        row_data[47] = c.metadata.get("original_complaint_id") or "" if c.metadata else ""  # ID
        row_data[48] = staff_name  # اسم الشخص المشتكى عليه
        row_data[49] = domain_name  # Domain
        row_data[50] = category_name  # Category
        row_data[51] = subcategory_name  # Sub-Category
        row_data[52] = classification_name  # Classification
        row_data[53] = description  # محتوى الشكوى (عربي) - full description
        row_data[54] = ""  # محتوى الشكوى (English) - empty, Arabic column has full content
        row_data[55] = satisfaction  # Satisfied/Dissatisfied
        row_data[56] = rightful_side  # The Rightful Side
        row_data[57] = c.recommendation_action_plan or ""  # Recommendation/Action plan
        # Duplicate columns (67-105) - formulas reference first set, keep empty

        # Write row
        for col_num, val in enumerate(row_data, 1):
            cell = ws.cell(row=row_num, column=col_num, value=val)
            cell.border = thin_border
            cell.alignment = Alignment(vertical="center", wrap_text=True)

        row_num += 1

    # Auto-adjust column widths for main columns
    for col_num in range(1, 60):
        col_letter = get_column_letter(col_num)
        if col_num in [3, 4, 5, 6, 7, 8]:  # Arabic text columns
            ws.column_dimensions[col_letter].width = 25
        elif col_num in [54, 55]:  # Description columns
            ws.column_dimensions[col_letter].width = 50
        elif col_num == 49:  # Staff name
            ws.column_dimensions[col_letter].width = 30
        else:
            ws.column_dimensions[col_letter].width = 15

    # Hide the duplicate columns (67-105) as they're for formulas
    for col_num in range(59, 98):
        col_letter = get_column_letter(col_num)
        ws.column_dimensions[col_letter].hidden = True

    # Generate filename
    if date_start and date_end:
        sheet_name = f"{date_start}_to_{date_end}"
    elif date_start:
        sheet_name = f"from_{date_start}"
    elif date_end:
        sheet_name = f"until_{date_end}"
    else:
        sheet_name = "all_complaints"

    ws.title = sheet_name[:31]  # Excel sheet name limit

    filename = f"historical_complaints_{sheet_name}.xlsx"

    # Save to response
    response = HttpResponse(content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
    response["Content-Disposition"] = f'attachment; filename="{filename}"'
    wb.save(response)

    return response
