<?php
/**
 * Bootstrap compartido por todos los endpoints de /api.
 * Gestiona: conexión a MySQL, sesiones, manejo de errores, funciones compartidas.
 *
 * GARANTIZA que SIEMPRE devuelve JSON válido con detalle del problema,
 * nunca una respuesta vacía que rompa JSON.parse().
 */

// ===== CONFIGURACIÓN DE ERRORES =====
header('Content-Type: application/json; charset=utf-8');
ini_set('display_errors', '0'); // nunca mostrar HTML de error crudo al cliente
error_reporting(E_ALL);

// ===== MANEJADOR DE EXCEPCIONES =====
set_exception_handler(function ($e) {
    if (!headers_sent()) {
        http_response_code(500);
        header('Content-Type: application/json; charset=utf-8');
    }
    echo json_encode([
        'error' => 'Excepción en el servidor: ' . $e->getMessage(),
        'archivo' => basename($e->getFile()),
        'linea' => $e->getLine(),
    ], JSON_UNESCAPED_UNICODE);
    exit;
});

// ===== MANEJADOR DE ERRORES FATALES =====
register_shutdown_function(function () {
    $error = error_get_last();
    if ($error && in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR], true)) {
        if (!headers_sent()) {
            http_response_code(500);
            header('Content-Type: application/json; charset=utf-8');
        }
        echo json_encode([
            'error' => 'Error fatal en el servidor: ' . $error['message'],
            'archivo' => basename($error['file']),
            'linea' => $error['line'],
        ], JSON_UNESCAPED_UNICODE);
    }
});

// ===== INICIAR SESIONES =====
session_start();

// ===== GLOBAL PDO CONNECTION =====
$GLOBALS['pdo'] = null;

/**
 * Obtiene la conexión PDO a MySQL.
 * La conexión se crea una sola vez y se reutiliza.
 */
function getPDO() {
    if ($GLOBALS['pdo'] !== null) {
        return $GLOBALS['pdo'];
    }

    $rutaConfig = __DIR__ . '/../../datos-privados/config_mysql.php';
    if (!file_exists($rutaConfig)) {
        http_response_code(500);
        echo json_encode([
            'error' => 'No se encontró config_mysql.php',
            'ruta_buscada' => $rutaConfig,
        ], JSON_UNESCAPED_UNICODE);
        exit;
    }

    $config = require $rutaConfig;
    $mysqlConfig = $config['mysql'];

    try {
        $dsn = sprintf(
            'mysql:host=%s;port=%d;dbname=%s;charset=%s',
            $mysqlConfig['host'],
            $mysqlConfig['port'],
            $mysqlConfig['database'],
            $mysqlConfig['charset']
        );

        $pdo = new PDO($dsn, $mysqlConfig['usuario'], $mysqlConfig['password']);
        $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
        $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);

        $GLOBALS['pdo'] = $pdo;
        return $pdo;
    } catch (PDOException $e) {
        http_response_code(500);
        echo json_encode([
            'error' => 'No se pudo conectar a MySQL: ' . $e->getMessage(),
            'host' => $mysqlConfig['host'],
            'database' => $mysqlConfig['database'],
        ], JSON_UNESCAPED_UNICODE);
        exit;
    }
}

/**
 * Obtiene la configuración desde config_mysql.php
 */
function getConfig($clave = null) {
    static $config = null;
    if ($config === null) {
        $rutaConfig = __DIR__ . '/../../datos-privados/config_mysql.php';
        $config = require $rutaConfig;
    }
    return $clave ? ($config[$clave] ?? null) : $config;
}

/**
 * Verifica que el usuario esté autenticado.
 * Si no, devuelve error 401 y termina.
 */
function requireAuth() {
    if (empty($_SESSION['autenticado'])) {
        http_response_code(401);
        echo json_encode(['error' => 'No autenticado. Inicia sesión de nuevo.'], JSON_UNESCAPED_UNICODE);
        exit;
    }
}

/**
 * Lee y valida JSON del body de la request.
 * Si no es válido, devuelve error 400 y termina.
 */
function getJsonBody() {
    $body = file_get_contents('php://input');
    $config = getConfig('sistema');
    if (strlen($body) > $config['max_request_size']) {
        http_response_code(413);
        echo json_encode(['error' => 'El contenido es demasiado grande.'], JSON_UNESCAPED_UNICODE);
        exit;
    }

    $data = json_decode($body, true);
    if (json_last_error() !== JSON_ERROR_NONE) {
        http_response_code(400);
        echo json_encode(['error' => 'JSON inválido.'], JSON_UNESCAPED_UNICODE);
        exit;
    }

    return $data;
}

/**
 * Obtiene el ID de turno activo para esta request.
 *
 * Si la CUENTA (usuarios.turno_id) tiene un turno fijo asignado, ese
 * turno se usa siempre y se ignora cualquier ?turno_id= que llegue
 * (así un usuario amarrado a Vespertino no puede ver Matutino solo
 * cambiando el parámetro en la URL).
 *
 * Si la cuenta NO tiene turno fijo (turno_id = NULL, cuenta
 * "administradora"), se permite elegir turno:
 *   1. ?turno_id=N en la URL -> si es válido, SE GUARDA en la sesión.
 *   2. El turno guardado previamente en la sesión.
 *   3. El primer turno disponible (fallback).
 */
