"""
AI-Powered Analytics Service

Provides 5 advanced AI features:
1. AI-Generated Executive Summaries
2. Early Warning System (at-risk departments)
3. Predictive Complaint Volume (time-series forecasting)
4. SLA Breach Prediction
5. Automated Action Recommendations
"""

import json
import logging
import math
from datetime import datetime, timedelta
from typing import Any, Dict, List, Optional

from django.conf import settings
from django.core.cache import cache
from django.db.models import Avg, Count, F, Q
from django.utils import timezone

from apps.complaints.models import Complaint
from apps.feedback.models import Feedback
from apps.organizations.models import Department, Hospital
from apps.px_action_center.models import PXAction
from apps.surveys.models import SurveyInstance

logger = logging.getLogger(__name__)


# =============================================================================
# Shared AI Service Wrapper (uses apps.core.ai_service — reads model from .env)
# =============================================================================


def _ai_chat(system_prompt: str, user_prompt: str, max_tokens: int = 4096) -> Optional[str]:
    """Chat with the shared AIService. Model config is read from .env via settings."""
    from apps.core.ai_service import AIService, AIServiceError

    try:
        result = AIService.chat_completion(
            prompt=user_prompt,
            system_prompt=system_prompt,
            max_tokens=max_tokens,
            response_format="json_object",
        )
        result = result.strip()
        if result.startswith("```json"):
            result = result[7:]
        elif result.startswith("```"):
            result = result[3:]
        if result.endswith("```"):
            result = result[:-3]
        return result.strip()
    except (AIServiceError, Exception) as e:
        logger.error(f"AI chat error: {e}")
        return None


class _OpenRouterClient:
    """Compatibility wrapper — delegates to the shared AIService."""

    def is_configured(self) -> bool:
        return True  # AIService manages its own config

    def chat(
        self, system_prompt: str, user_prompt: str, temperature: float = 0.2, max_tokens: int = 4096
    ) -> Optional[str]:
        return _ai_chat(system_prompt, user_prompt, max_tokens)


_client = _OpenRouterClient()


# =============================================================================
# 1. AI-Generated Executive Summaries
# =============================================================================


