"""
Vistas — App Dashboard Analytics
===================================
Panel de control visual con métricas clave, gráficas Chart.js y tablas dinámicas.
AJAX: Filtro de rango de fechas actualiza todo sin recargar la página.
Permisos: ADMIN → datos globales · DOCTORA → solo sus datos.
"""

import logging
from datetime import date, timedelta
from decimal import Decimal

from django.contrib.auth.decorators import login_required
from django.db.models import Avg, Count, Q, Sum
from django.db.models.functions import ExtractWeek, TruncMonth, TruncWeek
from django.http import HttpResponse, JsonResponse
from django.shortcuts import render
from django.utils import timezone

from apps.users.decorators import moderador_required

logger = logging.getLogger("apps.dashboard")


# ── Helpers ───────────────────────────────────────────────────
def _parse_dates(request):
    """Extrae fecha_inicio / fecha_fin de GET, default: mes en curso completo."""
    import calendar
    hoy = timezone.now().date()
    try:
        fi = date.fromisoformat(request.GET.get("fecha_inicio", ""))
    except (ValueError, TypeError):
        fi = hoy.replace(day=1)
    try:
        ff = date.fromisoformat(request.GET.get("fecha_fin", ""))
    except (ValueError, TypeError):
        ultimo_dia = calendar.monthrange(hoy.year, hoy.month)[1]
        ff = hoy.replace(day=ultimo_dia)
    return fi, ff


def _base_cita_qs(user, fi, ff):
    """QuerySet base de Cita filtrado por rol y rango."""
    from apps.appointments.models import Cita

    qs = Cita.objects.filter(
        fecha_hora__date__gte=fi,
        fecha_hora__date__lte=ff,
        is_active=True,
    )
    # Doctora: solo ve sus propias citas (pacientes que atendió)
    # En este sistema la doctora es la única que atiende, pero filtramos
    # para mantener la lógica correcta si se añaden más doctoras.
    return qs


def _base_invoice_qs(user, fi, ff):
    """QuerySet base de Invoice filtrado por rol y rango."""
    from apps.finance.models import Invoice

    qs = Invoice.objects.filter(
        fecha_emision__date__gte=fi,
        fecha_emision__date__lte=ff,
        is_active=True,
    ).exclude(estado=Invoice.Estado.ANULADO)
    return qs


# ── Vista principal (renderiza template) ─────────────────────
@moderador_required
def analytics_view(request):
    """Renderiza la plantilla del dashboard analytics."""
    return render(request, "dashboard/analytics.html")


