<?php
/**
 * Motor de sincronización BD Local ↔ BD Nube
 *
 * Orquestador principal: maneja push, pull, fullSync, status y log.
 * Incluido desde api.php cuando tipo = syncPush|syncPull|syncFullSync|syncStatus|syncLog|syncPushTable|syncPullTable
 */
if (!defined('BASEPATH')) {
    die('Acceso directo no permitido');
}

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

// $conn ya está disponible desde api.php
// $tipo ya está disponible desde api.php
// $datapost ya está disponible desde api.php

switch ($tipo) {
    case 'syncPush':
        handleSyncPush($conn, $datapost);
        break;

    case 'syncPushTable':
        handleSyncPushTable($conn, $datapost);
        break;

    case 'syncPull':
        handleSyncPull($conn, $datapost);
        break;

    case 'syncPullTable':
        handleSyncPullTable($conn, $datapost);
        break;

    case 'syncFullSync':
        handleSyncFullSync($conn, $datapost);
        break;

    case 'syncStatus':
        handleSyncStatus($conn, $datapost);
        break;

    case 'syncLog':
        handleSyncLog($conn, $datapost);
        break;

    case 'syncReset':
        handleSyncReset($conn, $datapost);
        break;

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

// =============================================
// PUSH: Local → Nube
// =============================================

/**
 * Push completo: envía todas las tablas a la nube en orden FK
 */
function handleSyncPush($conn, $datapost) {
    global $SYNC_TABLES;

    $config = getSyncConfig($conn);
    if (empty($config['cloud_url']) || empty($config['sucursal'])) {
        sendResponse(false, "Configuración de nube incompleta. Configure URL, negocio y sucursal en Settings.");
    }

    $tables = getTableNamesInOrder($SYNC_TABLES);
    $results = [];
    $totalEnviados = 0;
    $totalErrores = 0;
    $startTime = microtime(true);

    foreach ($tables as $tableName) {
        $tableResult = pushTable($conn, $tableName, $SYNC_TABLES[$tableName], $config);
        $results[$tableName] = $tableResult;
        $totalEnviados += $tableResult['enviados'];
        if ($tableResult['error']) $totalErrores++;
    }

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

    sendResponse(true, "Push completo", [
        'tablas'     => $results,
        'total_enviados' => $totalEnviados,
        'total_errores'  => $totalErrores,
        'duracion_ms'    => $duracion,
    ]);
}

/**
 * Push de una tabla específica
 */
function handleSyncPushTable($conn, $datapost) {
    global $SYNC_TABLES;

    $tableName = $datapost['tabla'] ?? '';
    if (empty($tableName) || !isset($SYNC_TABLES[$tableName])) {
        sendResponse(false, "Tabla no válida: $tableName");
    }

    $config = getSyncConfig($conn);
    if (empty($config['cloud_url']) || empty($config['sucursal'])) {
        sendResponse(false, "Configuración de nube incompleta.");
    }

    $result = pushTable($conn, $tableName, $SYNC_TABLES[$tableName], $config);
    sendResponse(!$result['error'], $result['message'], $result);
}

/**
 * Ejecuta push de una tabla individual
 *
 * @return array Resultado del push
 */
function pushTable($conn, $tableName, $tableConfig, $syncConfig) {
    $sucursal = $syncConfig['sucursal'];
    $startTime = microtime(true);

    // Registrar inicio
    $logId = logSync($conn, [
        'tabla'     => $tableName,
        'sucursal'  => $sucursal,
        'direccion' => 'push',
        'estado'    => 'iniciado',
    ]);

    try {
        $lastPush = getLastSyncTimestamp($conn, $tableName, $sucursal, 'push');
        $pk = $tableConfig['pk'];
        $tsCol = $tableConfig['timestamp'];
        $excludeCols = $tableConfig['exclude_cols'] ?? [];
        // Excluir columnas de nube si existen en local (no deberían, pero por seguridad)
        $excludeCols = array_merge($excludeCols, ['sucursal', 'id_origen', 'editado_nube']);

        $totalEnviados = 0;
        $offset = 0;
        $batchSize = $syncConfig['batch_size'];
        $maxTimestamp = $lastPush;

        do {
            $batch = getModifiedRecords(
                $conn, $tableName, $pk, $tsCol, $lastPush, $batchSize, $offset, $excludeCols
            );

            if (empty($batch['records'])) break;

            // Enviar lote a la nube
            $payload = [
                'negocio'   => $syncConfig['negocio'],
                'sucursal'  => $sucursal,
                'tabla'     => $tableName,
                'registros' => $batch['records'],
            ];

            $response = callCloudApi(
                $syncConfig['cloud_url'],
                'syncReceive',
                $payload,
                $syncConfig['timeout']
            );

            if (!$response['success']) {
                throw new Exception("Error del servidor nube: " . ($response['message'] ?? 'Sin detalle'));
            }

            $totalEnviados += count($batch['records']);
            if ($batch['max_timestamp']) {
                $maxTimestamp = $batch['max_timestamp'];
            }

            $offset += $batchSize;
        } while ($batch['has_more']);

        // Actualizar marca de agua
        if ($maxTimestamp) {
            updateSyncTimestamp($conn, $tableName, $sucursal, 'push', $maxTimestamp);
        }

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

        updateSyncLog($conn, $logId, [
            'registros_enviados'    => $totalEnviados,
            'ultimo_sync_timestamp' => $maxTimestamp,
            'estado'                => 'completado',
            'duracion_ms'           => $duracion,
        ]);

        return [
            'tabla'     => $tableName,
            'enviados'  => $totalEnviados,
            'timestamp' => $maxTimestamp,
            'duracion_ms' => $duracion,
            'error'     => false,
            'message'   => "OK: $totalEnviados registros enviados",
        ];

    } catch (Exception $e) {
        $duracion = round((microtime(true) - $startTime) * 1000);

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

        return [
            'tabla'     => $tableName,
            'enviados'  => 0,
            'timestamp' => null,
            'duracion_ms' => $duracion,
            'error'     => true,
            'message'   => $e->getMessage(),
        ];
    }
}

// =============================================
// PULL: Nube → Local
// =============================================

/**
 * Pull completo: recibe datos de todas las tablas desde la nube
 */
function handleSyncPull($conn, $datapost) {
    global $SYNC_TABLES;

    $config = getSyncConfig($conn);
    if (empty($config['cloud_url']) || empty($config['sucursal'])) {
        sendResponse(false, "Configuración de nube incompleta.");
    }

    $tables = getTableNamesInOrder($SYNC_TABLES);
    $results = [];
    $totalRecibidos = 0;
    $totalConflictos = 0;
    $totalErrores = 0;
    $startTime = microtime(true);

    foreach ($tables as $tableName) {
        $tableResult = pullTable($conn, $tableName, $SYNC_TABLES[$tableName], $config);
        $results[$tableName] = $tableResult;
        $totalRecibidos += $tableResult['recibidos'];
        $totalConflictos += $tableResult['conflictos'];
        if ($tableResult['error']) $totalErrores++;
    }

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

    sendResponse(true, "Pull completo", [
        'tablas'           => $results,
        'total_recibidos'  => $totalRecibidos,
        'total_conflictos' => $totalConflictos,
        'total_errores'    => $totalErrores,
        'duracion_ms'      => $duracion,
    ]);
}

/**
 * Pull de una tabla específica
 */
function handleSyncPullTable($conn, $datapost) {
    global $SYNC_TABLES;

    $tableName = $datapost['tabla'] ?? '';
    if (empty($tableName) || !isset($SYNC_TABLES[$tableName])) {
        sendResponse(false, "Tabla no válida: $tableName");
    }

    $config = getSyncConfig($conn);
    if (empty($config['cloud_url']) || empty($config['sucursal'])) {
        sendResponse(false, "Configuración de nube incompleta.");
    }

    $result = pullTable($conn, $tableName, $SYNC_TABLES[$tableName], $config);
    sendResponse(!$result['error'], $result['message'], $result);
}

/**
 * Ejecuta pull de una tabla individual
 */
function pullTable($conn, $tableName, $tableConfig, $syncConfig) {
    $sucursal = $syncConfig['sucursal'];
    $startTime = microtime(true);

    $logId = logSync($conn, [
        'tabla'     => $tableName,
        'sucursal'  => $sucursal,
        'direccion' => 'pull',
        'estado'    => 'iniciado',
    ]);

    try {
        $lastPull = getLastSyncTimestamp($conn, $tableName, $sucursal, 'pull');
        $pk = $tableConfig['pk'];
        $tsCol = $tableConfig['timestamp'];
        $fkDefs = $tableConfig['fk'];
        $excludeCols = $tableConfig['exclude_cols'] ?? [];

        $totalRecibidos = 0;
        $totalConflictos = 0;
        $maxTimestamp = $lastPull;
        $hasMore = true;
        $page = 0;

        while ($hasMore) {
            // Pedir registros a la nube
            $payload = [
                'negocio'  => $syncConfig['negocio'],
                'sucursal' => $sucursal,
                'tabla'    => $tableName,
                'since'    => $lastPull,
                'page'     => $page,
                'batch_size' => $syncConfig['batch_size'],
            ];

            $response = callCloudApi(
                $syncConfig['cloud_url'],
                'syncSend',
                $payload,
                $syncConfig['timeout']
            );

            if (!$response['success']) {
                throw new Exception("Error del servidor nube: " . ($response['message'] ?? 'Sin detalle'));
            }

            $records = $response['data']['records'] ?? [];
            $hasMore = $response['data']['has_more'] ?? false;

            if (empty($records)) break;

            // Procesar cada registro
            foreach ($records as $record) {
                $remoteSucursal = $record['sucursal'] ?? '';
                $remoteIdOrigen = (int)($record['id_origen'] ?? 0);
                $remoteTimestamp = $record[$tsCol] ?? null;
                $isOwnSucursal = ($remoteSucursal === $sucursal);

                // Quitar columnas de nube del registro
                unset($record['sucursal'], $record['id_origen']);
                // Quitar PK original del registro (se genera local o se usa directo)
                $remotePk = $record[$pk] ?? null;
                unset($record[$pk]);

                // Quitar columnas excluidas
                foreach ($excludeCols as $excCol) {
                    unset($record[$excCol]);
                }

                if ($isOwnSucursal) {
                    // ====================================================
                    // REGISTRO DE LA PROPIA SUCURSAL (editado en la nube)
                    // id_origen ES el ID local directamente, no hay que buscar en sync_id_map
                    // No mapear FKs (ya usan IDs locales)
                    // ====================================================
                    $localId = $remoteIdOrigen;

                    // Verificar que el registro existe localmente
                    $localRecord = getLocalRecord($conn, $tableName, $pk, $localId, $tsCol);

                    if ($localRecord) {
                        // Verificar conflicto: si la local fue editada DESPUÉS que la nube, mantener local
                        if ($remoteTimestamp) {
                            $winner = resolveConflict(
                                $localRecord[$tsCol] ?? '1970-01-01',
                                $remoteTimestamp,
                                $sucursal,
                                '__cloud__'
                            );

                            if ($winner === 'local') {
                                $totalConflictos++;
                                continue;
                            }
                        }

                        // Actualizar con los datos editados en la nube
                        $updateResult = updateLocalRecord($conn, $tableName, $pk, $localId, $record);
                        if ($updateResult) {
                            $totalRecibidos++;
                        } else {
                            // Log del error para diagnóstico
                            error_log("[SyncPull] ERROR updateLocalRecord tabla=$tableName localId=$localId error=" . $conn->error);
                            error_log("[SyncPull] Columnas en record: " . implode(', ', array_keys($record)));
                        }
                    } else {
                        error_log("[SyncPull] Registro no encontrado localmente: tabla=$tableName id=$localId");
                    }

                } else {
                    // ====================================================
                    // REGISTRO DE OTRA SUCURSAL (comportamiento original)
                    // ====================================================

                    // Mapear foreign keys
                    if (!empty($fkDefs)) {
                        $record = mapForeignKeys($conn, $record, $fkDefs, $remoteSucursal);
                    }

                    // Buscar si ya existe localmente (por mapeo)
                    $localId = lookupLocalId($conn, $tableName, $remoteSucursal, $remoteIdOrigen);

                    if ($localId !== null) {
                        // Ya existe: verificar conflicto
                        $localRecord = getLocalRecord($conn, $tableName, $pk, $localId, $tsCol);

                        if ($localRecord && $remoteTimestamp) {
                            $winner = resolveConflict(
                                $localRecord[$tsCol] ?? '1970-01-01',
                                $remoteTimestamp,
                                $sucursal,
                                $remoteSucursal
                            );

                            if ($winner === 'local') {
                                $totalConflictos++;
                                continue; // Mantener versión local
                            }
                        }

                        // Actualizar registro existente
                        updateLocalRecord($conn, $tableName, $pk, $localId, $record);
                    } else {
                        // Nuevo registro: insertar
                        $localId = insertLocalRecord($conn, $tableName, $record);

                        if ($localId) {
                            // Guardar mapeo
                            saveIdMapping($conn, $tableName, $remoteSucursal, $remoteIdOrigen, $localId);
                        }
                    }

                    $totalRecibidos++;
                }

                if ($remoteTimestamp && (!$maxTimestamp || $remoteTimestamp > $maxTimestamp)) {
                    $maxTimestamp = $remoteTimestamp;
                }
            }

            $page++;
        }

        // Actualizar marca de agua
        if ($maxTimestamp) {
            updateSyncTimestamp($conn, $tableName, $sucursal, 'pull', $maxTimestamp);
        }

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

        updateSyncLog($conn, $logId, [
            'registros_recibidos'   => $totalRecibidos,
            'registros_conflictos'  => $totalConflictos,
            'ultimo_sync_timestamp' => $maxTimestamp,
            'estado'                => 'completado',
            'duracion_ms'           => $duracion,
        ]);

        return [
            'tabla'       => $tableName,
            'recibidos'   => $totalRecibidos,
            'conflictos'  => $totalConflictos,
            'timestamp'   => $maxTimestamp,
            'duracion_ms' => $duracion,
            'error'       => false,
            'message'     => "OK: $totalRecibidos recibidos, $totalConflictos conflictos",
        ];

    } catch (Exception $e) {
        $duracion = round((microtime(true) - $startTime) * 1000);

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

        return [
            'tabla'       => $tableName,
            'recibidos'   => 0,
            'conflictos'  => 0,
            'timestamp'   => null,
            'duracion_ms' => $duracion,
            'error'       => true,
            'message'     => $e->getMessage(),
        ];
    }
}

/**
 * Obtiene un registro local por PK
 */
function getLocalRecord($conn, $table, $pk, $id, $tsCol) {
    $stmt = $conn->prepare("SELECT `$pk`, `$tsCol` FROM `$table` WHERE `$pk` = ?");
    $stmt->bind_param('i', $id);
    $stmt->execute();
    $result = $stmt->get_result();
    $row = $result->fetch_assoc();
    $stmt->close();
    return $row;
}

/**
 * Actualiza un registro local existente
 */
function updateLocalRecord($conn, $table, $pk, $id, $data) {
    if (empty($data)) return false;

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

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

    $params[] = $id;
    $types .= 'i';

    $sql = "UPDATE `$table` SET " . implode(', ', $sets) . " WHERE `$pk` = ?";
    $stmt = $conn->prepare($sql);
    if (!$stmt) return false;

    $stmt->bind_param($types, ...$params);
    $result = $stmt->execute();
    $stmt->close();

    return $result;
}

/**
 * Inserta un registro nuevo en la tabla local
 *
 * @return int|false  ID insertado o false
 */
function insertLocalRecord($conn, $table, $data) {
    if (empty($data)) 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) {
        $types .= is_int($val) ? 'i' : (is_float($val) ? 'd' : 's');
    }

    $sql = "INSERT INTO `$table` ($colList) VALUES ($placeholders)";
    $stmt = $conn->prepare($sql);
    if (!$stmt) return false;

    $stmt->bind_param($types, ...$params);
    $stmt->execute();
    $id = $stmt->insert_id;
    $stmt->close();

    return $id ?: false;
}

