<?php
/**
 * Endpoints del lado NUBE para sincronización
 *
 * - syncReceive: Recibe datos de una sucursal (push de la sucursal)
 * - syncSend:    Envía datos a una sucursal (pull de la sucursal)
 *
 * Este archivo se ejecuta en el servidor de la NUBE.
 * La BD nube tiene columnas extra: sucursal, id_origen en cada tabla.
 */
if (!defined('BASEPATH')) {
    die('Acceso directo no permitido');
}

require_once(__DIR__ . '/sync_config.php');
require_once(__DIR__ . '/sync_utils.php');

switch ($tipo) {
    case 'syncReceive':
        handleSyncReceive($conn, $datapost);
        break;

    case 'syncSend':
        handleSyncSend($conn, $datapost);
        break;

    case 'syncCloudReset':
        handleSyncCloudReset($conn, $datapost);
        break;

    default:
        sendResponse(false, "Operación no reconocida: $tipo");
}

// =============================================
// syncReceive: Nube recibe datos de una sucursal
// =============================================

/**
 * Recibe un lote de registros de una sucursal y hace UPSERT en la BD nube.
 * Dedup por (sucursal, id_origen).
 *
 * Payload esperado:
 * {
 *   negocio: string,
 *   sucursal: string,
 *   tabla: string,
 *   registros: [ { ...campos... }, ... ]
 * }
 */