class ExecutiveSummaryGenerator:
    """
    Generates bilingual (EN/AR) executive summaries by feeding
    aggregated KPI data to the LLM.
    """

    CACHE_TIMEOUT = 3600  # 1 hour

    @classmethod
    def _cache_key(cls, hospital_id=None, department_id=None, period="30d") -> str:
        return f"exec_summary:{hospital_id or 'all'}:{department_id or 'all'}:{period}"

    @staticmethod
    def _gather_data(user, hospital_id=None, department_id=None, period="30d") -> Dict[str, Any]:
        """Collect all data needed for the summary."""
        now = timezone.now()
        if period == "7d":
            start = now - timedelta(days=7)
        elif period == "90d":
            start = now - timedelta(days=90)
        else:
            start = now - timedelta(days=30)

        # Base filters
        base_complaint = Complaint.objects.filter(created_at__gte=start)
        base_action = PXAction.objects.filter(created_at__gte=start)
        base_survey = SurveyInstance.objects.filter(completed_at__gte=start, status="completed")
        base_feedback = Feedback.objects.filter(created_at__gte=start)

        if hospital_id:
            h = Hospital.objects.filter(id=hospital_id).first()
            if h:
                base_complaint = base_complaint.filter(hospital=h)
                base_action = base_action.filter(hospital=h)
                base_survey = base_survey.filter(survey_template__hospital=h)
                base_feedback = base_feedback.filter(hospital=h)
        elif hasattr(user, "hospital") and user.hospital and not getattr(user, "is_px_admin", lambda: False)():
            base_complaint = base_complaint.filter(hospital=user.hospital)
            base_action = base_action.filter(hospital=user.hospital)
            base_survey = base_survey.filter(survey_template__hospital=user.hospital)
            base_feedback = base_feedback.filter(hospital=user.hospital)

        if department_id:
            d = Department.objects.filter(id=department_id).first()
            if d:
                base_complaint = base_complaint.filter(department=d)
                base_action = base_action.filter(department=d)

        # Previous period for comparison
        duration = now - start
        prev_start = start - duration
        prev_end = start

        prev_complaints = Complaint.objects.filter(created_at__gte=prev_start, created_at__lt=prev_end)
        if hospital_id:
            prev_complaints = prev_complaints.filter(hospital_id=hospital_id)
        if department_id:
            prev_complaints = prev_complaints.filter(department_id=department_id)

        # Complaints stats
        total_complaints = base_complaint.count()
        prev_complaint_count = prev_complaints.count()
        resolved = base_complaint.filter(status__in=["resolved", "closed"]).count()
        overdue = base_complaint.filter(is_overdue=True).count()
        critical = base_complaint.filter(severity__in=["high", "critical"]).count()

        # Top categories
        top_cats = list(
            base_complaint.filter(category__isnull=False)
            .values("category__name_en")
            .annotate(c=Count("id"))
            .order_by("-c")[:5]
        )

        # Top departments by complaint volume
        top_depts = list(
            base_complaint.filter(department__isnull=False)
            .values("department__name_en")
            .annotate(c=Count("id"))
            .order_by("-c")[:5]
        )

        # Actions stats
        total_actions = base_action.count()
        open_actions = base_action.filter(status__in=["open", "in_progress"]).count()
        closed_actions = base_action.filter(status="closed").count()
        overdue_actions = base_action.filter(is_overdue=True).count()

        # Survey stats
        total_surveys = base_survey.count()
        avg_score = base_survey.aggregate(avg=Avg("total_score"))["avg"] or 0
        negative = base_survey.filter(is_negative=True).count()
        nps_surveys = base_survey.filter(survey_template__survey_type="nps", total_score__isnull=False)
        if nps_surveys.exists():
            promoters = nps_surveys.filter(total_score__gte=9).count()
            detractors = nps_surveys.filter(total_score__lte=6).count()
            nps = ((promoters - detractors) / nps_surveys.count() * 100) if nps_surveys.count() > 0 else 0
        else:
            nps = 0

        # SLA compliance
        total_with_sla = base_complaint.filter(due_at__isnull=False).count()
        resolved_within = base_complaint.filter(status__in=["resolved", "closed"], resolved_at__lte=F("due_at")).count()
        sla_rate = (resolved_within / total_with_sla * 100) if total_with_sla > 0 else 0

        # Feedback
        total_fb = base_feedback.count()
        compliments = base_feedback.filter(feedback_type="compliment").count()

        # Avg resolution hours
        resolved_with_time = base_complaint.filter(
            status__in=["resolved", "closed"], resolved_at__isnull=False, activated_at__isnull=False
        )
        if resolved_with_time.exists():
            avg_res_hrs = resolved_with_time.annotate(rt=F("resolved_at") - F("activated_at")).aggregate(avg=Avg("rt"))[
                "avg"
            ]
            avg_res_hrs = (avg_res_hrs.total_seconds() / 3600) if avg_res_hrs else 0
        else:
            avg_res_hrs = 0

        complaint_change_pct = 0
        if prev_complaint_count > 0:
            complaint_change_pct = ((total_complaints - prev_complaint_count) / prev_complaint_count) * 100

        # Visit analytics
        from apps.integrations.models import HISPatientVisit

        visit_qs = HISPatientVisit.objects.filter(admit_date__gte=start)
        if hospital_id:
            visit_qs = visit_qs.filter(hospital_id=hospital_id)
        total_visits = visit_qs.count()
        ed_visits = visit_qs.filter(patient_type="ED").count()
        ip_visits = visit_qs.filter(patient_type="IP").count()
        op_visits = visit_qs.filter(patient_type="OP").count()

        avg_visit_duration = {}
        for pt in ["ED", "IP", "OP"]:
            durations = []
            for v in visit_qs.filter(
                patient_type=pt, admit_date__isnull=False, discharge_date__isnull=False
            ).values_list("admit_date", "discharge_date")[:500]:
                d = (v[1] - v[0]).total_seconds() / 60
                if 0 < d < 100000:
                    durations.append(d)
            avg_visit_duration[pt] = round(sum(durations) / len(durations)) if durations else 0

        return {
            "period": period,
            "total_complaints": total_complaints,
            "complaint_change_pct": round(complaint_change_pct, 1),
            "resolved_complaints": resolved,
            "overdue_complaints": overdue,
            "critical_complaints": critical,
            "top_categories": [c["category__name_en"] for c in top_cats],
            "top_departments": [d["department__name_en"] for d in top_depts],
            "total_actions": total_actions,
            "open_actions": open_actions,
            "closed_actions": closed_actions,
            "overdue_actions": overdue_actions,
            "total_surveys": total_surveys,
            "avg_survey_score": round(avg_score, 2),
            "negative_surveys": negative,
            "nps_score": round(nps, 1),
            "sla_compliance": round(sla_rate, 1),
            "total_feedback": total_fb,
            "compliments": compliments,
            "avg_resolution_hours": round(avg_res_hrs, 1),
            "total_visits": total_visits,
            "ed_visits": ed_visits,
            "ip_visits": ip_visits,
            "op_visits": op_visits,
            "avg_visit_duration_ed": avg_visit_duration.get("ED", 0),
            "avg_visit_duration_ip": avg_visit_duration.get("IP", 0),
            "avg_visit_duration_op": avg_visit_duration.get("OP", 0),
        }

    @classmethod
    def generate(cls, user, hospital_id=None, department_id=None, period="30d", force_refresh=False) -> Dict[str, Any]:
        """Generate or return cached executive summary."""
        cache_key = cls._cache_key(hospital_id, department_id, period)
        if not force_refresh:
            cached = cache.get(cache_key)
            if cached:
                return cached

        data = cls._gather_data(user, hospital_id, department_id, period)

        system_prompt = (
            "You are a senior healthcare quality analyst writing an executive summary "
            "for hospital leadership. Your summaries must be concise, data-driven, "
            "and actionable. Always provide BOTH English and Arabic (فصحى معاصرة) versions. "
            "Respond with ONLY valid JSON, no markdown."
        )

        top_cats_str = ", ".join(data["top_categories"]) if data["top_categories"] else "N/A"
        top_depts_str = ", ".join(data["top_departments"]) if data["top_departments"] else "N/A"
        direction = "up" if data["complaint_change_pct"] > 0 else "down"

        user_prompt = f"""Write an executive summary for the past {data["period"]}. Use these exact data points:

COMPLAINTS: {data["total_complaints"]} total ({direction} {abs(data["complaint_change_pct"])}% vs prior period). {data["resolved_complaints"]} resolved, {data["overdue_complaints"]} overdue, {data["critical_complaints"]} critical/high severity. Avg resolution: {data["avg_resolution_hours"]}h. SLA compliance: {data["sla_compliance"]}%.
Top complaint categories: {top_cats_str}
Top departments by volume: {top_depts_str}

ACTIONS: {data["total_actions"]} total, {data["open_actions"]} open/in-progress, {data["closed_actions"]} closed, {data["overdue_actions"]} overdue.

SURVEYS: {data["total_surveys"]} responses. Avg score: {data["avg_survey_score"]}/5. {data["negative_surveys"]} negative. NPS: {data["nps_score"]}.

FEEDBACK: {data["total_feedback"]} items, {data["compliments"]} compliments.

PATIENT VISITS: {data["total_visits"]} total ({data["ed_visits"]} ED, {data["ip_visits"]} IP, {data["op_visits"]} OP). Avg visit duration: ED {data["avg_visit_duration_ed"]}min, IP {data["avg_visit_duration_ip"]}min, OP {data["avg_visit_duration_op"]}min.

Write a 4-5 sentence English summary incorporating visit efficiency alongside complaints/surveys, then a 4-5 sentence Arabic summary, then 3 specific recommended actions. Return ONLY this JSON structure:

{{
  "summary_en": "...",
  "summary_ar": "...",
  "key_findings_en": ["finding 1", "finding 2", "finding 3"],
  "key_findings_ar": ["اكتشاف 1", "اكتشاف 2", "اكتشاف 3"],
  "recommendations_en": ["action 1", "action 2", "action 3"],
  "recommendations_ar": ["إجراء 1", "إجراء 2", "إجراء 3"],
  "risk_level": "low|medium|high"
}}"""

        raw = _client.chat(system_prompt, user_prompt, temperature=0.2, max_tokens=2048)

        if not raw:
            result = {
                "summary_en": "AI summary unavailable — check OpenRouter configuration.",
                "summary_ar": "ملخص الذكاء الاصطناعي غير متاح — تحقق من إعدادات OpenRouter.",
                "key_findings_en": [
                    f"{data['total_complaints']} complaints recorded",
                    f"SLA compliance at {data['sla_compliance']}%",
                    f"NPS: {data['nps_score']}",
                    f"{data['total_visits']} patient visits (avg ED {data['avg_visit_duration_ed']}min, IP {data['avg_visit_duration_ip']}min, OP {data['avg_visit_duration_op']}min)",
                ],
                "key_findings_ar": [
                    f"تم تسجيل {data['total_complaints']} شكوى",
                    f"الالتزام باتفاقية مستوى الخدمة {data['sla_compliance']}%",
                    f"صافي نقاط الترويج: {data['nps_score']}",
                    f"{data['total_visits']} زيارة مريض",
                ],
                "recommendations_en": [
                    "Review overdue complaints",
                    "Address top complaint categories",
                    "Improve SLA compliance",
                ],
                "recommendations_ar": [
                    "مراجعة الشكاوى المتأخرة",
                    "معالجة فئات الشكاوى الرئيسية",
                    "تحسين الالتزام باتفاقية مستوى الخدمة",
                ],
                "risk_level": "medium",
            }
        else:
            try:
                result = json.loads(raw)
            except json.JSONDecodeError:
                result = {
                    "summary_en": raw[:500],
                    "summary_ar": "",
                    "key_findings_en": [],
                    "key_findings_ar": [],
                    "recommendations_en": [],
                    "recommendations_ar": [],
                    "risk_level": "medium",
                }

        result["_data"] = data  # include raw numbers for UI display
        cache.set(cache_key, result, cls.CACHE_TIMEOUT)
        return result