# ── API JSON para AJAX ───────────────────────────────────────
@moderador_required
def analytics_api(request):
    """
    Retorna JSON con todos los datos del dashboard analytics.
    Parámetros GET: fecha_inicio, fecha_fin (ISO format).
    """
    from apps.appointments.models import Cita
    from apps.finance.models import FacturaDia
    from apps.ratings.models import PromedioGlobal
    from apps.users.models import CustomUser

    fi, ff = _parse_dates(request)
    hoy = timezone.now().date()
    ayer = hoy - timedelta(days=1)
    user = request.user

    # ── KPI 1: Citas hoy vs ayer ──
    citas_hoy_qs = Cita.objects.filter(fecha_hora__date=hoy, is_active=True)
    citas_ayer_qs = Cita.objects.filter(fecha_hora__date=ayer, is_active=True)

    citas_hoy = citas_hoy_qs.count()
    citas_ayer = citas_ayer_qs.count()
    citas_diff = citas_hoy - citas_ayer

    # ── KPI 2: Ingresos del mes (ingreso_neto de FacturaDia) + variación vs mes anterior ──
    primer_dia_mes = hoy.replace(day=1)
    facturas_mes = FacturaDia.objects.filter(
        fecha__gte=primer_dia_mes,
        fecha__lte=hoy,
        is_active=True,
    ).exclude(estado=FacturaDia.Estado.ANULADA)
    ingresos_mes = Decimal("0")
    for fd in facturas_mes:
        ingresos_mes += fd.ingreso_neto

    # Mes anterior
    if primer_dia_mes.month == 1:
        mes_ant_inicio = primer_dia_mes.replace(year=primer_dia_mes.year - 1, month=12)
    else:
        mes_ant_inicio = primer_dia_mes.replace(month=primer_dia_mes.month - 1)
    mes_ant_fin = primer_dia_mes - timedelta(days=1)
    facturas_mes_ant = FacturaDia.objects.filter(
        fecha__gte=mes_ant_inicio,
        fecha__lte=mes_ant_fin,
        is_active=True,
    ).exclude(estado=FacturaDia.Estado.ANULADA)
    ingresos_mes_ant = Decimal("0")
    for fd in facturas_mes_ant:
        ingresos_mes_ant += fd.ingreso_neto

    if ingresos_mes_ant > 0:
        ingresos_var = float(
            ((ingresos_mes - ingresos_mes_ant) / ingresos_mes_ant * 100).quantize(Decimal("0.1"))
        )
    else:
        ingresos_var = 100.0 if ingresos_mes > 0 else 0.0

    # ── KPI 3: Pacientes nuevos este mes vs anterior ──
    pacientes_nuevos = CustomUser.objects.filter(
        rol="paciente",
        is_active=True,
        fecha_registro__date__gte=primer_dia_mes,
        fecha_registro__date__lte=hoy,
    ).count()

    pacientes_nuevos_ant = CustomUser.objects.filter(
        rol="paciente",
        is_active=True,
        fecha_registro__date__gte=mes_ant_inicio,
        fecha_registro__date__lte=mes_ant_fin,
    ).count()

    pacientes_diff = pacientes_nuevos - pacientes_nuevos_ant

    # ── KPI 4: Rating promedio ──
    prom_global = PromedioGlobal.get_instance()
    rating_promedio = float(prom_global.promedio)
    rating_total = prom_global.total

    # ══════════════════════════════════════════════════════════
    # GRÁFICAS (datos del rango filtrado)
    # ══════════════════════════════════════════════════════════

    invoices_rango = _base_invoice_qs(user, fi, ff)
    citas_rango = _base_cita_qs(user, fi, ff)

    # ── Gráfica 1: Ingresos por semana (FacturaDia del mes actual) ──
    facturas_mes_chart = FacturaDia.objects.filter(
        fecha__gte=primer_dia_mes,
        fecha__lte=hoy,
        is_active=True,
    ).exclude(estado=FacturaDia.Estado.ANULADA)

    # Agrupar por semana manualmente (ingreso_neto es property)
    from collections import OrderedDict
    semanas_dict = OrderedDict()
    for fd in facturas_mes_chart.order_by("fecha"):
        # ISO week
        semana_label = fd.fecha.strftime("Sem %V · %d/%m")
        iso_week = fd.fecha.isocalendar()[1]
        key = (fd.fecha.year, iso_week)
        if key not in semanas_dict:
            semanas_dict[key] = {"label": semana_label, "total": Decimal("0")}
        semanas_dict[key]["total"] += fd.ingreso_neto

    chart_ingresos = {
        "labels": [v["label"] for v in semanas_dict.values()],
        "data": [float(v["total"]) for v in semanas_dict.values()],
    }

    # ── Gráfica 2: Distribución por tipo de servicio (donut) — FacturaDia ──
    from apps.finance.models import DetalleFacturaDia
    tipo_display_map = dict(DetalleFacturaDia.TipoItem.choices)
    detalles_rango = (
        DetalleFacturaDia.objects.filter(
            factura__fecha__gte=fi,
            factura__fecha__lte=ff,
            factura__is_active=True,
        )
        .exclude(factura__estado=FacturaDia.Estado.ANULADA)
        .values("tipo")
        .annotate(total=Count("id"))
        .order_by("-total")
    )
    chart_servicios = {
        "labels": [tipo_display_map.get(item["tipo"], item["tipo"]) for item in detalles_rango],
        "data": [item["total"] for item in detalles_rango],
    }

    # ── Gráfica 3: Citas por estado (barras horizontales) ──
    estados_order = ["pendiente", "confirmada", "completada", "cancelada", "no_asistio"]
    estados_labels = {
        "pendiente": "Pendiente",
        "confirmada": "Confirmada",
        "completada": "Completada",
        "cancelada": "Cancelada",
        "no_asistio": "No Asistió",
    }
    citas_por_estado = dict(
        citas_rango
        .values_list("estado")
        .annotate(total=Count("id"))
    )
    chart_estados = {
        "labels": [estados_labels.get(e, e) for e in estados_order],
        "data": [citas_por_estado.get(e, 0) for e in estados_order],
    }

    # ══════════════════════════════════════════════════════════
    # TABLAS
    # ══════════════════════════════════════════════════════════

    # ── Últimas 10 citas del rango ──
    ultimas_citas_qs = (
        citas_rango
        .select_related("paciente", "servicio")
        .order_by("-fecha_hora")[:10]
    )
    ultimas_citas = [
        {
            "id": str(c.id),
            "paciente": c.paciente.get_full_name(),
            "servicio": c.servicio.nombre,
            "fecha_hora": c.fecha_hora.strftime("%d/%m/%Y %H:%M"),
            "estado": c.estado,
            "estado_display": c.get_estado_display(),
            "puede_confirmar": c.estado == "pendiente",
            "puede_cancelar": c.estado in ("pendiente", "confirmada"),
        }
        for c in ultimas_citas_qs
    ]

    # ── Top 5 servicios más solicitados ──
    top_servicios_qs = (
        citas_rango
        .values("servicio__nombre")
        .annotate(total=Count("id"))
        .order_by("-total")[:5]
    )
    top_servicios_list = list(top_servicios_qs)
    max_servicio = top_servicios_list[0]["total"] if top_servicios_list else 1
    top_servicios = [
        {
            "nombre": item["servicio__nombre"],
            "total": item["total"],
            "porcentaje": round(item["total"] / max_servicio * 100),
        }
        for item in top_servicios_list
    ]

    return JsonResponse({
        "kpis": {
            "citas_hoy": citas_hoy,
            "citas_diff": citas_diff,
            "ingresos_mes": float(ingresos_mes),
            "ingresos_var": ingresos_var,
            "pacientes_nuevos": pacientes_nuevos,
            "pacientes_diff": pacientes_diff,
            "rating_promedio": rating_promedio,
            "rating_total": rating_total,
        },
        "charts": {
            "ingresos": chart_ingresos,
            "servicios": chart_servicios,
            "estados": chart_estados,
        },
        "tables": {
            "ultimas_citas": ultimas_citas,
            "top_servicios": top_servicios,
        },
        "rango": {
            "fecha_inicio": fi.isoformat(),
            "fecha_fin": ff.isoformat(),
        },
    })