function handleSyncReceive($conn, $datapost) {
    global $SYNC_TABLES;

    $negocio   = $datapost['negocio'] ?? '';
    $sucursal  = $datapost['sucursal'] ?? '';
    $tableName = $datapost['tabla'] ?? '';
    $registros = $datapost['registros'] ?? [];

    if (empty($sucursal) || empty($tableName)) {
        sendResponse(false, "Parámetros requeridos: sucursal, tabla");
    }

    if (!isset($SYNC_TABLES[$tableName])) {
        sendResponse(false, "Tabla no válida para sync: $tableName");
    }

    if (empty($registros)) {
        sendResponse(true, "Sin registros para procesar", ['insertados' => 0, 'actualizados' => 0]);
    }

    $tableConfig = $SYNC_TABLES[$tableName];
    $pk = $tableConfig['pk'];
    $tsCol = $tableConfig['timestamp'];
    $startTime = microtime(true);

    $logId = logSync($conn, [
        'tabla'     => $tableName,
        'sucursal'  => $sucursal,
        'direccion' => 'push',  // Desde perspectiva del log, la sucursal hizo push
        'estado'    => 'iniciado',
    ]);

    $insertados = 0;
    $actualizados = 0;
    $conflictos = 0;
    $errores = [];

    $fkDefs = $tableConfig['fk'] ?? [];

    $conn->begin_transaction();

    try {
        // Marcar sesión como sync para que los triggers NO marquen editado_nube
        $conn->query("SET @is_sync = 1");

        foreach ($registros as $idx => $registro) {
            $idOrigen = $registro[$pk] ?? null;
            if ($idOrigen === null) {
                $errores[] = "Registro #$idx sin PK ($pk)";
                continue;
            }

            // Mapear FKs: traducir IDs de la sucursal a IDs de la nube
            foreach ($fkDefs as $fkCol => $refTable) {
                if (!isset($registro[$fkCol]) || $registro[$fkCol] === null || $registro[$fkCol] === '') {
                    continue;
                }
                $fkOriginalId = (int)$registro[$fkCol];
                // Buscar el ID que la nube asignó al registro referenciado
                $cloudId = lookupCloudId($conn, $refTable, $sucursal, $fkOriginalId);
                if ($cloudId !== null) {
                    $registro[$fkCol] = $cloudId;
                }
                // Si no se encuentra, mantener el original (puede fallar por FK)
            }

            // Verificar si ya existe en la nube por (sucursal, id_origen)
            $existing = getCloudRecord($conn, $tableName, $sucursal, $idOrigen, $tsCol);

            if ($existing) {
                // Verificar conflicto: ¿fue modificado por otra fuente desde el último sync?
                $remoteTimestamp = $registro[$tsCol] ?? null;
                $cloudTimestamp = $existing[$tsCol] ?? null;

                if ($cloudTimestamp && $remoteTimestamp) {
                    $winner = resolveConflict($cloudTimestamp, $remoteTimestamp, '__cloud__', $sucursal);
                    if ($winner === 'local') {
                        // La nube tiene datos más recientes, no sobrescribir
                        $conflictos++;
                        continue;
                    }
                }

                // Actualizar registro existente
                $updateData = $registro;
                unset($updateData[$pk]); // No actualizar PK de nube
                unset($updateData['editado_nube']); // No sobrescribir flag de nube
                $excludeCols = $tableConfig['exclude_cols'] ?? [];
                foreach ($excludeCols as $exc) {
                    unset($updateData[$exc]);
                }

                // Al recibir push de sucursal, resetear editado_nube = 0
                // (la sucursal tiene la versión más reciente, ya no hay edición pendiente)
                $updateData['editado_nube'] = 0;

                updateCloudRecord($conn, $tableName, $sucursal, $idOrigen, $updateData);
                $actualizados++;
            } else {
                // Insertar nuevo registro
                $insertData = $registro;
                $originalId = $insertData[$pk];
                unset($insertData[$pk]); // La nube genera su propio ID

                $excludeCols = $tableConfig['exclude_cols'] ?? [];
                foreach ($excludeCols as $exc) {
                    unset($insertData[$exc]);
                }

                // Agregar campos de nube
                $insertData['sucursal'] = $sucursal;
                $insertData['id_origen'] = $originalId;

                $insertErr = '';
                $insertResult = insertCloudRecord($conn, $tableName, $insertData, $insertErr);
                if ($insertResult === false || $insertResult === 0) {
                    $errores[] = "id_origen=$originalId: $insertErr";
                } else {
                    // Guardar mapeo: id_origen de sucursal → id de la nube
                    saveIdMapping($conn, $tableName, $sucursal, $originalId, $insertResult);
                    $insertados++;
                }
            }
        }

        $conn->commit();
        $conn->query("SET @is_sync = NULL");

        $duracion = round((microtime(true) - $startTime) * 1000);

        updateSyncLog($conn, $logId, [
            'registros_recibidos'  => $insertados + $actualizados,
            'registros_conflictos' => $conflictos,
            'estado'               => 'completado',
            'duracion_ms'          => $duracion,
        ]);

        sendResponse(true, "Recibidos correctamente", [
            'insertados'  => $insertados,
            'actualizados' => $actualizados,
            'conflictos'  => $conflictos,
            'errores'     => $errores,
            'duracion_ms' => $duracion,
        ]);

    } catch (Exception $e) {
        $conn->rollback();
        $conn->query("SET @is_sync = NULL");

        $duracion = round((microtime(true) - $startTime) * 1000);

        updateSyncLog($conn, $logId, [
            'estado'        => 'error',
            'error_mensaje' => $e->getMessage(),
            'duracion_ms'   => $duracion,
        ]);

        sendResponse(false, "Error procesando datos: " . $e->getMessage());
    }
}

// =============================================
// syncSend: Nube envía datos a una sucursal
// =============================================

/**
 * Envía registros de OTRAS sucursales a la sucursal solicitante.
 * Solo envía registros que la sucursal NO ha visto (cambiados después de 'since').
 *
 * Payload esperado:
 * {
 *   negocio: string,
 *   sucursal: string,       // Sucursal que pide datos (excluida de resultados)
 *   tabla: string,
 *   since: datetime|null,   // Desde cuándo
 *   page: int,
 *   batch_size: int
 * }
 */