# =============================================================================
# 2. Early Warning System — At-Risk Department Detection
# =============================================================================


class EarlyWarningSystem:
    """
    Detects departments showing risk signals across multiple channels:
    - Rising complaint volume
    - Declining survey scores
    - Increasing SLA breaches
    - Negative social/call center feedback
    Returns ranked list of at-risk departments.
    """

    CACHE_TIMEOUT = 1800  # 30 minutes

    @classmethod
    def _cache_key(cls, hospital_id=None, limit=10) -> str:
        return f"early_warning:{hospital_id or 'all'}:{limit}"

    RISK_WEIGHTS = {
        "complaint_volume_spike": 25,
        "survey_score_decline": 25,
        "sla_breach_increase": 20,
        "negative_feedback_rise": 15,
        "overdue_actions_rise": 15,
    }

    @classmethod
    def detect(cls, user, hospital_id=None, limit=10) -> List[Dict[str, Any]]:
        """Scan all active departments and return those with risk scores > threshold."""
        cache_key = cls._cache_key(hospital_id, limit)
        cached = cache.get(cache_key)
        if cached:
            return cached

        now = timezone.now()
        current_start = now - timedelta(days=30)
        prev_start = now - timedelta(days=60)

        # Get departments
        depts = Department.objects.filter(status="active")
        if hospital_id:
            depts = depts.filter(hospital_id=hospital_id)
        elif hasattr(user, "hospital") and user.hospital and not getattr(user, "is_px_admin", lambda: False)():
            depts = depts.filter(hospital=user.hospital)

        dept_ids = list(depts.values_list("id", flat=True))
        if not dept_ids:
            cache.set(cache_key, [], cls.CACHE_TIMEOUT)
            return []

        # Bulk fetch all metrics in a few queries instead of O(N) per department
        # 1. Complaint counts by department (current + previous)
        curr_complaints = {
            item["department_id"]: item["c"]
            for item in Complaint.objects.filter(department_id__in=dept_ids, created_at__gte=current_start)
            .values("department_id")
            .annotate(c=Count("id"))
        }
        prev_complaints = {
            item["department_id"]: item["c"]
            for item in Complaint.objects.filter(
                department_id__in=dept_ids, created_at__gte=prev_start, created_at__lt=current_start
            )
            .values("department_id")
            .annotate(c=Count("id"))
        }

        # 2. Survey averages by department (current + previous)
        curr_surveys = {
            item["journey_instance__department_id"]: item["a"]
            for item in SurveyInstance.objects.filter(
                journey_instance__department_id__in=dept_ids,
                completed_at__gte=current_start,
                status="completed",
                total_score__isnull=False,
            )
            .values("journey_instance__department_id")
            .annotate(a=Avg("total_score"))
        }
        prev_surveys = {
            item["journey_instance__department_id"]: item["a"]
            for item in SurveyInstance.objects.filter(
                journey_instance__department_id__in=dept_ids,
                completed_at__gte=prev_start,
                completed_at__lt=current_start,
                status="completed",
                total_score__isnull=False,
            )
            .values("journey_instance__department_id")
            .annotate(a=Avg("total_score"))
        }

        # 3. SLA breach counts (current + previous)
        curr_breached = {
            item["department_id"]: item["c"]
            for item in Complaint.objects.filter(
                department_id__in=dept_ids, created_at__gte=current_start, is_overdue=True
            )
            .values("department_id")
            .annotate(c=Count("id"))
        }
        prev_breached = {
            item["department_id"]: item["c"]
            for item in Complaint.objects.filter(
                department_id__in=dept_ids, created_at__gte=prev_start, created_at__lt=current_start, is_overdue=True
            )
            .values("department_id")
            .annotate(c=Count("id"))
        }

        # 4. Negative feedback counts (current + previous)
        curr_neg_fb = {
            item["department_id"]: item["c"]
            for item in Feedback.objects.filter(
                department_id__in=dept_ids, created_at__gte=current_start, sentiment="negative"
            )
            .values("department_id")
            .annotate(c=Count("id"))
        }
        prev_neg_fb = {
            item["department_id"]: item["c"]
            for item in Feedback.objects.filter(
                department_id__in=dept_ids,
                created_at__gte=prev_start,
                created_at__lt=current_start,
                sentiment="negative",
            )
            .values("department_id")
            .annotate(c=Count("id"))
        }

        # 5. Overdue actions counts (current + previous)
        curr_overdue_actions = {
            item["department_id"]: item["c"]
            for item in PXAction.objects.filter(
                department_id__in=dept_ids, created_at__gte=current_start, is_overdue=True
            )
            .values("department_id")
            .annotate(c=Count("id"))
        }
        prev_overdue_actions = {
            item["department_id"]: item["c"]
            for item in PXAction.objects.filter(
                department_id__in=dept_ids, created_at__gte=prev_start, created_at__lt=current_start, is_overdue=True
            )
            .values("department_id")
            .annotate(c=Count("id"))
        }

        results = []
        for dept in depts:
            did = dept.id
            signals = {}
            risk_score = 0

            # 1. Complaint volume spike
            cc = curr_complaints.get(did, 0)
            pc = prev_complaints.get(did, 0)
            change_pct = ((cc - pc) / pc * 100) if pc > 0 else (100 if cc > 0 else 0)
            volume_score = min(change_pct / 50 * 100, 100)
            risk_score += volume_score * cls.RISK_WEIGHTS["complaint_volume_spike"] / 100
            signals["complaint_volume_spike"] = {
                "current": cc,
                "previous": pc,
                "change_pct": round(change_pct, 1),
                "score": round(volume_score, 1),
            }

            # 2. Survey score decline
            csa = curr_surveys.get(did, 0) or 0
            psa = prev_surveys.get(did, 0) or 0
            survey_change = ((csa - psa) / psa * 100) if psa > 0 else 0
            decline_score = max(0, min(-survey_change / 20 * 100, 100))
            risk_score += decline_score * cls.RISK_WEIGHTS["survey_score_decline"] / 100
            signals["survey_score_decline"] = {
                "current_avg": round(csa, 2),
                "previous_avg": round(psa, 2),
                "change_pct": round(survey_change, 1),
                "score": round(decline_score, 1),
            }

            # 3. SLA breach increase
            cb = curr_breached.get(did, 0)
            ct = curr_complaints.get(did, 0)
            pb = prev_breached.get(did, 0)
            pt = prev_complaints.get(did, 0)
            curr_breach_rate = (cb / ct * 100) if ct > 0 else 0
            prev_breach_rate = (pb / pt * 100) if pt > 0 else 0
            breach_change = (curr_breach_rate - prev_breach_rate) if prev_breach_rate > 0 else curr_breach_rate
            sla_score = min(max(breach_change / 20 * 100, 0), 100)
            risk_score += sla_score * cls.RISK_WEIGHTS["sla_breach_increase"] / 100
            signals["sla_breach_increase"] = {
                "current_rate": round(curr_breach_rate, 1),
                "previous_rate": round(prev_breach_rate, 1),
                "change_pp": round(breach_change, 1),
                "score": round(sla_score, 1),
            }

            # 4. Negative feedback rise
            cn = curr_neg_fb.get(did, 0)
            pn = prev_neg_fb.get(did, 0)
            fb_change = ((cn - pn) / pn * 100) if pn > 0 else (100 if cn > 0 else 0)
            fb_score = min(max(fb_change / 50 * 100, 0), 100)
            risk_score += fb_score * cls.RISK_WEIGHTS["negative_feedback_rise"] / 100
            signals["negative_feedback_rise"] = {
                "current": cn,
                "previous": pn,
                "change_pct": round(fb_change, 1),
                "score": round(fb_score, 1),
            }

            # 5. Overdue actions rise
            co = curr_overdue_actions.get(did, 0)
            po = prev_overdue_actions.get(did, 0)
            overdue_change = ((co - po) / po * 100) if po > 0 else (100 if co > 0 else 0)
            overdue_score = min(max(overdue_change / 50 * 100, 0), 100)
            risk_score += overdue_score * cls.RISK_WEIGHTS["overdue_actions_rise"] / 100
            signals["overdue_actions_rise"] = {
                "current": co,
                "previous": po,
                "change_pct": round(overdue_change, 1),
                "score": round(overdue_score, 1),
            }

            signals["risk_score"] = round(risk_score, 1)
            signals["risk_level"] = (
                "critical"
                if risk_score >= 70
                else "high"
                if risk_score >= 50
                else "medium"
                if risk_score >= 30
                else "low"
            )
            signals["active_signals"] = sum(
                1
                for k in [
                    "complaint_volume_spike",
                    "survey_score_decline",
                    "sla_breach_increase",
                    "negative_feedback_rise",
                    "overdue_actions_rise",
                ]
                if signals.get(k, {}).get("score", 0) > 30
            )

            if risk_score > 20:
                signals["department_id"] = str(dept.id)
                signals["department_name"] = dept.name_en if hasattr(dept, "name_en") else str(dept)
                results.append(signals)

        results.sort(key=lambda x: x["risk_score"], reverse=True)
        results = results[:limit]

        cache.set(cache_key, results, cls.CACHE_TIMEOUT)
        return results