# ── Acciones rápidas AJAX ────────────────────────────────────
@moderador_required
def cambiar_estado_cita(request, cita_id):
    """Cambia el estado de una cita (confirmar/cancelar) vía AJAX."""
    from apps.appointments.models import Cita

    if request.method != "POST":
        return JsonResponse({"error": "Método no permitido"}, status=405)

    nuevo_estado = request.POST.get("estado", "")
    if nuevo_estado not in ("confirmada", "cancelada"):
        return JsonResponse({"error": "Estado inválido"}, status=400)

    from django.shortcuts import get_object_or_404
    cita = get_object_or_404(Cita, id=cita_id, is_active=True)

    if nuevo_estado == "confirmada" and cita.estado != "pendiente":
        return JsonResponse({"error": "Solo se pueden confirmar citas pendientes"}, status=400)

    if nuevo_estado == "cancelada" and cita.estado not in ("pendiente", "confirmada"):
        return JsonResponse({"error": "No se puede cancelar esta cita"}, status=400)

    # Log
    from apps.appointments.models import CitaLog
    CitaLog.objects.create(
        cita=cita,
        usuario=request.user,
        estado_anterior=cita.estado,
        estado_nuevo=nuevo_estado,
        motivo=f"Cambio desde dashboard analytics por {request.user.get_full_name()}",
    )

    cita.estado = nuevo_estado
    cita.save(update_fields=["estado", "updated_at"])

    logger.info(
        "Cita %s: %s → %s por %s (dashboard)",
        cita.id, cita.estado, nuevo_estado, request.user.email,
    )

    return JsonResponse({
        "ok": True,
        "estado": cita.estado,
        "estado_display": cita.get_estado_display(),
    })