function handleSyncSend($conn, $datapost) {
    global $SYNC_TABLES;

    $negocio    = $datapost['negocio'] ?? '';
    $sucursal   = $datapost['sucursal'] ?? '';
    $tableName  = $datapost['tabla'] ?? '';
    $since      = $datapost['since'] ?? null;
    $page       = (int)($datapost['page'] ?? 0);
    $batchSize  = (int)($datapost['batch_size'] ?? 500);

    if (empty($sucursal) || empty($tableName)) {
        sendResponse(false, "Parámetros requeridos: sucursal, tabla");
    }

    if (!isset($SYNC_TABLES[$tableName])) {
        sendResponse(false, "Tabla no válida para sync: $tableName");
    }

    $tableConfig = $SYNC_TABLES[$tableName];
    $tsCol = $tableConfig['timestamp'];
    $offset = $page * $batchSize;

    try {
        // Obtener columnas de la tabla (excluyendo editado_nube de la respuesta)
        $excludeCols = $tableConfig['exclude_cols'] ?? [];
        $excludeCols[] = 'editado_nube';
        $columns = getTableColumns($conn, $tableName, $excludeCols);
        $colList = implode(', ', array_map(function($c) { return "`$c`"; }, $columns));

        // ============================================================
        // Solo enviar registros de la MISMA sucursal editados en la nube
        // (editado_nube = 1) → devolver a su sucursal de origen
        // NO filtramos por timestamp: el flag editado_nube = 1 es la
        // fuente de verdad. Se resetea a 0 después de enviar, así que
        // no hay riesgo de reenvío duplicado.
        // ============================================================
        $sql = "SELECT $colList FROM `$tableName`
                WHERE sucursal = ? AND editado_nube = 1
                ORDER BY `$tsCol` ASC LIMIT ? OFFSET ?";

        $allParams = [$sucursal, $batchSize, $offset];
        $allTypes = 'sii';

        $stmt = $conn->prepare($sql);
        $stmt->bind_param($allTypes, ...$allParams);
        $stmt->execute();
        $result = $stmt->get_result();

        $records = [];
        $ownSucursalIds = []; // IDs de registros propios editados en nube (para reset)
        $fkDefs = $tableConfig['fk'] ?? [];

        while ($row = $result->fetch_assoc()) {
            // Rastrear registros de la propia sucursal para resetear editado_nube
            if (isset($row['sucursal']) && $row['sucursal'] === $sucursal && isset($row['id_origen'])) {
                $ownSucursalIds[] = (int)$row['id_origen'];
            }

            // Revertir FKs de IDs de nube → IDs locales (originales de la sucursal)
            // Al hacer push, los FKs se mapearon local→nube. Ahora hay que revertirlos.
            if (!empty($fkDefs)) {
                foreach ($fkDefs as $fkCol => $refTable) {
                    if (!isset($row[$fkCol]) || $row[$fkCol] === null || $row[$fkCol] === '') {
                        continue;
                    }
                    $cloudFkId = (int)$row[$fkCol];
                    // Buscar el ID original de la sucursal a partir del ID de la nube
                    $originalId = reverseCloudId($conn, $refTable, $sucursal, $cloudFkId);
                    if ($originalId !== null) {
                        $row[$fkCol] = $originalId;
                    }
                }
            }

            $records[] = $row;
        }
        $stmt->close();

        $hasMore = (count($records) === $batchSize);

        // Resetear editado_nube = 0 en los registros de la sucursal que ya se enviaron
        if (!empty($ownSucursalIds)) {
            $placeholders = implode(',', array_fill(0, count($ownSucursalIds), '?'));
            $resetSql = "UPDATE `$tableName` SET editado_nube = 0 WHERE sucursal = ? AND id_origen IN ($placeholders)";
            $resetTypes = 's' . str_repeat('i', count($ownSucursalIds));
            $resetParams = array_merge([$sucursal], $ownSucursalIds);

            // Usar @is_sync para que el trigger no vuelva a marcar editado_nube
            $conn->query("SET @is_sync = 1");
            $resetStmt = $conn->prepare($resetSql);
            $resetStmt->bind_param($resetTypes, ...$resetParams);
            $resetStmt->execute();
            $resetStmt->close();
            $conn->query("SET @is_sync = NULL");
        }

        sendResponse(true, "Datos enviados", [
            'records'  => $records,
            'has_more' => $hasMore,
            'page'     => $page,
            'count'    => count($records),
            'cloud_edits' => count($ownSucursalIds),
        ]);

    } catch (Exception $e) {
        sendResponse(false, "Error obteniendo datos: " . $e->getMessage());
    }
}