# =============================================================================
# 3. Predictive Complaint Volume — Time-Series Forecasting
# =============================================================================


class ComplaintVolumeForecaster:
    """
    Uses weighted moving average + seasonality detection to forecast
    complaint volumes for the next 30/60/90 days with confidence bands.
    """

    CACHE_TIMEOUT = 3600  # 1 hour

    @classmethod
    def _cache_key(cls, hospital_id=None, forecast_days=30) -> str:
        return f"complaint_forecast:{hospital_id or 'all'}:{forecast_days}"

    @classmethod
    def forecast(cls, user, hospital_id=None, forecast_days=30) -> Dict[str, Any]:
        cache_key = cls._cache_key(hospital_id, forecast_days)
        cached = cache.get(cache_key)
        if cached:
            return cached

        now = timezone.now()
        # Need at least 90 days of history
        history_start = now - timedelta(days=120)

        qs = Complaint.objects.filter(created_at__gte=history_start)
        if hospital_id:
            qs = qs.filter(hospital_id=hospital_id)
        elif hasattr(user, "hospital") and user.hospital and not getattr(user, "is_px_admin", lambda: False)():
            qs = qs.filter(hospital=user.hospital)

        # Aggregate daily counts
        daily = list(
            qs.extra(select={"date": "DATE(created_at)"}).values("date").annotate(count=Count("id")).order_by("date")
        )

        if len(daily) < 14:
            return cls._insufficient_data_response(forecast_days)

        # Build time series
        dates = []
        counts = []
        current = history_start.date()
        end = now.date()
        date_map = {str(d["date"]): d["count"] for d in daily}

        while current <= end:
            dates.append(current)
            counts.append(date_map.get(str(current), 0))
            current += timedelta(days=1)

        # 7-day weighted moving average forecast
        weights = [0.05, 0.08, 0.12, 0.15, 0.20, 0.25, 0.15]  # recent days weighted more
        predictions = []
        history = list(counts)

        for day_offset in range(1, forecast_days + 1):
            recent_7 = history[-7:]
            # Pad if needed
            while len(recent_7) < 7:
                recent_7.insert(0, recent_7[0] if recent_7 else 0)

            wma = sum(w * v for w, v in zip(weights, recent_7))
            predicted = max(0, round(wma, 1))
            predictions.append(predicted)
            history.append(predicted)  # feed back for next prediction

        # Confidence bands widen with forecast horizon
        recent_std = cls._std_dev(counts[-14:]) if len(counts) >= 14 else cls._std_dev(counts)
        forecast_dates = [now.date() + timedelta(days=i) for i in range(1, forecast_days + 1)]

        result_labels = [d.strftime("%Y-%m-%d") for d in forecast_dates]
        upper_band = []
        lower_band = []
        for i, pred in enumerate(predictions):
            # Confidence widens: 1 std at day 1, 3 std at day 30
            std_multiplier = 1 + (i / forecast_days) * 2
            margin = recent_std * std_multiplier
            upper_band.append(round(pred + margin, 1))
            lower_band.append(round(max(0, pred - margin), 1))

        # Detect seasonality signal (day-of-week patterns)
        dow_pattern = cls._detect_day_of_week_pattern(counts, dates)

        total_predicted = round(sum(predictions), 0)
        recent_30 = sum(counts[-30:]) if len(counts) >= 30 else sum(counts)
        change_pct = ((total_predicted - recent_30) / recent_30 * 100) if recent_30 > 0 else 0

        result = {
            "labels": result_labels,
            "predicted": predictions,
            "upper_band": upper_band,
            "lower_band": lower_band,
            "total_predicted_30d": int(total_predicted),
            "recent_30d_actual": recent_30,
            "change_pct": round(change_pct, 1),
            "confidence_level": "high" if len(counts) > 60 else "medium" if len(counts) > 30 else "low",
            "day_of_week_pattern": dow_pattern,
        }

        cache.set(cache_key, result, cls.CACHE_TIMEOUT)
        return result

    @staticmethod
    def _std_dev(values):
        if not values:
            return 0
        mean = sum(values) / len(values)
        variance = sum((x - mean) ** 2 for x in values) / len(values)
        return math.sqrt(variance)

    @staticmethod
    def _detect_day_of_week_pattern(counts, dates):
        """Detect if certain days of week have higher complaint volumes."""
        if len(counts) < 14:
            return {}

        dow_totals = {i: [] for i in range(7)}
        for date, count in zip(dates, counts):
            dow_totals[date.weekday()].append(count)

        dow_avgs = {
            ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"][k]: round(sum(v) / len(v), 1) if v else 0
            for k, v in dow_totals.items()
        }

        overall_avg = sum(dow_avgs.values()) / 7
        pattern = {
            day: "above_average"
            if avg > overall_avg * 1.1
            else "below_average"
            if avg < overall_avg * 0.9
            else "average"
            for day, avg in dow_avgs.items()
        }
        return pattern

    @staticmethod
    def _insufficient_data_response(forecast_days):
        return {
            "labels": [],
            "predicted": [],
            "upper_band": [],
            "lower_band": [],
            "total_predicted_30d": 0,
            "recent_30d_actual": 0,
            "change_pct": 0,
            "confidence_level": "insufficient_data",
            "day_of_week_pattern": {},
            "message": "At least 14 days of historical data required for forecasting.",
        }