// =============================================
// FULL SYNC: Push + Pull
// =============================================

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

    $config = getSyncConfig($conn);
    if (empty($config['cloud_url']) || empty($config['sucursal'])) {
        sendResponse(false, "Configuración de nube incompleta.");
    }

    $tables = getTableNamesInOrder($SYNC_TABLES);
    $pushResults = [];
    $pullResults = [];
    $startTime = microtime(true);

    // Fase 1: Push (enviar cambios locales)
    foreach ($tables as $tableName) {
        $pushResults[$tableName] = pushTable($conn, $tableName, $SYNC_TABLES[$tableName], $config);
    }

    // Fase 2: Pull (recibir cambios remotos)
    foreach ($tables as $tableName) {
        $pullResults[$tableName] = pullTable($conn, $tableName, $SYNC_TABLES[$tableName], $config);
    }

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

    $totalPushEnviados = array_sum(array_column($pushResults, 'enviados'));
    $totalPullRecibidos = array_sum(array_column($pullResults, 'recibidos'));
    $totalConflictos = array_sum(array_column($pullResults, 'conflictos'));

    sendResponse(true, "Sincronización completa", [
        'push'             => $pushResults,
        'pull'             => $pullResults,
        'total_enviados'   => $totalPushEnviados,
        'total_recibidos'  => $totalPullRecibidos,
        'total_conflictos' => $totalConflictos,
        'duracion_ms'      => $duracion,
    ]);
}

