<?php
/**
 * POST /modules/turnero/api/cambiar_estado.php
 * Avanza o cancela el estado de un turno.
 * Requiere login + módulo turnero.
 *
 * Body JSON:
 *   turno_id      int     requerido
 *   nuevo_estado  string  requerido  (ver transiciones válidas abajo)
 *   lugar_id      int     requerido cuando nuevo_estado = "en_espera_lugar"
 *
 * Transiciones permettidas:
 *   espera          → en_recepcion
 *   en_recepcion    → en_espera_lugar, ausente, cancelado
 *   en_espera_lugar → en_servicio, ausente, cancelado
 *   en_servicio     → finalizado, ausente, cancelado
 *   (cualquier)     → ausente, cancelado
 *
 * Bloqueo especial:
 *   en_espera_lugar → en_servicio  requiere que todos los consentimientos
 *   del turno estén en estado 'firmado' o 'rechazado'.
 */

require_once __DIR__ . '/_helpers.php';
require_once __DIR__ . '/../../../classes/lab/ActividadAdmin.php';
requireMethod('POST');
requireTurnero();

$datos       = inputJson();
$turnoId     = isset($datos['turno_id'])    ? (int)    $datos['turno_id']    : 0;
$nuevoEstado = isset($datos['nuevo_estado']) ? trim((string) $datos['nuevo_estado']) : '';
$lugarId     = isset($datos['lugar_id'])     ? (int)    $datos['lugar_id']    : null;
$deskId      = isset($datos['desk_id'])      ? (int)    $datos['desk_id']     : null;

// ── Validación básica ─────────────────────────────────────────
if ($turnoId <= 0)    jsonError('turno_id inválido.');
if ($nuevoEstado === '') jsonError('nuevo_estado es requerido.');

// Mapa de transiciones válidas [estado_actual => [estados_destino_permitidos]]
const TRANSICIONES = [
    'espera'          => ['en_recepcion', 'ausente', 'cancelado'],
    'en_recepcion'    => ['en_espera_lugar', 'espera', 'ausente', 'cancelado'],
    'en_espera_lugar' => ['en_servicio', 'en_recepcion', 'ausente', 'cancelado'],
    'en_servicio'     => ['finalizado', 'ausente', 'cancelado', 'en_espera_lugar', 'en_recepcion'],
];

$pdo = db();
$pdo->beginTransaction();

