"""
Servicios de negocio — App Appointments
==========================================
Validacion anti-solapamiento, creacion de citas, cambios de estado.
"""

from datetime import datetime, timedelta

from django.conf import settings
from django.core.exceptions import ValidationError
from django.db.models import Q
from django.utils import timezone
from django.utils.translation import gettext_lazy as _

from .models import BloqueoFecha, Cita, CitaLog, HorarioDoctora


def validar_disponibilidad(fecha_hora, duracion_minutos, excluir_cita_id=None):
    """
    Valida que el slot este disponible:
    1. No sea en el pasado
    2. No sea dia bloqueado
    3. Este dentro del horario de la doctora
    4. No se solape con otra cita activa
    Retorna True o lanza ValidationError.
    """
    if fecha_hora <= timezone.now():
        raise ValidationError(_("Não é possível agendar uma consulta no passado."))

    fecha = fecha_hora.date()
    hora_inicio = fecha_hora.time()
    fecha_hora_fin = fecha_hora + timedelta(minutes=duracion_minutos)
    hora_fin = fecha_hora_fin.time()

    # 1. Verificar dia bloqueado
    if BloqueoFecha.objects.filter(fecha=fecha, is_active=True).exists():
        raise ValidationError(_("Este dia não está disponível."))

    # 2. Verificar horario de la doctora
    dia_semana = fecha_hora.weekday()
    horario = HorarioDoctora.objects.filter(
        dia_semana=dia_semana,
        activo=True,
        is_active=True,
    ).first()

    if not horario:
        raise ValidationError(_("A doutora não atende neste dia."))

    if hora_inicio < horario.hora_inicio or hora_fin > horario.hora_fin:
        raise ValidationError(
            _("O horário selecionado está fora do horário de atendimento "
              "(%(inicio)s - %(fin)s).") % {
                "inicio": horario.hora_inicio.strftime("%H:%M"),
                "fin": horario.hora_fin.strftime("%H:%M"),
            }
        )

    # 3. Validar anti-solapamiento
    estados_activos = [Cita.Estado.PENDIENTE, Cita.Estado.CONFIRMADA]

    # Usar rango de datetime en vez de __date para evitar problemas con CONVERT_TZ
    dia_inicio = datetime.combine(fecha, datetime.min.time())
    dia_fin = datetime.combine(fecha, datetime.max.time())
    if settings.USE_TZ:
        tz = timezone.get_current_timezone()
        dia_inicio = timezone.make_aware(dia_inicio, tz)
        dia_fin = timezone.make_aware(dia_fin, tz)

    citas_existentes = Cita.objects.filter(
        fecha_hora__range=(dia_inicio, dia_fin),
        estado__in=estados_activos,
        is_active=True,
    )

    if excluir_cita_id:
        citas_existentes = citas_existentes.exclude(id=excluir_cita_id)

    for cita in citas_existentes:
        cita_fin = cita.fecha_hora + timedelta(minutes=cita.duracion_minutos)
        if fecha_hora < cita_fin and fecha_hora_fin > cita.fecha_hora:
            raise ValidationError(
                _("Este horário sobrepõe-se a outra consulta existente.")
            )

    return True


def obtener_slots_disponibles(fecha, servicio):
    """
    Retorna lista de slots disponibles para una fecha y servicio dados.
    Cada slot es un dict: {"hora": "09:00", "datetime": datetime_obj}
    """
    dia_semana = fecha.weekday()
    horario = HorarioDoctora.objects.filter(
        dia_semana=dia_semana,
        activo=True,
        is_active=True,
    ).first()

    if not horario:
        return []

    if BloqueoFecha.objects.filter(fecha=fecha, is_active=True).exists():
        return []

    duracion = servicio.duracion_minutos
    slots = []
    current = datetime.combine(fecha, horario.hora_inicio)
    fin_jornada = datetime.combine(fecha, horario.hora_fin)

    if settings.USE_TZ:
        tz = timezone.get_current_timezone()
        current = timezone.make_aware(current, tz)
        fin_jornada = timezone.make_aware(fin_jornada, tz)

    # Obtener citas activas del dia
    estados_activos = [Cita.Estado.PENDIENTE, Cita.Estado.CONFIRMADA]
    dia_inicio = datetime.combine(fecha, datetime.min.time())
    dia_fin = datetime.combine(fecha, datetime.max.time())
    if settings.USE_TZ:
        dia_inicio = timezone.make_aware(dia_inicio, tz)
        dia_fin = timezone.make_aware(dia_fin, tz)
    citas_dia = list(
        Cita.objects.filter(
            fecha_hora__range=(dia_inicio, dia_fin),
            estado__in=estados_activos,
            is_active=True,
        ).values_list("fecha_hora", "duracion_minutos")
    )

    while current + timedelta(minutes=duracion) <= fin_jornada:
        slot_fin = current + timedelta(minutes=duracion)
        solapado = False

        for cita_inicio, cita_dur in citas_dia:
            cita_fin = cita_inicio + timedelta(minutes=cita_dur)
            if current < cita_fin and slot_fin > cita_inicio:
                solapado = True
                break

        if not solapado and current > timezone.now():
            slots.append({
                "hora": current.strftime("%H:%M"),
                "datetime": current.isoformat(),
            })

        current += timedelta(minutes=duracion)

    return slots


def crear_cita(paciente, servicio, fecha_hora, notas=""):
    """Crea una cita validando disponibilidad."""
    duracion = servicio.duracion_minutos
    validar_disponibilidad(fecha_hora, duracion)

    cita = Cita.objects.create(
        paciente=paciente,
        servicio=servicio,
        fecha_hora=fecha_hora,
        duracion_minutos=duracion,
        estado=Cita.Estado.PENDIENTE,
        notas_paciente=notas,
    )

    CitaLog.objects.create(
        cita=cita,
        usuario=paciente,
        estado_anterior="",
        estado_nuevo=Cita.Estado.PENDIENTE,
        motivo="Consulta criada pelo paciente.",
    )

    return cita


def cambiar_estado_cita(cita, nuevo_estado, usuario, motivo=""):
    """Cambia el estado de una cita y registra en el log."""
    estado_anterior = cita.estado

    transiciones_validas = {
        Cita.Estado.PENDIENTE: [Cita.Estado.CONFIRMADA, Cita.Estado.CANCELADA],
        Cita.Estado.CONFIRMADA: [Cita.Estado.COMPLETADA, Cita.Estado.CANCELADA, Cita.Estado.NO_ASISTIO],
    }

    estados_permitidos = transiciones_validas.get(estado_anterior, [])
    if nuevo_estado not in estados_permitidos:
        raise ValidationError(
            _("Não é possível mudar de %(anterior)s para %(nuevo)s.") % {
                "anterior": estado_anterior,
                "nuevo": nuevo_estado,
            }
        )

    cita.estado = nuevo_estado
    cita.save(update_fields=["estado", "updated_at"])

    CitaLog.objects.create(
        cita=cita,
        usuario=usuario,
        estado_anterior=estado_anterior,
        estado_nuevo=nuevo_estado,
        motivo=motivo,
    )

    return cita