// =============================================
// STATUS: Estado actual de sincronización
// =============================================

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

    $config = getSyncConfig($conn);
    $sucursal = $config['sucursal'] ?: 'sin_configurar';
    $tables = getTableNamesInOrder($SYNC_TABLES);
    $status = [];

    foreach ($tables as $tableName) {
        $tableConf = $SYNC_TABLES[$tableName];
        $lastPush = getLastSyncTimestamp($conn, $tableName, $sucursal, 'push');
        $lastPull = getLastSyncTimestamp($conn, $tableName, $sucursal, 'pull');

        // Contar pendientes de push
        $pendingPush = 0;
        try {
            $pendingPush = countPendingRecords($conn, $tableName, $tableConf['timestamp'], $lastPush);
        } catch (Exception $e) {
            // Tabla puede no existir aún
        }

        $status[$tableName] = [
            'level'         => $tableConf['level'],
            'mode'          => $tableConf['mode'],
            'last_push_at'  => $lastPush,
            'last_pull_at'  => $lastPull,
            'pending_push'  => $pendingPush,
        ];
    }

    sendResponse(true, "Estado de sincronización", [
        'sucursal'        => $sucursal,
        'negocio'         => $config['negocio'],
        'cloud_url'       => $config['cloud_url'] ? '***configurado***' : 'no_configurado',
        'tablas'          => $status,
        'total_tablas'    => count($status),
    ]);
}