try {
    // Leer turno actual con lock
    $stmt = $pdo->prepare(
        'SELECT t.*, p.codigo AS prioridad_codigo
           FROM turnero_turnos t
           JOIN turnero_prioridades p ON p.id = t.prioridad_id
          WHERE t.id = ?
          FOR UPDATE'
    );
    $stmt->execute([$turnoId]);
    $turno = $stmt->fetch(PDO::FETCH_ASSOC);

    if (!$turno) {
        $pdo->rollBack();
        jsonError('Turno no encontrado.', 404);
    }

    $estadoActual = $turno['estado'];

    // Estados finales — solo admin (sin role_id) puede reabrir
    if (in_array($estadoActual, ['finalizado', 'ausente', 'cancelado'], true)) {
        $roleId = $_SESSION['admin_user']['role_id'] ?? null;
        if (!empty($roleId)) {
            $pdo->rollBack();
            jsonError("El turno ya está en estado '{$estadoActual}' y no puede modificarse.", 422);
        }

        $permitidosAdmin = ['en_servicio', 'en_espera_lugar', 'en_recepcion', 'espera'];
        if (!in_array($nuevoEstado, $permitidosAdmin, true)) {
            $pdo->rollBack();
            jsonError("Estado destino inválido para reapertura. Opciones: " . implode(', ', $permitidosAdmin), 422);
        }

        $sets  = ['estado = ?'];
        $binds = [$nuevoEstado];

        if ($nuevoEstado === 'en_servicio') {
            $sets[] = 'fin_lugar_at = NULL';
            $sets[] = 'inicio_lugar_at = COALESCE(inicio_lugar_at, NOW())';
            $sets[] = 'atendido_lugar_por = COALESCE(atendido_lugar_por, ?)';
            $binds[] = adminId();
        } elseif ($nuevoEstado === 'en_espera_lugar') {
            $sets[] = 'fin_lugar_at = NULL';
            $sets[] = 'inicio_lugar_at = NULL';
            $sets[] = 'llamado_lugar_at = NULL';
        } elseif ($nuevoEstado === 'espera') {
            $sets[] = 'llamado_recepcion_at = NULL';
            $sets[] = 'inicio_recepcion_at  = NULL';
            $sets[] = 'fin_recepcion_at     = NULL';
            $sets[] = 'llamado_lugar_at     = NULL';
            $sets[] = 'inicio_lugar_at      = NULL';
            $sets[] = 'fin_lugar_at         = NULL';
            $sets[] = 'atendido_recepcion_por = NULL';
            $sets[] = 'atendido_lugar_por   = NULL';
            $sets[] = 'recepcion_desk_id    = NULL';
        }

        $sql = 'UPDATE turnero_turnos SET ' . implode(', ', $sets) . ' WHERE id = ?';
        $binds[] = $turnoId;
        $pdo->prepare($sql)->execute($binds);
        $pdo->commit();

        $stmt = $pdo->prepare(
            'SELECT t.*, p.codigo AS prioridad_codigo, p.nombre AS prioridad_nombre, p.color AS prioridad_color
               FROM turnero_turnos t
               JOIN turnero_prioridades p ON p.id = t.prioridad_id
              WHERE t.id = ?'
        );
        $stmt->execute([$turnoId]);
        $turnoActualizado = $stmt->fetch(PDO::FETCH_ASSOC);

        notificarSSE((int) $turnoActualizado['sesion_id']);
        jsonOk(['turno' => $turnoActualizado], "Turno reabierto: estado actualizado a '{$nuevoEstado}'");
    }

    // Verificar que la transición sea válida
    $permitidos = TRANSICIONES[$estadoActual] ?? [];
    if (!in_array($nuevoEstado, $permitidos, true)) {
        $pdo->rollBack();
        jsonError(
            "Transición '{$estadoActual}' → '{$nuevoEstado}' no está permitida. "
            . "Transiciones válidas: " . implode(', ', $permitidos),
            422
        );
    }

    // ── Regla especial: en_espera_lugar → en_servicio ─────────
    if ($nuevoEstado === 'en_servicio') {
        $cStmt = $pdo->prepare(
            "SELECT COUNT(*) FROM turnero_consentimientos
              WHERE turno_id = ?
                AND estado NOT IN ('firmado', 'rechazado')"
        );
        $cStmt->execute([$turnoId]);
        $pendientes = (int) $cStmt->fetchColumn();

        if ($pendientes > 0) {
            $pdo->rollBack();
            jsonError(
                "Hay {$pendientes} consentimiento(s) pendiente(s). "
                . "El turno no puede pasar a 'en_servicio' hasta que todos estén firmados o rechazados.",
                422
            );
        }
    }

    // ── Construir UPDATE dinámico ─────────────────────────────
    $sets  = ['estado = ?'];
    $binds = [$nuevoEstado];

    switch ($nuevoEstado) {
        case 'en_recepcion':
            $sets[]  = 'llamado_recepcion_at = NOW()';
            $sets[]  = 'inicio_recepcion_at  = COALESCE(inicio_recepcion_at, NOW())';
            $sets[]  = 'atendido_recepcion_por = COALESCE(atendido_recepcion_por, ?)';
            $binds[] = adminId();
            if ($deskId) {
                $sets[]  = 'recepcion_desk_id = ?';
                $binds[] = $deskId;
            }
            // Viene de un lugar: limpiar campos del lugar para que quede reasignable
            if (in_array($estadoActual, ['en_espera_lugar', 'en_servicio'], true)) {
                $sets[] = 'lugar_destino_id   = NULL';
                $sets[] = 'llamado_lugar_at   = NULL';
                $sets[] = 'inicio_lugar_at    = NULL';
                $sets[] = 'fin_recepcion_at   = NULL';
                $sets[] = 'atendido_lugar_por = NULL';
            }
            break;

        case 'en_espera_lugar':
            // Requiere lugar_id (al volver desde en_servicio se conserva lugar_destino_id actual)
            if ($lugarId) {
                $sets[]  = 'lugar_destino_id      = ?';
                $binds[] = $lugarId;
            }
            $sets[]  = 'llamado_lugar_at    = NULL';
            $sets[]  = 'inicio_lugar_at     = NULL';
            $sets[]  = 'fin_recepcion_at    = COALESCE(fin_recepcion_at, NOW())';
            $sets[]  = 'atendido_recepcion_por = COALESCE(atendido_recepcion_por, ?)';
            $binds[] = adminId();
            break;

        case 'en_servicio':
            $sets[]  = 'llamado_lugar_at   = COALESCE(llamado_lugar_at, NOW())';
            $sets[]  = 'inicio_lugar_at    = COALESCE(inicio_lugar_at, NOW())';
            $sets[]  = 'atendido_lugar_por = COALESCE(atendido_lugar_por, ?)';
            $binds[] = adminId();
            break;

        case 'finalizado':
            $sets[]  = 'fin_lugar_at       = COALESCE(fin_lugar_at, NOW())';
            $sets[]  = 'atendido_lugar_por = COALESCE(atendido_lugar_por, ?)';
            $binds[] = adminId();
            break;

        case 'espera':
            // Soltar: liberar el turno para que otro escritorio lo llame
            $sets[]  = 'recepcion_desk_id     = NULL';
            $sets[]  = 'inicio_recepcion_at   = NULL';
            $sets[]  = 'atendido_recepcion_por = NULL';
            break;

        case 'ausente':
        case 'cancelado':
            // Sin timestamps adicionales; simplemente cambio de estado
            break;
    }

    $sql     = 'UPDATE turnero_turnos SET ' . implode(', ', $sets) . ' WHERE id = ?';
    $binds[] = $turnoId;

    $pdo->prepare($sql)->execute($binds);
    $pdo->commit();

    // Refrescar y devolver turno actualizado
    $stmt = $pdo->prepare(
        'SELECT t.*, p.codigo AS prioridad_codigo, p.nombre AS prioridad_nombre, p.color AS prioridad_color
           FROM turnero_turnos t
           JOIN turnero_prioridades p ON p.id = t.prioridad_id
          WHERE t.id = ?'
    );
    $stmt->execute([$turnoId]);
    $turnoActualizado = $stmt->fetch(PDO::FETCH_ASSOC);

    notificarSSE((int) $turnoActualizado['sesion_id']);

    // Queda constancia de quién cambió el estado a mano. Sin esto, en el
    // historial aparecía un turno cancelado o ausente sin ninguna huella
    // de quién lo hizo ni desde qué estado.
    try {
        (new ActividadAdmin())->registrar(
            adminId(),
            'turnero',
            'cambiar_estado',
            $turnoId,
            [
                'turno_id'       => $turnoId,
                'turno_codigo'   => $turnoActualizado['codigo'] ?? null,
                'estado_previo'  => $estadoActual,
                'estado_nuevo'   => $nuevoEstado,
            ]
        );
    } catch (\Throwable $e) {
        // La auditoría nunca debe tumbar la operación
        error_log('[cambiar_estado] auditoría: ' . $e->getMessage());
    }

    jsonOk(['turno' => $turnoActualizado], "Estado actualizado a '{$nuevoEstado}'");

} catch (\Throwable $e) {
    if ($pdo->inTransaction()) {
        $pdo->rollBack();
    }
    jsonError('Error al cambiar estado: ' . $e->getMessage(), 500);
}