// =============================================
// FUNCIONES AUXILIARES PARA BD NUBE
// =============================================

// =============================================
// syncCloudReset: Limpia datos de UNA sucursal en la nube
// =============================================

function handleSyncCloudReset($conn, $datapost) {
    global $SYNC_TABLES;

    $negocio  = $datapost['negocio'] ?? '';
    $sucursal = $datapost['sucursal'] ?? '';

    if (empty($sucursal)) {
        sendResponse(false, "Sucursal requerida para reset");
        return;
    }

    // Orden inverso para respetar FK (nivel 6 primero, nivel 0 al final)
    $tables = getTablesInOrder($SYNC_TABLES);
    $tableNames = array_keys($tables);
    $tableNames = array_reverse($tableNames);

    $conn->query("SET FOREIGN_KEY_CHECKS = 0");

    $deleted = [];
    foreach ($tableNames as $tableName) {
        $stmt = $conn->prepare("DELETE FROM `$tableName` WHERE sucursal = ?");
        $stmt->bind_param("s", $sucursal);
        $stmt->execute();
        $deleted[$tableName] = $stmt->affected_rows;
        $stmt->close();
    }

    // Limpiar tablas de control SOLO de esta sucursal
    $stmt = $conn->prepare("DELETE FROM sync_control WHERE sucursal = ?");
    $stmt->bind_param("s", $sucursal);
    $stmt->execute();
    $stmt->close();

    $stmt = $conn->prepare("DELETE FROM sync_log WHERE sucursal = ?");
    $stmt->bind_param("s", $sucursal);
    $stmt->execute();
    $stmt->close();

    $stmt = $conn->prepare("DELETE FROM sync_id_map WHERE sucursal_origen = ?");
    $stmt->bind_param("s", $sucursal);
    $stmt->execute();
    $stmt->close();

    $conn->query("SET FOREIGN_KEY_CHECKS = 1");

    $totalDeleted = array_sum($deleted);

    sendResponse(true, "Nube limpiada para sucursal '$sucursal': $totalDeleted registros eliminados", [
        'sucursal' => $sucursal,
        'tablas' => $deleted,
        'total_eliminados' => $totalDeleted,
    ]);
}

/**
 * Revierte un ID de nube al ID original de la sucursal.
 * Usado en syncSend para devolver FKs con IDs locales (originales)
 * cuando se envían registros editados en la nube de vuelta a su sucursal.
 *
 * sync_id_map en nube: (tabla, sucursal_origen, id_remoto=id_local_original) → id_local=id_nube
 * Queremos: dado id_nube (id_local en el map), obtener id_local_original (id_remoto en el map)
 */
function reverseCloudId($conn, $table, $sucursal, $cloudId) {
    $stmt = $conn->prepare(
        "SELECT id_remoto FROM sync_id_map WHERE tabla = ? AND sucursal_origen = ? AND id_local = ?"
    );
    $stmt->bind_param('ssi', $table, $sucursal, $cloudId);
    $stmt->execute();
    $result = $stmt->get_result();
    $row = $result->fetch_assoc();
    $stmt->close();

    return $row ? (int)$row['id_remoto'] : null;
}

/**
 * Busca el ID de la nube para un registro dado su tabla, sucursal e id_origen.
 * Primero busca en sync_id_map, si no busca directamente en la tabla.
 */