// =============================================
// LOG: Historial de sincronizaciones
// =============================================

function handleSyncLog($conn, $datapost) {
    $limit = (int)($datapost['limit'] ?? 50);
    $offset = (int)($datapost['offset'] ?? 0);
    $tabla = $datapost['tabla'] ?? null;
    $estado = $datapost['estado'] ?? null;

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

    if ($tabla) {
        $where[] = "tabla = ?";
        $params[] = $tabla;
        $types .= 's';
    }
    if ($estado) {
        $where[] = "estado = ?";
        $params[] = $estado;
        $types .= 's';
    }

    $whereClause = !empty($where) ? 'WHERE ' . implode(' AND ', $where) : '';

    // Total
    $countSql = "SELECT COUNT(*) as total FROM sync_log $whereClause";
    if (!empty($params)) {
        $stmt = $conn->prepare($countSql);
        $stmt->bind_param($types, ...$params);
        $stmt->execute();
    } else {
        $stmt = $conn->prepare($countSql);
        $stmt->execute();
    }
    $total = $stmt->get_result()->fetch_assoc()['total'];
    $stmt->close();

    // Registros
    $sql = "SELECT * FROM sync_log $whereClause ORDER BY created_at DESC LIMIT ? OFFSET ?";
    $allParams = array_merge($params, [$limit, $offset]);
    $allTypes = $types . 'ii';

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

    $logs = [];
    while ($row = $result->fetch_assoc()) {
        $logs[] = $row;
    }
    $stmt->close();

    sendResponse(true, "Historial de sincronización", [
        'logs'   => $logs,
        'total'  => $total,
        'limit'  => $limit,
        'offset' => $offset,
    ]);
}

