import calendar
from collections import defaultdict
from datetime import date, datetime

from django.db.models import Count, Q, F
from django.db.models.functions import TruncMonth
from django.utils import timezone as dj_timezone

from apps.complaints.models import Complaint

SOURCE_TYPES = ["MOH", "CHI", "CCHI", "Insurance"]
LOCATION_TYPES = ["IP", "OP", "ER"]
DEPT_TYPES = ["Medical", "Admin", "Nursing", "Support Services"]
TIMELINE_BUCKETS = ["24h", "48h", "72h", "72h+"]
MONTHS_ABBR = [calendar.month_abbr[m] for m in range(1, 13)]


class ComplaintQuarterlyService:
    def __init__(self, hospital_id, start_date, end_date):
        self.hospital_id = hospital_id
        if isinstance(start_date, str):
            start_date = date.fromisoformat(start_date)
        if isinstance(end_date, str):
            end_date = date.fromisoformat(end_date)
        self.start_date = start_date
        self.end_date = end_date
        self.year = start_date.year
        self._cache = {}

        if start_date.year == end_date.year:
            self.active_months = list(range(start_date.month, end_date.month + 1))
        else:
            self.active_months = []
            y = start_date.year
            m = start_date.month
            while (y, m) <= (end_date.year, end_date.month):
                self.active_months.append(m)
                m += 1
                if m > 12:
                    m = 1
                    y += 1

        self.active_month_labels = [calendar.month_abbr[m] for m in self.active_months]

    def _base_qs(self):
        dt_start = dj_timezone.make_aware(datetime.combine(self.start_date, datetime.min.time()))
        dt_end = dj_timezone.make_aware(datetime.combine(self.end_date, datetime.max.time()))
        return Complaint.objects.filter(
            hospital_id=self.hospital_id,
            created_at__range=(dt_start, dt_end),
        ).select_related(
            "department",
            "source", "assigned_to", "created_by",
        )

    def _month_qs(self, month):
        return self._base_qs().filter(created_at__month=month)

    def _cached(self, key, fn):
        if key not in self._cache:
            self._cache[key] = fn()
        return self._cache[key]

    def get_available_years(self):
        years = (
            Complaint.objects.filter(hospital_id=self.hospital_id)
            .values_list("created_at__year", flat=True)
            .distinct()
            .order_by("-created_at__year")
        )
        return list(years)

    def get_kpi_data(self):
        return self._cached("kpi_data", self._compute_kpi_data)

    def _compute_kpi_data(self):
        result = {}
        for m in self.active_months:
            qs = self._month_qs(m)
            total = qs.count()
            closed = qs.filter(status__in=["closed", "resolved"]).count()
            resolved_72h = 0
            resolved_48h = 0
            for c in qs.filter(status__in=["closed", "resolved"]).iterator(chunk_size=2000):
                if c.resolved_at and c.activated_at:
                    hours = (c.resolved_at - c.activated_at).total_seconds() / 3600
                    if hours <= 72:
                        resolved_72h += 1
                    if hours <= 48:
                        resolved_48h += 1
                elif c.closed_at and c.created_at:
                    hours = (c.closed_at - c.created_at).total_seconds() / 3600
                    if hours <= 48:
                        resolved_48h += 1
            activated = qs.filter(activated_at__isnull=False).count()
            not_activated = total - activated

            result[m] = {
                "total": total,
                "closed": closed,
                "resolution_rate": round(closed / total, 3) if total > 0 else 0,
                "resolved_72h": resolved_72h,
                "resolved_72h_rate": round(resolved_72h / total, 3) if total > 0 else 0,
                "resolved_48h": resolved_48h,
                "resolved_48h_rate": round(resolved_48h / total, 3) if total > 0 else 0,
                "activated": activated,
                "activation_rate": round(activated / total, 3) if total > 0 else 0,
                "not_activated": not_activated,
                "not_activated_rate": round(not_activated / total, 3) if total > 0 else 0,
            }

        totals = {
            "total": sum(r["total"] for r in result.values()),
            "closed": sum(r["closed"] for r in result.values()),
            "resolved_72h": sum(r["resolved_72h"] for r in result.values()),
            "resolved_48h": sum(r["resolved_48h"] for r in result.values()),
            "activated": sum(r["activated"] for r in result.values()),
            "not_activated": sum(r["not_activated"] for r in result.values()),
        }
        totals["resolution_rate"] = round(totals["closed"] / totals["total"], 3) if totals["total"] > 0 else 0
        totals["resolved_72h_rate"] = round(totals["resolved_72h"] / totals["total"], 3) if totals["total"] > 0 else 0
        totals["resolved_48h_rate"] = round(totals["resolved_48h"] / totals["total"], 3) if totals["total"] > 0 else 0
        totals["activation_rate"] = round(totals["activated"] / totals["total"], 3) if totals["total"] > 0 else 0
        totals["not_activated_rate"] = round(totals["not_activated"] / totals["total"], 3) if totals["total"] > 0 else 0

        return {"months": result, "totals": totals}

    def get_satisfaction_data(self):
        return self._cached("satisfaction_data", self._compute_satisfaction_data)

    def _compute_satisfaction_data(self):
        result = {}
        call_months = {}
        survey_months = {}
        for m in self.active_months:
            qs = self._month_qs(m)
            total = qs.count()
            sat_qs = qs.filter(satisfaction__isnull=False).exclude(satisfaction="")
            surveyed = sat_qs.count()
            satisfied = sat_qs.filter(satisfaction="satisfied").count()
            result[m] = {
                "surveyed": surveyed,
                "satisfied": satisfied,
                "rate": round(satisfied / surveyed, 3) if surveyed > 0 else 0,
            }

            call_resp = qs.filter(resolution_survey_sent_at__isnull=False).count()
            call_months[m] = {
                "responses": call_resp,
                "total": total,
                "rate": round(call_resp / total, 3) if total > 0 else 0,
            }

            survey_resp = sat_qs.count()
            survey_months[m] = {
                "responses": survey_resp,
                "total": total,
                "rate": round(survey_resp / total, 3) if total > 0 else 0,
            }

        total_surveyed = sum(r["surveyed"] for r in result.values())
        total_satisfied = sum(r["satisfied"] for r in result.values())

        call_total = sum(c["responses"] for c in call_months.values())
        call_grand = sum(c["total"] for c in call_months.values())
        survey_total = sum(s["responses"] for s in survey_months.values())

        return {
            "months": result,
            "totals": {
                "surveyed": total_surveyed,
                "satisfied": total_satisfied,
                "rate": round(total_satisfied / total_surveyed, 3) if total_surveyed > 0 else 0,
            },
            "call_months": call_months,
            "call_totals": {
                "responses": call_total,
                "total": call_grand,
                "rate": round(call_total / call_grand, 3) if call_grand > 0 else 0,
            },
            "survey_months": survey_months,
            "survey_totals": {
                "responses": survey_total,
                "total": call_grand,
                "rate": round(survey_total / call_grand, 3) if call_grand > 0 else 0,
            },
        }

    def get_source_breakdown(self):
        return self._cached("source_breakdown", self._compute_source_breakdown)

    def _compute_source_breakdown(self):
        result = {}
        for m in self.active_months:
            qs = self._month_qs(m)
            total = qs.count()
            ext = qs.filter(complaint_source_type="external").count()
            internal = total - ext

            moh = qs.filter(Q(source__code__iexact="moh") | Q(moh_reference__gt="")).count()
            chi = qs.filter(
                Q(source__code__iexact="chi") | Q(source__code__iexact="cchi") | Q(chi_reference__gt="")
            ).count()
            insurance = qs.exclude(
                Q(source__code__iexact="moh") | Q(source__code__iexact="chi") | Q(source__code__iexact="cchi")
            ).filter(source__name_en__icontains="insurance").count()
            patients = qs.filter(
                Q(source__name_en__icontains="patient") | Q(source__name_en__icontains="walk")
            ).count()
            relatives = qs.filter(
                Q(source__name_en__icontains="relative")
                | Q(source__name_en__icontains="family")
                | Q(relation_to_patient="relative")
            ).count()

            by_source = list(
                qs.filter(source__isnull=False)
                .values("source__name_en")
                .annotate(count=Count("id"))
                .order_by("-count")
            )
            no_source = qs.filter(source__isnull=True).count()
            if no_source > 0:
                by_source.append({"source__name_en": "No Source", "count": no_source})

            result[m] = {
                "total": total,
                "external": ext,
                "internal": internal,
                "moh": moh,
                "chi": chi,
                "insurance": insurance,
                "patients": patients,
                "relatives": relatives,
                "by_source": by_source,
            }
        return result

    def get_location_breakdown(self):
        return self._cached("location_breakdown", self._compute_location_breakdown)

    def _compute_location_breakdown(self):
        result = {}
        for m in self.active_months:
            qs = self._month_qs(m)
            total = qs.count()
            ip = qs.filter(department__location_type="IP").count()
            er = qs.filter(department__location_type="ER").count()
            op = qs.filter(department__location_type="OP").count()
            general = total - ip - er - op

            result[m] = {"total": total, "ip": ip, "op": op, "er": er, "general": general}
        return result

    def get_dept_type_breakdown(self):
        return self._cached("dept_type_breakdown", self._compute_dept_type_breakdown)

    def _compute_dept_type_breakdown(self):
        result = {}
        for m in self.active_months:
            qs = self._month_qs(m)
            total = qs.count()
            medical = qs.filter(department__category="medical").count()
            administrative = qs.filter(department__category="administrative").count()
            nursing = qs.filter(department__category="nursing").count()
            support = qs.filter(department__category="support_services").count()
            other = total - medical - administrative - nursing - support

            result[m] = {
                "total": total,
                "medical": medical,
                "administrative": administrative,
                "nursing": nursing,
                "support": support,
                "other": other,
            }
        return result

    def get_escalated_breakdown(self):
        return self._cached("escalated_breakdown", self._compute_escalated_breakdown)

    def _compute_escalated_breakdown(self):
        qs = self._base_qs().filter(escalated_at__isnull=False)
        by_category = defaultdict(lambda: defaultdict(int))
        by_source = defaultdict(int)
        for c in qs.iterator(chunk_size=2000):
            dept_name = c.department.name if c.department else "Unknown"
            domain_type = "medical"
            if c.department and c.department.category:
                dt = c.department.category
                if dt in ("admin", "non_medical", "administrative"):
                    domain_type = "administrative"
                elif dt == "nursing":
                    domain_type = "nursing"
                elif dt in ("support", "support_services"):
                    domain_type = "support_services"
            elif c.domain:
                dt = c.domain.domain_type or ""
                if dt in ("admin", "non_medical", "MANAGEMENT"):
                    domain_type = "administrative"
                elif dt == "nursing":
                    domain_type = "nursing"
                elif dt in ("support", "support_services"):
                    domain_type = "support_services"
            by_category[domain_type][dept_name] += 1

            if c.complaint_source_type == "internal":
                by_source["internal"] += 1
            elif c.source and c.source.code and c.source.code.upper() == "MOH":
                by_source["moh"] += 1
            elif c.source and c.source.code and c.source.code.upper() in ("CHI", "CCHI"):
                by_source["chi"] += 1
            else:
                by_source["internal"] += 1

        total_escalated = qs.count()
        total_complaints = self._base_qs().count()

        return {
            "by_category": dict(by_category),
            "by_source": dict(by_source),
            "total_escalated": total_escalated,
            "total_complaints": total_complaints,
            "escalation_rate": round(total_escalated / total_complaints, 3) if total_complaints > 0 else 0,
        }

    def _bucket_response_time(self, complaint):
        if not complaint.closed_at or not complaint.created_at:
            return None
        hours = (complaint.closed_at - complaint.created_at).total_seconds() / 3600
        if hours <= 24:
            return "24h"
        elif hours <= 48:
            return "48h"
        elif hours <= 72:
            return "72h"
        else:
            return "72h+"

    def _response_time_hours(self, complaint):
        if not complaint.closed_at or not complaint.created_at:
            return None
        return (complaint.closed_at - complaint.created_at).total_seconds() / 3600

    def get_response_rates(self):
        return self._cached("response_rates", self._compute_response_rates)

    def _compute_response_rates(self):
        qs = self._base_qs().filter(status__in=["closed", "resolved"])
        buckets = {
            "internal": defaultdict(int),
            "external_moh": defaultdict(int),
            "external_chi": defaultdict(int),
        }
        individual = {
            "internal": [],
            "external_moh": [],
            "external_chi": [],
        }
        for c in qs.iterator(chunk_size=2000):
            bucket = self._bucket_response_time(c)
            if not bucket:
                continue
            hours = self._response_time_hours(c)
            if c.complaint_source_type == "internal":
                buckets["internal"][bucket] += 1
                individual["internal"].append({
                    "ref": c.reference_number or "",
                    "dept": c.department.name if c.department else "Unknown",
                    "hours": round(hours, 2) if hours else None,
                    "bucket": bucket,
                    "status": c.status,
                })
            elif c.source and c.source.code and c.source.code.upper() == "MOH":
                buckets["external_moh"][bucket] += 1
                individual["external_moh"].append({
                    "ref": c.reference_number or "",
                    "dept": c.department.name if c.department else "Unknown",
                    "hours": round(hours, 2) if hours else None,
                    "bucket": bucket,
                    "status": c.status,
                })
            elif c.source and c.source.code and c.source.code.upper() in ("CHI", "CCHI"):
                buckets["external_chi"][bucket] += 1
                individual["external_chi"].append({
                    "ref": c.reference_number or "",
                    "dept": c.department.name if c.department else "Unknown",
                    "hours": round(hours, 2) if hours else None,
                    "bucket": bucket,
                    "status": c.status,
                })
            else:
                buckets["internal"][bucket] += 1
                individual["internal"].append({
                    "ref": c.reference_number or "",
                    "dept": c.department.name if c.department else "Unknown",
                    "hours": round(hours, 2) if hours else None,
                    "bucket": bucket,
                    "status": c.status,
                })

        def _to_rate(d):
            total = sum(d.values())
            return {
                "counts": {b: d.get(b, 0) for b in TIMELINE_BUCKETS},
                "total": total,
                "rates": {b: round(d.get(b, 0) / total, 3) if total > 0 else 0 for b in TIMELINE_BUCKETS},
            }

        return {
            "internal": {**_to_rate(buckets["internal"]), "individual": individual["internal"]},
            "moh": {**_to_rate(buckets["external_moh"]), "individual": individual["external_moh"]},
            "chi": {**_to_rate(buckets["external_chi"]), "individual": individual["external_chi"]},
        }

    def get_monthly_summary(self):
        return self._cached("monthly_summary", self._compute_monthly_summary)

    def _compute_monthly_summary(self):
        source = self.get_source_breakdown()
        result = []
        for m in self.active_months:
            s = source[m]
            total = s["total"]
            result.append({
                "month": calendar.month_abbr[m],
                "month_num": m,
                "moh": s["moh"],
                "chi": s["chi"],
                "insurance": s["insurance"],
                "internal": s["internal"],
                "total": total,
                "moh_pct": round(s["moh"] / total, 3) if total > 0 else 0,
                "chi_pct": round(s["chi"] / total, 3) if total > 0 else 0,
            })
        return result

    def get_per_department_breakdown(self):
        return self._cached("per_department", self._compute_per_department)

    def _compute_per_department(self):
        qs = self._base_qs()
        dept_data = defaultdict(lambda: defaultdict(int))
        dept_domain = {}

        for c in qs.iterator(chunk_size=2000):
            if not c.department:
                continue
            dept_name = c.department.name
            dept_id = c.department_id

            if c.department.category:
                domain_type = c.department.category
            elif c.domain:
                dt = c.domain.domain_type or ""
                if dt in ("admin", "non_medical", "MANAGEMENT"):
                    domain_type = "administrative"
                elif dt == "nursing":
                    domain_type = "nursing"
                elif dt in ("support", "support_services"):
                    domain_type = "support_services"
            else:
                domain_type = "medical"

            dept_domain[dept_id] = domain_type

            is_moh = (c.source and c.source.code and c.source.code.upper() == "MOH")
            is_chi = (c.source and c.source.code and c.source.code.upper() in ("CHI", "CCHI"))
            is_insurance = (
                not is_moh and not is_chi and
                c.source and c.source.name_en and "insurance" in c.source.name_en.lower()
            )
            is_escalated = c.escalated_at is not None

            key = (dept_id, dept_name)
            dept_data[key]["total"] += 1
            if is_moh:
                dept_data[key]["moh"] += 1
            if is_chi:
                dept_data[key]["chi"] += 1
            if is_insurance:
                dept_data[key]["insurance"] += 1
            if c.complaint_source_type == "internal" and not is_moh and not is_chi and not is_insurance:
                dept_data[key]["internal"] += 1
            if is_escalated:
                dept_data[key]["escalated"] += 1

            if c.closed_at and c.created_at:
                hours = (c.closed_at - c.created_at).total_seconds() / 3600
                dept_data[key]["response_hours_sum"] += hours
                dept_data[key]["response_count"] += 1

        categories = defaultdict(list)
        for (dept_id, dept_name), data in sorted(dept_data.items(), key=lambda x: x[0][1]):
            domain_type = dept_domain.get(dept_id, "medical")
            avg_response = round(data["response_hours_sum"] / data["response_count"], 1) if data["response_count"] > 0 else 0
            categories[domain_type].append({
                "name": dept_name,
                "moh": data["moh"],
                "chi": data["chi"],
                "insurance": data.get("insurance", 0),
                "internal": data["internal"],
                "total": data["total"],
                "escalated": data["escalated"],
                "avg_response_days": round(avg_response / 24, 1),
            })

        source_totals = {
            "moh": sum(d["moh"] for cats in categories.values() for d in cats),
            "chi": sum(d["chi"] for cats in categories.values() for d in cats),
            "internal": sum(d["internal"] for cats in categories.values() for d in cats),
            "insurance": sum(d["insurance"] for cats in categories.values() for d in cats),
            "total": sum(d["total"] for cats in categories.values() for d in cats),
        }

        avg_response = {}
        for dtype in ["medical", "administrative", "nursing", "support_services"]:
            depts = categories.get(dtype, [])
            total_hours = sum(d["avg_response_days"] * 24 * (d["total"] - d.get("_excluded", 0)) for d in depts)
            total_count = sum(d["total"] for d in depts)
            avg_response[dtype] = round(total_hours / (total_count * 24), 1) if total_count > 0 else 0

        return {
            "categories": dict(categories),
            "source_totals": source_totals,
            "avg_response_days": avg_response,
        }

    def get_per_dept_response_times(self):
        return self._cached("per_dept_response_times", self._compute_per_dept_response_times)

    def _compute_per_dept_response_times(self):
        qs = self._base_qs().filter(status__in=["closed", "resolved"])
        dept_times = defaultdict(list)
        dept_domain = {}

        for c in qs.iterator(chunk_size=2000):
            if not c.department:
                continue
            if not c.closed_at or not c.created_at:
                continue

            dept_name = c.department.name
            if c.department.category:
                domain_type = c.department.category
            elif c.domain:
                dt = c.domain.domain_type or ""
                if dt in ("admin", "non_medical", "MANAGEMENT"):
                    domain_type = "administrative"
                elif dt == "nursing":
                    domain_type = "nursing"
                elif dt in ("support", "support_services"):
                    domain_type = "support_services"
            else:
                domain_type = "medical"

            dept_domain[dept_name] = domain_type
            hours = (c.closed_at - c.created_at).total_seconds() / 3600

            is_escalated = c.escalated_at is not None
            status_label = "Escalated" if is_escalated else (
                "Closed" if c.status == "closed" else ""
            )

            dept_times[dept_name].append({
                "hours": round(hours, 2),
                "days": round(hours / 24, 1),
                "status": status_label,
            })

        categories = defaultdict(dict)
        for dept_name, times in dept_times.items():
            domain_type = dept_domain.get(dept_name, "medical")
            categories[domain_type][dept_name] = times

        return dict(categories)

    def get_all_complaints_raw(self):
        return self._cached("all_raw", self._compute_all_raw)

    def _compute_all_raw(self):
        qs = self._base_qs().order_by("created_at")
        rows = []
        for c in qs.iterator(chunk_size=2000):
            source_label = c.complaint_source_type
            if c.moh_reference:
                source_label = "MOH"
            elif c.chi_reference:
                source_label = "CHI"
            elif c.source and c.source.name_en:
                sn = c.source.name_en.lower()
                if "insurance" in sn:
                    source_label = "Insurance"
                elif "patient" in sn or "walk" in sn:
                    source_label = "Patient"
                elif "relative" in sn:
                    source_label = "Patient's relatives"

            location_label = ""
            if c.department and c.department.location_type:
                location_label = c.department.location_type

            dept_label = c.department.name if c.department else ""
            domain_label = c.domain.name_en if c.domain else ""

            hours_to_bucket = ""
            if c.closed_at and c.created_at:
                h = (c.closed_at - c.created_at).total_seconds() / 3600
                if h <= 24:
                    hours_to_bucket = "24 Hours"
                elif h <= 48:
                    hours_to_bucket = "48 Hours"
                elif h <= 72:
                    hours_to_bucket = "72 Hours"
                else:
                    hours_to_bucket = "More than 72 Hours"

            created_by_name = ""
            if c.created_by:
                created_by_name = c.created_by.get_full_name() or str(c.created_by)
            assigned_to_name = ""
            if c.assigned_to:
                assigned_to_name = c.assigned_to.get_full_name() or str(c.assigned_to)

            processing_time = None
            if c.resolved_at and c.activated_at:
                processing_time = (c.resolved_at - c.activated_at).total_seconds() / 3600

            rows.append({
                "reference": c.reference_number or "",
                "source": source_label,
                "location": location_label,
                "department": dept_label,
                "domain": domain_label,
                "created_at": c.created_at,
                "entered_by": created_by_name,
                "timeline_bucket": hours_to_bucket,
                "form_sent_at": c.form_sent_at,
                "activated_at": c.activated_at,
                "assigned_to": assigned_to_name,
                "sent_at": c.forwarded_to_dept_at,
                "time_entry_to_send": (
                    (c.forwarded_to_dept_at - c.created_at).total_seconds() / 3600
                    if c.forwarded_to_dept_at and c.created_at else None
                ),
                "first_reminder": c.reminder_sent_at,
                "time_first_reminder_to_send": (
                    (c.reminder_sent_at - c.forwarded_to_dept_at).total_seconds() / 3600
                    if c.reminder_sent_at and c.forwarded_to_dept_at else None
                ),
                "second_reminder": c.second_reminder_sent_at,
                "escalated_at": c.escalated_at,
                "closed_at": c.closed_at,
                "resolved_at": c.resolved_at,
                "response_date": c.response_date,
                "processing_hours": round(processing_time, 2) if processing_time else None,
                "status": c.status,
                "satisfaction": c.satisfaction or "",
            })
        return rows

    def get_moh_kpi_data(self):
        return self._cached("moh_kpi", self._compute_moh_kpi)

    def _compute_moh_kpi(self):
        moh_months = {}
        real_months = {}
        for m in self.active_months:
            qs = self._month_qs(m).filter(
                Q(source__code__iexact="moh") | Q(moh_reference__gt="")
            )
            total = qs.count()
            satisfied_scoring = qs.filter(satisfaction="satisfied").count()
            moh_months[m] = {
                "satisfied_scoring": satisfied_scoring,
                "total": total,
                "rate": round(satisfied_scoring / total, 3) if total > 0 else 0,
            }

            real_qs = qs.filter(satisfaction__isnull=False).exclude(satisfaction="")
            real_resp = real_qs.count()
            real_satisfied = real_qs.filter(satisfaction="satisfied").count()
            real_months[m] = {
                "satisfied_scoring": real_satisfied,
                "total": total,
                "rate": round(real_satisfied / total, 3) if total > 0 else 0,
            }

        moh_total = sum(v["total"] for v in moh_months.values())
        moh_satisfied = sum(v["satisfied_scoring"] for v in moh_months.values())
        real_satisfied_total = sum(v["satisfied_scoring"] for v in real_months.values())

        return {
            "moh": {
                "months": moh_months,
                "totals": {
                    "satisfied_scoring": moh_satisfied,
                    "total": moh_total,
                    "rate": round(moh_satisfied / moh_total, 3) if moh_total > 0 else 0,
                },
            },
            "real": {
                "months": real_months,
                "totals": {
                    "satisfied_scoring": real_satisfied_total,
                    "total": moh_total,
                    "rate": round(real_satisfied_total / moh_total, 3) if moh_total > 0 else 0,
                },
            },
        }

    def get_location_ratios(self):
        return self._cached("location_ratios", self._compute_location_ratios)

    def _compute_location_ratios(self):
        from apps.dashboard.services.census import CensusService, PATIENT_TYPE_MAP

        location = self.get_location_breakdown()
        census = CensusService(hospital_id=self.hospital_id, year=self.year)
        monthly_visits = census.get_monthly_counts(self.year)

        result = {}
        for m in self.active_months:
            visits = monthly_visits.get(m, {"OPD": 0, "ER": 0, "IP": 0})
            loc = location[m]
            result[m] = {
                "ip_complaints": loc["ip"],
                "ip_patients": visits["IP"],
                "ip_ratio": round(loc["ip"] / visits["IP"], 6) if visits["IP"] > 0 else 0,
                "op_complaints": loc["op"],
                "op_patients": visits["OPD"],
                "op_ratio": round(loc["op"] / visits["OPD"], 6) if visits["OPD"] > 0 else 0,
                "er_complaints": loc["er"],
                "er_patients": visits["ER"],
                "er_ratio": round(loc["er"] / visits["ER"], 6) if visits["ER"] > 0 else 0,
            }

        totals = {}
        for area in ["ip", "op", "er"]:
            total_comp = sum(result[m][f"{area}_complaints"] for m in self.active_months)
            total_pat = sum(result[m][f"{area}_patients"] for m in self.active_months)
            totals[area] = {
                "complaints": total_comp,
                "patients": total_pat,
                "ratio": round(total_comp / total_pat, 6) if total_pat > 0 else 0,
            }

        return {"months": result, "totals": totals}

    def get_quarterly_volumes(self):
        return self._cached("quarterly_volumes", self._compute_quarterly_volumes)

    def _compute_quarterly_volumes(self):
        from apps.dashboard.services.census import CensusService

        census = CensusService(hospital_id=self.hospital_id, year=self.year)
        return census.get_quarterly_data(self.year)

    def get_source_totals(self):
        return self._cached("source_totals", self._compute_source_totals)

    def _compute_source_totals(self):
        source = self.get_source_breakdown()
        total_all = sum(s["total"] for s in source.values())
        moh = sum(s["moh"] for s in source.values())
        chi = sum(s["chi"] for s in source.values())
        insurance = sum(s["insurance"] for s in source.values())
        internal = sum(s["internal"] for s in source.values())
        external = sum(s["external"] for s in source.values())
        return {
            "internal": {"count": internal, "pct": round(internal / total_all, 3) if total_all else 0},
            "external": {"count": external, "pct": round(external / total_all, 3) if total_all else 0},
            "moh": {"count": moh, "pct": round(moh / total_all, 3) if total_all else 0},
            "chi": {"count": chi, "pct": round(chi / total_all, 3) if total_all else 0},
            "insurance": {"count": insurance, "pct": round(insurance / total_all, 3) if total_all else 0},
            "total": total_all,
        }

    def get_dept_type_monthly_table(self):
        return self._cached("dept_type_table", self._compute_dept_type_table)

    def _compute_dept_type_table(self):
        dept = self.get_dept_type_breakdown()
        result = []
        for m in self.active_months:
            d = dept[m]
            result.append({
                "month": calendar.month_abbr[m],
                "medical": d["medical"],
                "administrative": d["administrative"],
                "nursing": d["nursing"],
                "support": d["support"],
                "other": d["other"],
                "total": d["total"],
            })
        totals = {
            "medical": sum(r["medical"] for r in result),
            "administrative": sum(r["administrative"] for r in result),
            "nursing": sum(r["nursing"] for r in result),
            "support": sum(r["support"] for r in result),
            "other": sum(r["other"] for r in result),
            "total": sum(r["total"] for r in result),
        }
        total_all = totals["total"]
        pcts = {k: round(v / total_all, 3) if total_all else 0 for k, v in totals.items() if k != "total"}
        pcts["total"] = 1
        return {"months": result, "totals": totals, "percentages": pcts}

    def get_chart_data(self):
        kpi = self.get_kpi_data()
        source = self.get_source_breakdown()
        location = self.get_location_breakdown()
        dept_type = self.get_dept_type_breakdown()
        satisfaction = self.get_satisfaction_data()

        return {
            "kpi_resolution": {
                "months": self.active_month_labels,
                "values": [kpi["months"][m]["resolution_rate"] for m in self.active_months],
                "target": 0.95,
                "threshold": 0.9,
            },
            "kpi_72h": {
                "months": self.active_month_labels,
                "values": [kpi["months"][m]["resolved_72h_rate"] for m in self.active_months],
            },
            "monthly_totals": {
                "months": self.active_month_labels,
                "values": [kpi["months"][m]["total"] for m in self.active_months],
            },
            "source_distribution": {
                "external": sum(s["external"] for s in source.values()),
                "internal": sum(s["internal"] for s in source.values()),
                "by_source": source[self.active_months[0]]["by_source"] if self.active_months else [],
            },
            "location_distribution": {
                "IP": sum(l["ip"] for l in location.values()),
                "OP": sum(l["op"] for l in location.values()),
                "ER": sum(l["er"] for l in location.values()),
            },
            "dept_type_distribution": {
                "Medical": sum(d["medical"] for d in dept_type.values()),
                "Administrative": sum(d["administrative"] for d in dept_type.values()),
                "Nursing": sum(d["nursing"] for d in dept_type.values()),
                "Support Services": sum(d["support"] for d in dept_type.values()),
                "Other": sum(d["other"] for d in dept_type.values()),
            },
            "satisfaction": {
                "months": self.active_month_labels,
                "values": [satisfaction["months"][m]["rate"] for m in self.active_months],
            },
        }