# ── Exportar Excel del dashboard ─────────────────────────────
@moderador_required
def exportar_analytics_excel(request):
    """Exporta los datos del dashboard analytics a Excel."""
    from openpyxl import Workbook
    from openpyxl.styles import Alignment, Border, Font, PatternFill, Side

    from apps.appointments.models import Cita
    from apps.finance.models import Invoice

    fi, ff = _parse_dates(request)
    user = request.user

    citas_rango = _base_cita_qs(user, fi, ff).select_related("paciente", "servicio")
    invoices_rango = _base_invoice_qs(user, fi, ff).select_related("paciente", "servicio")

    wb = Workbook()

    # ── Estilos ──
    h_font = Font(name="Calibri", bold=True, color="FFFFFF", size=10)
    h_fill = PatternFill(start_color="E8336A", end_color="E8336A", fill_type="solid")
    h_align = Alignment(horizontal="center", vertical="center", wrap_text=True)
    border = Border(
        left=Side(style="thin"), right=Side(style="thin"),
        top=Side(style="thin"), bottom=Side(style="thin"),
    )
    money_fmt = '#,##0.00" Kz"'

    # ── Hoja 1: Citas ──
    ws1 = wb.active
    ws1.title = "Citas"
    headers_citas = ["Fecha/Hora", "Paciente", "Servicio", "Estado", "Duración (min)"]
    for col, (h, w) in enumerate(zip(headers_citas, [18, 28, 25, 14, 12]), 1):
        cell = ws1.cell(row=1, column=col, value=h)
        cell.font = h_font
        cell.fill = h_fill
        cell.alignment = h_align
        cell.border = border
        ws1.column_dimensions[cell.column_letter].width = w

    for row_i, cita in enumerate(citas_rango.order_by("-fecha_hora"), 2):
        data = [
            cita.fecha_hora.strftime("%d/%m/%Y %H:%M"),
            cita.paciente.get_full_name(),
            cita.servicio.nombre,
            cita.get_estado_display(),
            cita.duracion_minutos,
        ]
        for col, val in enumerate(data, 1):
            cell = ws1.cell(row=row_i, column=col, value=val)
            cell.border = border

    # ── Hoja 2: Ingresos ──
    ws2 = wb.create_sheet("Ingresos")
    headers_ing = ["Nro. Factura", "Fecha", "Paciente", "Servicio", "Subtotal", "IRT", "Total", "Estado"]
    for col, (h, w) in enumerate(zip(headers_ing, [16, 14, 28, 25, 16, 14, 16, 14]), 1):
        cell = ws2.cell(row=1, column=col, value=h)
        cell.font = h_font
        cell.fill = h_fill
        cell.alignment = h_align
        cell.border = border
        ws2.column_dimensions[cell.column_letter].width = w

    for row_i, inv in enumerate(invoices_rango.order_by("-fecha_emision"), 2):
        data = [
            inv.numero,
            inv.fecha_emision.strftime("%d/%m/%Y"),
            inv.paciente.get_full_name(),
            inv.servicio.nombre,
            float(inv.subtotal),
            float(inv.irt),
            float(inv.total),
            inv.get_estado_display(),
        ]
        for col, val in enumerate(data, 1):
            cell = ws2.cell(row=row_i, column=col, value=val)
            cell.border = border
            if col in (5, 6, 7):
                cell.number_format = money_fmt

    # ── Hoja 3: Top Servicios ──
    ws3 = wb.create_sheet("Top Servicios")
    headers_svc = ["Servicio", "Total Citas"]
    for col, (h, w) in enumerate(zip(headers_svc, [30, 14]), 1):
        cell = ws3.cell(row=1, column=col, value=h)
        cell.font = h_font
        cell.fill = h_fill
        cell.alignment = h_align
        cell.border = border
        ws3.column_dimensions[cell.column_letter].width = w

    top_svc = (
        citas_rango.values("servicio__nombre")
        .annotate(total=Count("id"))
        .order_by("-total")
    )
    for row_i, item in enumerate(top_svc, 2):
        ws3.cell(row=row_i, column=1, value=item["servicio__nombre"]).border = border
        ws3.cell(row=row_i, column=2, value=item["total"]).border = border

    # ── Response ──
    response = HttpResponse(
        content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
    )
    filename = f"analytics_{fi}_{ff}.xlsx"
    response["Content-Disposition"] = f'attachment; filename="{filename}"'
    wb.save(response)
    logger.info("Excel analytics exportado: %s a %s por %s", fi, ff, user.email)
    return response