// =============================================
// RESET: Borrar datos de sync y re-enviar todo
// =============================================

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

    $target = $datapost['target'] ?? 'local'; // 'local', 'cloud', o 'both'
    $config = getSyncConfig($conn);
    $sucursal = $config['sucursal'] ?: '';

    $results = ['local' => null, 'cloud' => null];

    // Reset local: limpiar tablas de control
    if ($target === 'local' || $target === 'both') {
        $conn->query("TRUNCATE TABLE sync_control");
        $conn->query("TRUNCATE TABLE sync_log");
        $conn->query("TRUNCATE TABLE sync_id_map");
        $results['local'] = 'OK - sync_control, sync_log, sync_id_map limpiados';
    }

    // Reset cloud: pedir a la nube que limpie datos
    if ($target === 'cloud' || $target === 'both') {
        if (empty($config['cloud_url'])) {
            $results['cloud'] = 'Error: URL de nube no configurada';
        } else {
            $response = callCloudApi(
                $config['cloud_url'],
                'syncCloudReset',
                ['negocio' => $config['negocio'], 'sucursal' => $sucursal],
                60
            );
            $results['cloud'] = $response['success']
                ? 'OK - ' . ($response['message'] ?? 'Nube limpiada')
                : 'Error: ' . ($response['message'] ?? 'Fallo');
        }
    }

    sendResponse(true, "Reset completado", $results);
}