# =============================================================================
# 4. SLA Breach Prediction
# =============================================================================


class SLABreachPredictor:
    """
    Predicts which open/in-progress complaints are at risk of breaching SLA.
    Uses factors: age, severity, assigned staff workload, historical resolution time.
    """

    CACHE_TIMEOUT = 900  # 15 minutes

    @classmethod
    def _cache_key(cls, hospital_id=None, limit=20) -> str:
        return f"sla_breach_pred:{hospital_id or 'all'}:{limit}"

    @classmethod
    def predict(cls, user, hospital_id=None, limit=20) -> List[Dict[str, Any]]:
        """Return complaints ranked by breach probability."""
        cache_key = cls._cache_key(hospital_id, limit)
        cached = cache.get(cache_key)
        if cached:
            return cached

        now = timezone.now()
        qs = Complaint.objects.filter(
            status__in=["open", "in_progress"],
            due_at__isnull=False,
        )

        if hospital_id:
            qs = qs.filter(hospital_id=hospital_id)
        elif hasattr(user, "hospital") and user.hospital and not getattr(user, "is_px_admin", lambda: False)():
            qs = qs.filter(hospital=user.hospital)

        qs = qs.select_related("hospital", "department", "source", "assigned_to").order_by("due_at")
        complaints = list(qs[: limit * 2])

        # Pre-compute workloads in ONE query instead of N
        assignee_ids = {c.assigned_to_id for c in complaints if c.assigned_to_id}
        workload_map = {}
        if assignee_ids:
            workload_qs = (
                Complaint.objects.filter(
                    assigned_to_id__in=assignee_ids,
                    status__in=["open", "in_progress"],
                )
                .values("assigned_to_id")
                .annotate(c=Count("id"))
            )
            workload_map = {item["assigned_to_id"]: item["c"] for item in workload_qs}

        # Pre-compute average resolution time per (severity, department) in ONE query
        dept_ids = {c.department_id for c in complaints if c.department_id}
        severities = {c.severity for c in complaints if c.severity}
        resolution_map = {}
        if severities:
            base_res = (
                Complaint.objects.filter(
                    status__in=["resolved", "closed"],
                    severity__in=severities,
                    resolved_at__isnull=False,
                    activated_at__isnull=False,
                )
                .annotate(rt=F("resolved_at") - F("activated_at"))
                .values("severity", "department_id")
                .annotate(avg=Avg("rt"))
            )
            for item in base_res:
                key = (item["severity"], item["department_id"])
                avg_val = item["avg"]
                resolution_map[key] = avg_val.total_seconds() / 3600 if avg_val else None

            # Also compute severity-only averages for departments with no match
            base_res_sev_only = (
                Complaint.objects.filter(
                    status__in=["resolved", "closed"],
                    severity__in=severities,
                    resolved_at__isnull=False,
                    activated_at__isnull=False,
                )
                .annotate(rt=F("resolved_at") - F("activated_at"))
                .values("severity")
                .annotate(avg=Avg("rt"))
            )
            for item in base_res_sev_only:
                key = (item["severity"], None)
                avg_val = item["avg"]
                resolution_map[key] = avg_val.total_seconds() / 3600 if avg_val else None

        results = []
        for complaint in complaints:
            prediction = cls._predict_complaint_breach(complaint, now, workload_map, resolution_map)
            if prediction["breach_probability"] > 30:  # only show > 30% risk
                prediction["complaint_id"] = str(complaint.id)
                prediction["title"] = complaint.title
                prediction["severity"] = complaint.severity
                prediction["status"] = complaint.status
                prediction["due_at"] = complaint.due_at.isoformat() if complaint.due_at else None
                prediction["hours_remaining"] = (
                    round((complaint.due_at - now).total_seconds() / 3600, 1) if complaint.due_at else None
                )
                if complaint.department:
                    prediction["department"] = (
                        complaint.department.name_en
                        if hasattr(complaint.department, "name_en")
                        else str(complaint.department)
                    )
                if complaint.assigned_to:
                    prediction["assigned_to"] = f"{complaint.assigned_to.first_name} {complaint.assigned_to.last_name}"

                results.append(prediction)

        results.sort(key=lambda x: x["breach_probability"], reverse=True)
        results = results[:limit]

        cache.set(cache_key, results, cls.CACHE_TIMEOUT)
        return results

    @classmethod
    def _predict_complaint_breach(cls, complaint, now, workload_map, resolution_map) -> Dict[str, Any]:
        """Calculate breach probability for a single complaint using pre-computed bulk data."""
        probability = 0
        factors = []

        # Factor 1: Time remaining (0-35 points)
        if complaint.due_at:
            hours_remaining = (complaint.due_at - now).total_seconds() / 3600
            total_sla_hours = (
                (complaint.due_at - complaint.created_at).total_seconds() / 3600 if complaint.created_at else 24
            )

            if hours_remaining <= 0:
                time_score = 35
                factors.append("SLA already expired")
            elif hours_remaining < 4:
                time_score = 30
                factors.append(f"Only {hours_remaining:.0f}h remaining")
            elif hours_remaining < 12:
                time_score = 20
                factors.append(f"{hours_remaining:.0f}h remaining — urgent window")
            elif hours_remaining < 24:
                time_score = 10
                factors.append(f"{hours_remaining:.0f}h remaining")
            else:
                time_pct = 1 - (hours_remaining / total_sla_hours) if total_sla_hours > 0 else 0
                time_score = min(time_pct * 35, 35)
                if time_pct > 0.7:
                    factors.append(f"{time_pct * 100:.0f}% of SLA time consumed")
        else:
            time_score = 15
            factors.append("No SLA deadline set")

        probability += time_score

        # Factor 2: Severity (0-20 points)
        severity_scores = {"critical": 20, "high": 15, "medium": 8, "low": 3}
        sev_score = severity_scores.get(complaint.severity, 10)
        probability += sev_score
        if sev_score >= 15:
            factors.append(f"{complaint.severity.capitalize()} severity — typically slower to resolve")

        # Factor 3: Assignment status (0-15 points)
        if not complaint.assigned_to_id:
            probability += 15
            factors.append("Unassigned — no owner yet")
        else:
            workload = workload_map.get(complaint.assigned_to_id, 0)
            if workload >= 10:
                probability += 12
                factors.append(f"Assignee has {workload} active cases — overloaded")
            elif workload >= 5:
                probability += 6
                factors.append(f"Assignee has {workload} active cases — moderate load")

        # Factor 4: Historical resolution time for similar complaints (0-20 points)
        avg_hrs = None
        if complaint.department_id:
            avg_hrs = resolution_map.get((complaint.severity, complaint.department_id))
        if avg_hrs is None:
            avg_hrs = resolution_map.get((complaint.severity, None))

        if avg_hrs is not None:
            total_sla_hrs = (
                (complaint.due_at - complaint.created_at).total_seconds() / 3600
                if complaint.due_at and complaint.created_at
                else 24
            )

            if avg_hrs > total_sla_hrs * 0.9:
                probability += 18
                factors.append(f"Similar complaints avg {avg_hrs:.0f}h to resolve — exceeds SLA")
            elif avg_hrs > total_sla_hrs * 0.7:
                probability += 10
                factors.append(f"Similar complaints avg {avg_hrs:.0f}h — close to SLA limit")

        # Factor 5: Age without progress (0-10 points)
        if complaint.created_at:
            age_hours = (now - complaint.created_at).total_seconds() / 3600
            if complaint.status == "open" and age_hours > 24:
                probability += 10
                factors.append(f"Open for {age_hours:.0f}h without status change")
            elif complaint.status == "open" and age_hours > 12:
                probability += 5
                factors.append(f"Open for {age_hours:.0f}h")

        probability = min(round(probability, 1), 100)

        return {
            "breach_probability": probability,
            "risk_factors": factors,
            "recommendation": cls._get_recommendation(probability, factors),
        }

    @staticmethod
    def _get_recommendation(probability, factors):
        if probability >= 80:
            return "Immediate escalation required"
        elif probability >= 60:
            return "Escalate or reassign to available staff"
        elif probability >= 40:
            return "Add priority flag and monitor closely"
        else:
            return "On track — continue standard monitoring"