function getTurnoId() {
    $pdo = getPDO();

    // 0. Cuenta con turno fijo: manda siempre, sin excepción.
    if (array_key_exists('usuario_turno_id', $_SESSION) && $_SESSION['usuario_turno_id'] !== null) {
        return (int)$_SESSION['usuario_turno_id'];
    }

    // 1. ¿Vino un turno_id explícito en la URL? (solo aplica a cuentas sin turno fijo)
    if (!empty($_GET['turno_id'])) {
        $solicitado = (int)$_GET['turno_id'];
        $stmt = $pdo->prepare('SELECT id FROM turnos WHERE id = ? AND activo = 1');
        $stmt->execute([$solicitado]);
        $turno = $stmt->fetch();

        if ($turno) {
            $_SESSION['turno_id'] = (int)$turno['id'];
            return (int)$turno['id'];
        }
        // Si mandaron un turno_id inválido, lo ignoramos silenciosamente
        // y seguimos con la sesión / el default.
    }

    // 2. Turno ya elegido anteriormente en esta sesión
    if (!empty($_SESSION['turno_id'])) {
        return (int)$_SESSION['turno_id'];
    }

    // 3. Fallback: el primer turno activo
    $stmt = $pdo->query('SELECT id FROM turnos WHERE activo = 1 ORDER BY orden ASC LIMIT 1');
    $turno = $stmt->fetch();

    if (!$turno) {
        http_response_code(500);
        echo json_encode(['error' => 'No hay turnos configurados en la base de datos.'], JSON_UNESCAPED_UNICODE);
        exit;
    }

    $_SESSION['turno_id'] = (int)$turno['id'];
    return (int)$turno['id'];
}

/**
 * true si la cuenta logueada tiene un turno fijo asignado (usuarios.turno_id
 * no es NULL). Falso para cuentas "administradoras" que pueden alternar turno.
 */
function tieneTurnoFijo() {
    return array_key_exists('usuario_turno_id', $_SESSION) && $_SESSION['usuario_turno_id'] !== null;
}

/**
 * Lista todos los turnos activos, para que el cliente pueda mostrar un selector.
 */
function listarTurnos() {
    $pdo = getPDO();
    $stmt = $pdo->query('SELECT id, nombre FROM turnos WHERE activo = 1 ORDER BY orden ASC');
    return $stmt->fetchAll();
}

/**
 * Registra un cambio en la tabla de versiones (para auditoría).
 */
function registrarVersion($turnoId, $tabla, $tipoOperacion, $versionHash = null) {
    $pdo = getPDO();
    $usuario = $_SESSION['usuario'] ?? 'desconocido';

    $stmt = $pdo->prepare('
        INSERT INTO versiones (turno_id, tabla, usuario, tipo_operacion, version_hash)
        VALUES (:turno_id, :tabla, :usuario, :tipo_operacion, :version_hash)
    ');

    $stmt->execute([
        ':turno_id' => $turnoId,
        ':tabla' => $tabla,
        ':usuario' => $usuario,
        ':tipo_operacion' => $tipoOperacion,
        ':version_hash' => $versionHash,
    ]);
}

/**
 * Calcula el hash MD5 de los datos actuales (para control de versiones optimista).
 * Útil para detectar si alguien más cambió los datos mientras tú editabas.
 */
/**
 * Ordena recursivamente las llaves de un array (incluyendo sub-arrays),
 * para que el hash de versión no cambie solo porque MySQL devolvió las
 * filas en distinto orden.
 *
 * NOTA: PHP no tiene una constante JSON_SORT_KEYS (nunca existió); por
 * eso se implementa a mano aquí en vez de pasarla a json_encode().
 */
function ordenarLlavesRecursivo($valor) {
    if (is_array($valor)) {
        foreach ($valor as $k => $v) {
            $valor[$k] = ordenarLlavesRecursivo($v);
        }
        // Si es un array asociativo (no una lista secuencial), ordenamos sus llaves.
        if (array_keys($valor) !== range(0, count($valor) - 1)) {
            ksort($valor);
        }
    }
    return $valor;
}

function calcularVersionHash($datos) {
    $ordenado = ordenarLlavesRecursivo($datos);
    return md5(json_encode($ordenado, JSON_UNESCAPED_UNICODE));
}

/**
 * Normaliza un valor antes de mandarlo a MySQL:
 *  - true/false (checkboxes de evaluación, etc.) -> 1/0, para columnas TINYINT.
 *  - Cadena vacía en un campo de fecha (p.ej. "fecha_real" cuando aún no
 *    se cierra el plano) -> NULL, para no romper columnas tipo DATE.
 *  - Cadena vacía en un campo "*_id" (p.ej. "pmce_id" cuando el PAT no está
 *    vinculado a ninguna meta) -> NULL, para no romper referencias.
 */
function normalizarValor($campo, $valor) {
    if (is_bool($valor)) {
        return $valor ? 1 : 0;
    }
    if ($valor === '' && (strpos($campo, 'fecha_') === 0 || substr($campo, -3) === '_id')) {
        return null;
    }
    return $valor;
}

/**
 * Devuelve error JSON con el código HTTP especificado.
 */
function errorJSON($mensaje, $codigo = 400) {
    http_response_code($codigo);
    echo json_encode(['error' => $mensaje], JSON_UNESCAPED_UNICODE);
    exit;
}

/**
 * Devuelve éxito JSON.
 */
function exitJSON($data = [], $codigo = 200) {
    http_response_code($codigo);
    echo json_encode($data, JSON_UNESCAPED_UNICODE);
    exit;
}