function lookupCloudId($conn, $table, $sucursal, $idOrigen) {
    // Buscar en sync_id_map (sucursal_origen = sucursal, id_remoto = id_origen)
    $stmt = $conn->prepare(
        "SELECT id_local FROM sync_id_map WHERE tabla = ? AND sucursal_origen = ? AND id_remoto = ?"
    );
    $stmt->bind_param('ssi', $table, $sucursal, $idOrigen);
    $stmt->execute();
    $result = $stmt->get_result();
    $row = $result->fetch_assoc();
    $stmt->close();

    if ($row) return (int)$row['id_local'];

    // Fallback: buscar directamente en la tabla por (sucursal, id_origen)
    $stmt2 = $conn->prepare("SELECT id FROM `$table` WHERE sucursal = ? AND id_origen = ?");
    if (!$stmt2) return null;
    $stmt2->bind_param('si', $sucursal, $idOrigen);
    $stmt2->execute();
    $result2 = $stmt2->get_result();
    $row2 = $result2->fetch_assoc();
    $stmt2->close();

    if ($row2) {
        // Guardar en sync_id_map para futuras búsquedas
        saveIdMapping($conn, $table, $sucursal, $idOrigen, (int)$row2['id']);
        return (int)$row2['id'];
    }

    return null;
}

/**
 * Busca un registro en la nube por (sucursal, id_origen)
 */
function getCloudRecord($conn, $table, $sucursal, $idOrigen, $tsCol) {
    $stmt = $conn->prepare(
        "SELECT id, `$tsCol` FROM `$table` WHERE sucursal = ? AND id_origen = ?"
    );
    $stmt->bind_param('si', $sucursal, $idOrigen);
    $stmt->execute();
    $result = $stmt->get_result();
    $row = $result->fetch_assoc();
    $stmt->close();
    return $row;
}

/**
 * Actualiza un registro en la nube por (sucursal, id_origen)
 */
function updateCloudRecord($conn, $table, $sucursal, $idOrigen, $data) {
    if (empty($data)) return;

    $sets = [];
    $params = [];
    $types = '';

    foreach ($data as $col => $val) {
        $sets[] = "`$col` = ?";
        $params[] = $val;
        if (is_null($val)) {
            $types .= 's';
        } elseif (is_int($val)) {
            $types .= 'i';
        } elseif (is_float($val)) {
            $types .= 'd';
        } else {
            $types .= 's';
        }
    }

    $params[] = $sucursal;
    $params[] = $idOrigen;
    $types .= 'si';

    $sql = "UPDATE `$table` SET " . implode(', ', $sets) . " WHERE sucursal = ? AND id_origen = ?";
    $stmt = $conn->prepare($sql);
    $stmt->bind_param($types, ...$params);
    $stmt->execute();
    $stmt->close();
}

/**
 * Inserta un registro nuevo en la nube
 */
function insertCloudRecord($conn, $table, $data, &$errorMsg = '') {
    if (empty($data)) {
        $errorMsg = 'Data vacía';
        return false;
    }

    $columns = array_keys($data);
    $colList = implode(', ', array_map(function($c) { return "`$c`"; }, $columns));
    $placeholders = implode(', ', array_fill(0, count($columns), '?'));

    $params = array_values($data);
    $types = '';
    foreach ($params as $val) {
        if (is_null($val)) {
            $types .= 's';
        } elseif (is_int($val)) {
            $types .= 'i';
        } elseif (is_float($val)) {
            $types .= 'd';
        } else {
            $types .= 's';
        }
    }

    $sql = "INSERT INTO `$table` ($colList) VALUES ($placeholders)";
    $stmt = $conn->prepare($sql);
    if (!$stmt) {
        $errorMsg = 'Prepare: ' . $conn->error;
        return false;
    }
    if (!$stmt->bind_param($types, ...$params)) {
        $errorMsg = 'Bind: ' . $stmt->error;
        $stmt->close();
        return false;
    }
    if (!$stmt->execute()) {
        $errorMsg = 'Execute: ' . $stmt->error;
        $stmt->close();
        return false;
    }
    $id = $stmt->insert_id;
    $stmt->close();

    return $id;
}