# =============================================================================
# 5. Automated Action Recommendations
# =============================================================================


class ActionRecommendationEngine:
    """
    Analyzes clusters of similar complaints and recommends specific
    PX Actions based on historical resolution patterns.
    """

    CACHE_TIMEOUT = 3600  # 1 hour

    @classmethod
    def _cache_key(cls, hospital_id=None, department_id=None, limit=5) -> str:
        return f"action_recommendations:{hospital_id or 'all'}:{department_id or 'all'}:{limit}"

    @classmethod
    def generate_recommendations(cls, user, hospital_id=None, department_id=None, limit=5) -> List[Dict[str, Any]]:
        """Generate AI-powered action recommendations from complaint analysis."""
        cache_key = cls._cache_key(hospital_id, department_id, limit)
        cached = cache.get(cache_key)
        if cached:
            return cached

        # Gather recent resolved complaints with resolution data
        now = timezone.now()
        start = now - timedelta(days=90)

        qs = Complaint.objects.filter(
            status__in=["resolved", "closed"],
            created_at__gte=start,
            resolution__isnull=False,
        )

        if hospital_id:
            qs = qs.filter(hospital_id=hospital_id)
        elif hasattr(user, "hospital") and user.hospital and not getattr(user, "is_px_admin", lambda: False)():
            qs = qs.filter(hospital=user.hospital)
        if department_id:
            qs = qs.filter(department_id=department_id)

        qs = qs.select_related("department").order_by("-resolved_at")

        # Group by category/domain
        category_data = {}
        for c in qs[:200]:
            cat = c.category if hasattr(c, "category") and c.category else "uncategorized"
            if cat not in category_data:
                category_data[cat] = {
                    "count": 0,
                    "resolutions": [],
                    "severities": [],
                    "departments": set(),
                }
            category_data[cat]["count"] += 1
            if hasattr(c, "resolution") and c.resolution:
                category_data[cat]["resolutions"].append(c.resolution[:200])
            if hasattr(c, "severity"):
                category_data[cat]["severities"].append(c.severity)
            if c.department:
                dept_name = c.department.name_en if hasattr(c.department, "name_en") else str(c.department)
                category_data[cat]["departments"].add(dept_name)

        # Filter to categories with enough volume (at least 3 complaints)
        significant_categories = {k: v for k, v in category_data.items() if v["count"] >= 3}

        if not significant_categories:
            return cls._no_data_response()

        # Use AI to generate recommendations for top categories
        recommendations = []
        top_categories = sorted(significant_categories.items(), key=lambda x: x[1]["count"], reverse=True)[:limit]

        for category, data in top_categories:
            rec = cls._generate_ai_recommendation(category, data)
            if rec:
                recommendations.append(rec)

        # If AI is unavailable, fall back to rule-based recommendations
        if not recommendations:
            recommendations = cls._generate_rule_based_recommendations(top_categories)

        cache.set(cache_key, recommendations, cls.CACHE_TIMEOUT)
        return recommendations

    @classmethod
    def _generate_ai_recommendation(cls, category, data) -> Optional[Dict[str, Any]]:
        """Use OpenRouter to generate a recommendation for a complaint cluster."""
        if not _client.is_configured():
            return None

        system_prompt = (
            "You are a healthcare quality improvement expert. Given a cluster of similar complaints "
            "and their resolutions, recommend specific, actionable improvement steps. "
            "Respond with ONLY valid JSON, no markdown."
        )

        resolutions_sample = (
            "\n".join(data["resolutions"][:5]) if data["resolutions"] else "No resolution data available."
        )
        most_common_sev = max(set(data["severities"]), key=data["severities"].count) if data["severities"] else "medium"
        depts_str = ", ".join(list(data["departments"])[:3]) if data["departments"] else "Multiple departments"

        user_prompt = f"""Category: {category}
Complaint count: {data["count"]}
Most common severity: {most_common_sev}
Affected departments: {depts_str}

Sample resolutions:
{resolutions_sample}

Based on this pattern, recommend 2-3 specific, actionable improvement actions. Return ONLY this JSON:

{{
  "category": "{category}",
  "problem_summary_en": "Brief description of the systemic issue",
  "problem_summary_ar": "وصف موجز للمشكلة المنهجية",
  "complaint_count": {data["count"]},
  "affected_departments": ["dept1", "dept2"],
  "recommended_actions_en": ["Action 1", "Action 2", "Action 3"],
  "recommended_actions_ar": ["إجراء 1", "إجراء 2", "إجراء 3"],
  "expected_impact_en": "Expected improvement if actions are implemented",
  "priority": "low|medium|high|critical",
  "action_category": "clinical_quality|patient_safety|service_quality|staff_behavior|facility|process_improvement|other"
}}"""

        raw = _client.chat(system_prompt, user_prompt, temperature=0.2, max_tokens=1024)

        if not raw:
            return None

        try:
            result = json.loads(raw)
            result["source"] = "ai_generated"
            return result
        except json.JSONDecodeError:
            return None

    @classmethod
    def _generate_rule_based_recommendations(cls, top_categories) -> List[Dict[str, Any]]:
        """Fallback: generate recommendations without AI."""
        recommendations = []
        category_templates = {
            "wait_time": {
                "problem_en": "Recurring complaints about excessive wait times",
                "problem_ar": "شكاوى متكررة حول أوقات الانتظار الطويلة",
                "actions_en": [
                    "Review scheduling system",
                    "Add real-time wait tracking",
                    "Implement patient communication updates",
                ],
                "actions_ar": ["مراجعة نظام الجدولة", "إضافة تتبع وقت الانتظار", "تطبيق تحديثات التواصل مع المرضى"],
                "impact_en": "Reduce average wait time by 30% and improve patient satisfaction",
                "category": "process_improvement",
                "priority": "high",
            },
            "staff_behavior": {
                "problem_en": "Pattern of complaints about staff communication and behavior",
                "problem_ar": "نمط من الشكاوى حول سلوك وتواصل الموظفين",
                "actions_en": [
                    "Conduct patient communication training",
                    "Implement feedback loop for staff performance",
                    "Establish patient relations team",
                ],
                "actions_ar": [
                    "إجراء تدريب على التواصل مع المرضى",
                    "تطبيق حلقة التغذية الراجعة لأداء الموظفين",
                    "إنشاء فريق علاقات المرضى",
                ],
                "impact_en": "Improve staff-patient relationships and reduce behavior-related complaints",
                "category": "staff_behavior",
                "priority": "high",
            },
            "clinical": {
                "problem_en": "Clinical quality and care delivery concerns",
                "problem_ar": "مخاوف حول الجودة السريرية وتقديم الرعاية",
                "actions_en": ["Conduct clinical audit", "Review care protocols", "Implement peer review process"],
                "actions_ar": ["إجراء مراجعة سريرية", "مراجعة بروتوكولات الرعاية", "تطبيق عملية مراجعة الأقران"],
                "impact_en": "Improve clinical outcomes and patient safety metrics",
                "category": "clinical_quality",
                "priority": "critical",
            },
            "facility": {
                "problem_en": "Facility condition and environmental complaints",
                "problem_ar": "شكاوى حول حالة المنشأة والبيئة",
                "actions_en": ["Conduct facility inspection", "Create maintenance schedule", "Upgrade affected areas"],
                "actions_ar": ["إجراء فحص للمنشأة", "إنشاء جدول صيانة", "ترقية المناطق المتأثرة"],
                "impact_en": "Improve patient environment and facility standards compliance",
                "category": "facility",
                "priority": "medium",
            },
        }

        for category, data in top_categories:
            template = None
            for key, tmpl in category_templates.items():
                if key in category.lower():
                    template = tmpl
                    break

            if not template:
                template = {
                    "problem_en": f"Recurring complaints in {category}",
                    "problem_ar": f"شكاوى متكررة في {category}",
                    "actions_en": [
                        "Investigate complaint pattern",
                        "Develop corrective action plan",
                        "Monitor improvement metrics",
                    ],
                    "actions_ar": ["التحقيق في نمط الشكاوى", "وضع خطة عمل تصحيحية", "مراقبة مقاييس التحسين"],
                    "impact_en": f"Reduce {category}-related complaints",
                    "category": "other",
                    "priority": "medium",
                }

            recommendations.append(
                {
                    "category": category,
                    "problem_summary_en": template["problem_en"],
                    "problem_summary_ar": template["problem_ar"],
                    "complaint_count": data["count"],
                    "affected_departments": list(data["departments"])[:5],
                    "recommended_actions_en": template["actions_en"],
                    "recommended_actions_ar": template["actions_ar"],
                    "expected_impact_en": template["impact_en"],
                    "priority": template["priority"],
                    "action_category": template["category"],
                    "source": "rule_based",
                }
            )

        return recommendations

    @staticmethod
    def _no_data_response() -> List[Dict[str, Any]]:
        return [
            {
                "category": "general",
                "problem_summary_en": "Insufficient resolved complaint data to identify patterns",
                "problem_summary_ar": "بيانات غير كافية لتحديد الأنماط",
                "complaint_count": 0,
                "affected_departments": [],
                "recommended_actions_en": [
                    "Ensure all resolved complaints have resolution notes",
                    "Review complaint categorization",
                    "Build historical resolution patterns",
                ],
                "recommended_actions_ar": [
                    "ضمان وجود ملاحظات حل لجميع الشكاوى المحلولة",
                    "مراجعة تصنيف الشكاوى",
                    "بناء أنماط الحل التاريخية",
                ],
                "expected_impact_en": "Enable data-driven action recommendations",
                "priority": "medium",
                "action_category": "process_improvement",
                "source": "system",
            }
        ]


