<?php
/**
 * Helpers compartidos para los endpoints del módulo Turnero.
 * Incluido por cada endpoint de modules/turnero/api/.
 */

ob_start();

require_once __DIR__ . '/../../../config/config.php';

error_reporting(E_ERROR | E_PARSE);
ini_set('display_errors', '0');
ini_set('html_errors', '0');

register_shutdown_function(function () {
    $err = error_get_last();
    if ($err && in_array($err['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])) {
        ob_clean();
        http_response_code(500);
        header('Content-Type: application/json; charset=utf-8');
        echo json_encode(['ok' => false, 'error' => 'Error interno: ' . $err['message']]);
    }
});

header('Content-Type: application/json; charset=utf-8');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type');

if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
    http_response_code(204);
    exit;
}

// ── Funciones de respuesta ───────────────────────────────────

function jsonOk(array $payload = [], string $mensaje = ''): void
{
    ob_clean();
    $resp = ['ok' => true, 'success' => true];
    if ($mensaje) {
        $resp['message'] = $mensaje;
    }
    echo json_encode(array_merge($resp, $payload), JSON_UNESCAPED_UNICODE);
    exit;
}

function jsonError(string $mensaje, int $code = 400): void
{
    ob_clean();
    http_response_code($code);
    echo json_encode(['ok' => false, 'success' => false, 'error' => $mensaje], JSON_UNESCAPED_UNICODE);
    exit;
}

function requireMethod(string $method): void
{
    if ($_SERVER['REQUEST_METHOD'] !== strtoupper($method)) {
        jsonError('Método no permitido', 405);
    }
}

function inputJson(): array
{
    static $parsed = null;
    if ($parsed === null) {
        $raw    = file_get_contents('php://input');
        $parsed = json_decode($raw, true) ?? [];
    }
    return $parsed;
}

// ── Autenticación (kiosko y display son públicos) ────────────

/**
 * Exige sesión activa. Para endpoints de login requerido.
 */
function requireAuth(): void
{
    if (empty($_SESSION['admin_user']['id'])) {
        jsonError('No autenticado', 401);
    }
}

/**
 * Exige sesión + módulo turnero.
 */
function requireTurnero(): void
{
    requireAuth();
    $modules = $_SESSION['admin_user']['modules'] ?? [];
    $roleId  = $_SESSION['admin_user']['role_id'] ?? null;

    // Superadmin / admin sin role_id = acceso total
    if (empty($roleId)) {
        return;
    }

    if (!in_array('turnero', (array) $modules, true)) {
        jsonError('Sin acceso al módulo Turnero', 403);
    }
}

function adminId(): ?int
{
    return isset($_SESSION['admin_user']['id'])
        ? (int) $_SESSION['admin_user']['id']
        : null;
}

// ── Helper BD ────────────────────────────────────────────────

function db(): PDO
{
    return Database::getInstance()->getConnection();
}

// ── Motor de cola ────────────────────────────────────────────

/**
 * Obtiene o crea la sesión del día actual.
 * Devuelve el id de la sesión.
 */
function obtenerOCrearSesionHoy(): int
{
    $pdo  = db();
    $hoy  = date('Y-m-d');

    $stmt = $pdo->prepare('SELECT id FROM turnero_sesiones WHERE fecha = ? LIMIT 1');
    $stmt->execute([$hoy]);
    $row = $stmt->fetch(PDO::FETCH_ASSOC);

    if ($row) {
        return (int) $row['id'];
    }

    // Crear sesión del día
    $stmt = $pdo->prepare(
        'INSERT INTO turnero_sesiones (fecha, abierto_por, inicio_at) VALUES (?, ?, NOW())'
    );
    $stmt->execute([$hoy, adminId()]);
    return (int) $pdo->lastInsertId();
}

/**
 * Devuelve ['id', 'fin_at'] de la sesión de hoy si existe, o null.
 * Para uso en pantallas: NO crea sesión nueva.
 */
function sesionHoy(): ?array
{
    $pdo  = db();
    $stmt = $pdo->prepare('SELECT id, fin_at FROM turnero_sesiones WHERE fecha = CURDATE() LIMIT 1');
    $stmt->execute();
    return $stmt->fetch(PDO::FETCH_ASSOC) ?: null;
}

/**
 * Genera el siguiente número correlativo de turno para una sesión.
 * Usa bloqueo a nivel de fila para evitar duplicados en concurrencia.
 */
function siguienteNumero(int $sesionId): int
{
    $pdo  = db();
    $stmt = $pdo->prepare(
        'SELECT COALESCE(MAX(numero), 0) + 1 AS siguiente
           FROM turnero_turnos
          WHERE sesion_id = ?
          FOR UPDATE'
    );
    $stmt->execute([$sesionId]);
    return (int) $stmt->fetchColumn();
}

/**
 * Genera el siguiente número correlativo de turno POR PRIORIDAD para una sesión.
 * Cada prioridad (A, B, C, D, E, F) tiene su propia secuencia independiente.
 * Usa bloqueo a nivel de fila para evitar duplicados en concurrencia.
 */
function siguienteNumeroPorPrioridad(int $sesionId, int $prioridadId): int
{
    $pdo  = db();
    $stmt = $pdo->prepare(
        'SELECT COALESCE(MAX(numero), 0) + 1 AS siguiente
           FROM turnero_turnos
          WHERE sesion_id = ?
            AND prioridad_id = ?
          FOR UPDATE'
    );
    $stmt->execute([$sesionId, $prioridadId]);
    return (int) $stmt->fetchColumn();
}

/**
 * Selecciona el siguiente turno a llamar según el motor de prioridades.
 *
 * @param  string      $estado    Estado que deben tener los turnos en cola
 * @param  int|null    $lugarId   Si no es null, filtra por lugar_destino_id (o su grupo)
 * @param  array|null  $lugarIds  Lista explícita de lugar_ids (grupo completo)
 * @return array|null             Fila de turnero_turnos o null si cola vacía
 */
function siguienteTurnoEnCola(string $estado, ?int $lugarId = null, ?array $lugarIds = null, ?int $sesionId = null): ?array
{
    $pdo = db();

    // Resolver grupo si se pasó un lugarId sin lista explícita
    if ($lugarId !== null && $lugarIds === null) {
        $lugarIds = lugarIdsDeGrupo($lugarId);
    }

    $sesionCond = $sesionId !== null ? 'AND t.sesion_id = ?' : '';

    if (!empty($lugarIds)) {
        $in   = implode(',', array_map('intval', $lugarIds));
        $sql  = "
            SELECT t.*, p.orden_peso, p.codigo AS prioridad_codigo, p.color AS prioridad_color
              FROM turnero_turnos t
              JOIN turnero_prioridades p ON p.id = t.prioridad_id
             WHERE t.estado = ?
               $sesionCond
               AND t.lugar_destino_id IN ($in)
          ORDER BY p.orden_peso ASC, t.creado_at ASC
             LIMIT 1
             FOR UPDATE
        ";
        $binds = $sesionId !== null ? [$estado, $sesionId] : [$estado];
        $stmt  = $pdo->prepare($sql);
        $stmt->execute($binds);
    } else {
        $sql = "
            SELECT t.*, p.orden_peso, p.codigo AS prioridad_codigo, p.color AS prioridad_color
              FROM turnero_turnos t
              JOIN turnero_prioridades p ON p.id = t.prioridad_id
             WHERE t.estado = ?
               $sesionCond
          ORDER BY p.orden_peso ASC, t.creado_at ASC
             LIMIT 1
             FOR UPDATE
        ";
        $binds = $sesionId !== null ? [$estado, $sesionId] : [$estado];
        $stmt  = $pdo->prepare($sql);
        $stmt->execute($binds);
    }

    $row = $stmt->fetch(PDO::FETCH_ASSOC);
    return $row ?: null;
}

/**
 * Devuelve todos los lugar_ids que comparten grupo con $lugarId.
 * Si no tiene grupo, devuelve [$lugarId].
 */
function lugarIdsDeGrupo(int $lugarId): array
{
    $pdo  = db();
    $stmt = $pdo->prepare("SELECT grupo_id FROM turnero_lugares WHERE id = ?");
    $stmt->execute([$lugarId]);
    $row  = $stmt->fetch(PDO::FETCH_ASSOC);

    if (!$row || $row['grupo_id'] === null) {
        return [$lugarId];
    }

    $grupoId = (int)$row['grupo_id'];
    $stmt2   = $pdo->prepare("SELECT id FROM turnero_lugares WHERE grupo_id = ? AND activo = 1");
    $stmt2->execute([$grupoId]);
    $ids = $stmt2->fetchAll(PDO::FETCH_COLUMN);
    return $ids ?: [$lugarId];
}

/**
 * Emite un evento SSE a todos los clientes suscritos escribiendo
 * un flag en la tabla turnero_sesiones (campo sse_ping_at).
 * Los clientes hacen polling de ese campo o escuchan el SSE directamente.
 *
 * Implementación ligera: actualiza timestamp en sesión para que
 * sse_turno.php lo detecte y reenvíe el estado.
 */
function notificarSSE(int $sesionId): void
{
    try {
        db()->prepare(
            'UPDATE turnero_sesiones SET sse_ping_at = NOW() WHERE id = ?'
        )->execute([$sesionId]);
    } catch (\Throwable $e) {
        // No es crítico si falla; el SSE continuará con polling normal
    }
}