class VisitEfficiencyAnalyzer:
    """
    Analyzes patient visit journey stage durations and generates
    AI-powered bottleneck detection and efficiency recommendations.
    """

    CACHE_TIMEOUT = 3600

    STAGE_CATEGORIES = {
        "Registration": ["consultation", "registration", "triage", "admission"],
        "Lab": ["lab", "sample"],
        "Radiology": ["rad", "radiology"],
        "Pharmacy": ["drug", "pharmacy"],
        "Doctor": ["doctor", "procedure", "episode"],
    }
    STAGE_ORDER = ["Registration", "Lab", "Radiology", "Pharmacy", "Doctor", "Other"]

    @classmethod
    def _cache_key(cls, hospital_id=None) -> str:
        return f"visit_efficiency:{hospital_id or 'all'}"

    @classmethod
    def _classify_event(cls, event_type):
        et = (event_type or "").lower()
        for cat, keywords in cls.STAGE_CATEGORIES.items():
            if any(kw in et for kw in keywords):
                return cat
        return "Other"

    @classmethod
    def _gather_stage_data(cls, hospital_id=None) -> Dict[str, Any]:
        from apps.integrations.models import HISPatientVisit, HISVisitEvent

        visit_qs = HISPatientVisit.objects.all()
        if hospital_id:
            visit_qs = visit_qs.filter(hospital_id=hospital_id)

        stage_data = {"stages": cls.STAGE_ORDER}
        sample_sizes = {}

        for pt in ["ED", "IP", "OP"]:
            visit_ids = list(
                visit_qs.filter(patient_type=pt, visit_events__isnull=False)
                .distinct()
                .values_list("id", flat=True)[:300]
            )

            stage_totals = {s: [] for s in cls.STAGE_ORDER}

            for vid in visit_ids:
                events = list(
                    HISVisitEvent.objects.filter(visit_id=vid, parsed_date__isnull=False).order_by(
                        "parsed_date"
                    )
                )
                visit_stage_time = {s: 0.0 for s in cls.STAGE_ORDER}

                for i in range(len(events) - 1):
                    gap = (events[i + 1].parsed_date - events[i].parsed_date).total_seconds() / 60
                    if gap > 0:
                        cat = cls._classify_event(events[i].event_type)
                        visit_stage_time[cat] += gap

                for s in cls.STAGE_ORDER:
                    if visit_stage_time[s] > 0:
                        stage_totals[s].append(visit_stage_time[s])

            stage_data[pt] = [
                round(sum(stage_totals[s]) / len(stage_totals[s])) if stage_totals[s] else 0
                for s in cls.STAGE_ORDER
            ]
            sample_sizes[pt] = len(visit_ids)

        stage_data["sample_sizes"] = sample_sizes
        return stage_data

    @classmethod
    def analyze(cls, user, hospital_id=None, force_refresh=False) -> Dict[str, Any]:
        cache_key = cls._cache_key(hospital_id)
        if not force_refresh:
            cached = cache.get(cache_key)
            if cached:
                return cached

        data = cls._gather_stage_data(hospital_id)

        has_data = any(
            data.get(pt) and any(v > 0 for v in data[pt]) for pt in ["ED", "IP", "OP"]
        )

        if not has_data:
            result = {
                "bottlenecks_en": [],
                "bottlenecks_ar": [],
                "recommendations_en": [],
                "recommendations_ar": [],
                "efficiency_score": None,
                "priority_type": None,
                "summary_en": "Insufficient visit journey data for analysis. Visit events need to be synced from HIS.",
                "summary_ar": "بيانات رحلة الزيارة غير كافية للتحليل. يجب مزامنة أحداث الزيارة من النظام.",
                "_data": data,
            }
            cache.set(cache_key, result, cls.CACHE_TIMEOUT)
            return result

        system_prompt = (
            "You are a healthcare operations analyst specializing in patient flow "
            "optimization. Analyze the visit journey stage data below and identify "
            "bottlenecks and efficiency improvements. Be specific — mention exact "
            "stage names and durations. Always provide BOTH English and Arabic "
            "(فصحى معاصرة) versions. Respond with ONLY valid JSON, no markdown."
        )

        def _fmt(m):
            if m <= 0:
                return "0"
            if m < 60:
                return f"{m}min"
            h = m // 60
            mins = m % 60
            return f"{h}h {mins}m" if mins else f"{h}h"

        stage_lines = []
        for pt in ["ED", "IP", "OP"]:
            vals = data.get(pt, [0] * 6)
            parts = [f"{cls.STAGE_ORDER[i]}={_fmt(vals[i])}" for i in range(6)]
            total = sum(vals)
            stage_lines.append(
                f"{pt} ({data['sample_sizes'].get(pt, 0)} visits sampled): "
                + ", ".join(parts)
                + f". Total={_fmt(total)}."
            )

        user_prompt = f"""VISIT JOURNEY STAGE DATA (average time per stage in minutes):

{chr(10).join(stage_lines)}

Identify the top 3 bottlenecks (longest or most disproportionate stages) and recommend 
3 specific operational improvements to reduce patient wait times and improve throughput.

Return ONLY this JSON:
{{
  "bottlenecks_en": ["bottleneck 1 with specific data", "bottleneck 2", "bottleneck 3"],
  "bottlenecks_ar": ["عنق الزجاجة 1", "عنق الزجاجة 2", "عنق الزجاجة 3"],
  "recommendations_en": ["recommendation 1", "recommendation 2", "recommendation 3"],
  "recommendations_ar": ["التوصية 1", "التوصية 2", "التوصية 3"],
  "efficiency_score": <0-100>,
  "priority_type": "<ED|IP|OP — which type needs most attention>",
  "summary_en": "<2-3 sentence operational summary>",
  "summary_ar": "<ملخص تشغيلي من جملتين إلى 3>"
}}"""

        raw = _client.chat(system_prompt, user_prompt, temperature=0.3, max_tokens=2048)

        if raw:
            try:
                result = json.loads(raw)
            except json.JSONDecodeError:
                result = cls._fallback_analysis(data)
        else:
            result = cls._fallback_analysis(data)

        result["_data"] = data
        cache.set(cache_key, result, cls.CACHE_TIMEOUT)
        return result

    @classmethod
    def _fallback_analysis(cls, data) -> Dict[str, Any]:
        bottlenecks = []
        for pt in ["ED", "IP", "OP"]:
            vals = data.get(pt, [0] * 6)
            if vals:
                max_idx = max(range(6), key=lambda i: vals[i])
                max_stage = cls.STAGE_ORDER[max_idx]
                max_val = vals[max_idx]
                if max_val > 0:
                    bottlenecks.append(f"{pt} bottleneck: {max_stage} averaging {max_val}min")

        total_ed = sum(data.get("ED", []))
        total_ip = sum(data.get("IP", []))
        total_op = sum(data.get("OP", []))
        priority = "ED" if total_ed >= total_ip and total_ed >= total_op else ("IP" if total_ip >= total_op else "OP")

        return {
            "bottlenecks_en": bottlenecks[:3] or ["Insufficient data for bottleneck analysis"],
            "bottlenecks_ar": bottlenecks[:3] or ["بيانات غير كافية لتحليل الاختناقات"],
            "recommendations_en": [
                "Review staffing allocation during peak hours",
                "Implement parallel processing for Lab and Radiology",
                "Track real-time patient flow to identify live bottlenecks",
            ],
            "recommendations_ar": [
                "مراجعة توزيع الموظفين خلال ساعات الذروة",
                "تنفيذ المعالجة المتوازية للمختبر والأشعة",
                "تتبع تدفق المرضى في الوقت الفعلي لتحديد الاختناقات",
            ],
            "efficiency_score": 50,
            "priority_type": priority,
            "summary_en": "Visit efficiency analysis generated from algorithmic fallback. Configure AI service for detailed insights.",
            "summary_ar": "تحليل كفاءة الزيارة من النظام الاحتياطي. قم بتكوين خدمة الذكاء الاصطناعي للحصول على رؤى مفصلة.",
        }
