<?php
/**
 * API para Receber Dados de Realizado via Push (HTTPS POST)
 * 
 * O servidor ERP envia dados para cá. Protegido por token Bearer.
 * Este arquivo é INDEPENDENTE do fluxo de sincronização existente (pull via cron).
 * 
 * Rota: POST https://helm.tec.br/api_receber_realizado.php
 * Headers: Authorization: Bearer {token}, Content-Type: application/json
 * 
 * Body JSON:
 * {
 *   "cdintegracao": 1,
 *   "dtinicio": "2026-02-01",
 *   "dtfim": "2026-02-28",
 *   "registros": [
 *     {
 *       "cdempresa": 1,
 *       "cdcentrocusto": 10,
 *       "cdplanocontabil": 100,
 *       "dtmovimento": "2026-02-15",
 *       "tpmovimento": "D",
 *       "valor": 1500.50,
 *       "dscomplemento": "NF 12345",
 *       "cdorigem": 99001
 *     }
 *   ]
 * }
 * 
 * Resposta JSON:
 * {
 *   "ok": true,
 *   "inseridos": 150,
 *   "ignorados": 3,
 *   "erros": 0,
 *   "consolidacao": "OK",
 *   "mensagem": "..."
 * }
 */

declare(strict_types=1);

header('Content-Type: application/json; charset=utf-8');

// ====== APENAS GET ou POST ======
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
    // Preflight CORS (se necessário no futuro)
    http_response_code(204);
    exit;
}

if (!in_array($_SERVER['REQUEST_METHOD'], ['GET', 'POST'], true)) {
    http_response_code(405);
    echo json_encode(['ok' => false, 'erro' => 'Método não permitido. Use GET (config) ou POST (dados).']);
    exit;
}

// ====== AUTENTICAÇÃO POR TOKEN BEARER ======
// LiteSpeed/Apache pode não passar Authorization header por padrão.
// O .htaccess tem regra para passar via REDIRECT_HTTP_AUTHORIZATION.
$authHeader = $_SERVER['HTTP_AUTHORIZATION'] 
    ?? $_SERVER['REDIRECT_HTTP_AUTHORIZATION'] 
    ?? '';

if (!preg_match('/^Bearer\s+(.+)$/i', $authHeader, $matches)) {
    http_response_code(401);
    echo json_encode(['ok' => false, 'erro' => 'Token de autenticação ausente. Use header Authorization: Bearer {token}']);
    exit;
}
$tokenRecebido = trim($matches[1]);

// ====== CONECTAR BANCO ======
require_once dirname(__DIR__) . '/app/config/db.php';

// Importar funções de realizado_actions sem executar o switch
if (!defined('HOME_ACTIONS_LOADING')) {
    define('HOME_ACTIONS_LOADING', true);
}
require_once dirname(__DIR__) . '/app/modules/realizado_actions.php';
require_once dirname(__DIR__) . '/app/lib/origem_sync_lib.php';

// ====== VALIDAR TOKEN CONTRA INTEGRAÇÃO ======
// O token plaintext é verificado contra o hash bcrypt armazenado em back.integracao.api_token_hash
$stmtToken = $pdo->prepare("
    SELECT cdintegracao, dsintegracao, api_token_hash
    FROM back.integracao 
    WHERE flativo = true 
      AND api_token_hash IS NOT NULL
      AND api_token_hash != ''
");
$stmtToken->execute();
$integracoes = $stmtToken->fetchAll(PDO::FETCH_ASSOC);

$integracaoAutenticada = null;
foreach ($integracoes as $integ) {
    if (password_verify($tokenRecebido, $integ['api_token_hash'])) {
        $integracaoAutenticada = $integ;
        break;
    }
}

if (!$integracaoAutenticada) {
    // Rate limiting simples: delay para dificultar brute force
    usleep(500000); // 0.5s
    http_response_code(401);
    echo json_encode(['ok' => false, 'erro' => 'Token inválido ou integração inativa.']);
    exit;
}

$cdintegracao = (int)$integracaoAutenticada['cdintegracao'];
$dsintegracao = $integracaoAutenticada['dsintegracao'];

// ====== GET = RETORNAR CONFIGURAÇÃO DA INTEGRAÇÃO ======
// O push_realizado.php usa isso para auto-configurar SQL e conexão do ERP
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
    $stmtCfg = $pdo->prepare("
        SELECT cdintegracao, dsintegracao, tipo_banco, host, porta, dbname, 
               usuario, senha_encrypted, schema_banco, sql_origem
        FROM back.integracao
        WHERE cdintegracao = :id
    ");
    $stmtCfg->execute([':id' => $cdintegracao]);
    $cfg = $stmtCfg->fetch(PDO::FETCH_ASSOC);

    if (!$cfg) {
        http_response_code(404);
        echo json_encode(['ok' => false, 'erro' => 'Configuração não encontrada.']);
        exit;
    }

    // Retornar config (sem expor o hash do token)
    // Incluir TODAS as integrações de origem vinculadas (para o PS1 executar localmente)
    $origensVinculadas = origemBuscarVinculadas($cdintegracao, $pdo);
    $origensConfig = [];
    foreach ($origensVinculadas as $ov) {
        $ovId = (int)$ov['cdintegracao'];

        $stmtOv = $pdo->prepare("SELECT cdintegracao, dsintegracao, sql_origem, campo_empresa FROM back.integracao WHERE cdintegracao = :id AND flativo = true");
        $stmtOv->execute([':id' => $ovId]);
        $ovCfg = $stmtOv->fetch(PDO::FETCH_ASSOC);
        if ($ovCfg && !empty(trim($ovCfg['sql_origem'] ?? ''))) {
            $origensConfig[] = [
                'cdintegracao'  => (int)$ovCfg['cdintegracao'],
                'dsintegracao'  => $ovCfg['dsintegracao'],
                'sql_origem'    => $ovCfg['sql_origem'],
                'campo_empresa' => $ovCfg['campo_empresa'] ?? '',
            ];
        }
    }

    echo json_encode([
        'ok' => true,
        'config' => [
            'cdintegracao'  => (int)$cfg['cdintegracao'],
            'dsintegracao'  => $cfg['dsintegracao'],
            'tipo_banco'    => $cfg['tipo_banco'],
            'host'          => $cfg['host'],
            'porta'         => (int)($cfg['porta'] ?? 0),
            'dbname'        => $cfg['dbname'],
            'usuario'       => $cfg['usuario'],
            'senha'         => $cfg['senha_encrypted'],
            'schema_banco'  => $cfg['schema_banco'] ?? '',
            'sql_origem'    => $cfg['sql_origem'],
        ],
        'origens' => $origensConfig,
    ]);
    exit;
}

// ====== POST = RECEBER DADOS ======

// ====== PARSEAR PAYLOAD ======
$rawBody = file_get_contents('php://input');
if (empty($rawBody)) {
    http_response_code(400);
    echo json_encode(['ok' => false, 'erro' => 'Corpo da requisição vazio.']);
    exit;
}

$payload = json_decode($rawBody, true);
if (!$payload || !is_array($payload)) {
    http_response_code(400);
    echo json_encode(['ok' => false, 'erro' => 'JSON inválido no corpo da requisição.']);
    exit;
}

// Validar que cdintegracao do payload bate com o token (se informado)
if (isset($payload['cdintegracao']) && (int)$payload['cdintegracao'] !== $cdintegracao) {
    http_response_code(403);
    echo json_encode(['ok' => false, 'erro' => 'Token não corresponde à integração informada no payload.']);
    exit;
}

$registros = $payload['registros'] ?? [];
if (empty($registros) || !is_array($registros)) {
    echo json_encode(['ok' => true, 'mensagem' => 'Nenhum registro enviado.', 'inseridos' => 0, 'ignorados' => 0, 'erros' => 0]);
    exit;
}

// Proteção contra payloads gigantes
$maxRegistros = 10000;
if (count($registros) > $maxRegistros) {
    http_response_code(413);
    echo json_encode(['ok' => false, 'erro' => "Máximo de {$maxRegistros} registros por requisição. Envie em lotes menores."]);
    exit;
}

$dtinicio = $payload['dtinicio'] ?? null;
$dtfim    = $payload['dtfim'] ?? null;

// Suporte a envio em múltiplos lotes HTTP:
// lote_num = número do lote atual (1-based)
// total_lotes = total de lotes que serão enviados
// Apenas o lote 1 faz DELETE; apenas o último faz refresh das views.
$loteNum    = isset($payload['lote_num']) ? (int)$payload['lote_num'] : 1;
$totalLotes = isset($payload['total_lotes']) ? (int)$payload['total_lotes'] : 1;
$isPrimeiroLote = ($loteNum <= 1);
$isUltimoLote   = ($loteNum >= $totalLotes);

// Se não informou período, calcular a partir dos registros recebidos
if (!$dtinicio || !$dtfim) {
    $datas = array_filter(array_column($registros, 'dtmovimento'));
    if (!empty($datas)) {
        sort($datas);
        $dtinicio = $dtinicio ?: reset($datas);
        $dtfim    = $dtfim ?: end($datas);
    } else {
        $dtinicio = date('Y-m-01');
        $dtfim    = date('Y-m-t');
    }
}

// ====== EMPRESAS PERMITIDAS PARA ESTA INTEGRAÇÃO ======
$stmtEmp = $pdo->prepare('SELECT cdempresa FROM back.integracao_empresa WHERE cdintegracao = :id');
$stmtEmp->execute([':id' => $cdintegracao]);
$empresasPermitidas = array_map('intval', $stmtEmp->fetchAll(PDO::FETCH_COLUMN));

if (empty($empresasPermitidas)) {
    echo json_encode(['ok' => false, 'erro' => 'Nenhuma empresa vinculada a esta integração.']);
    exit;
}

// Mapa ERP cdempresa -> HELM cdempresa (via back.empresa.cdintegracao)
$mapEmpresa = [];
$resEmp = $pdo->query("SELECT cdempresa, cdintegracao FROM back.empresa WHERE cdintegracao IS NOT NULL");
if ($resEmp) {
    foreach ($resEmp->fetchAll(PDO::FETCH_ASSOC) as $rowEmp) {
        $mapEmpresa[(int)$rowEmp['cdintegracao']] = (int)$rowEmp['cdempresa'];
    }
}

// ====== PROCESSAR REGISTROS (mesma lógica de sincronizarIntegracaoAgora) ======
if (function_exists('set_time_limit')) {
    @set_time_limit(120);
}

$dtExecInicio = date('Y-m-d H:i:s');

$cacheExercicio = [];
$stmtExSel = $pdo->prepare('SELECT cdexercicio FROM back.exercicio WHERE cdempresa = :emp AND anoref = :ano LIMIT 1');
$stmtExIns = $pdo->prepare('INSERT INTO back.exercicio (cdempresa, anoref, descricao, flativo, floficial) VALUES (:emp, :ano, :descricao, 0, 0) RETURNING cdexercicio');

$cacheLote = [];
$stmtLoteSel = $pdo->prepare('
    SELECT idlote FROM back.realizado
    WHERE cdempresa = :emp AND cdcentrocusto = :cc AND anoref = :ano 
      AND florigem = 2 AND cdintegracao = :cdintegracao
    LIMIT 1
');

$inseridos = 0;
$ignorados = 0;
$erros = 0;
$ignoradosMotivos = [
    'empresa_origem_sem_mapeamento' => 0,
    'empresa_nao_vinculada_integracao' => 0,
    'dtmovimento_vazio' => 0,
    'dtmovimento_invalido' => 0,
    'exercicio_nao_criado' => 0,
];

// Rastreamento por empresa HELM (para log igual ao da conexão direta)
$statsPorEmpresa = [];

$batch = [];
$batchSize = 500;
$dtcriacao = date('Y-m-d H:i:s');

$pdo->beginTransaction();

try {
    // Remover lançamentos ERP existentes no período (mesmo comportamento do sync pull)
    // APENAS no primeiro lote - lotes subsequentes só inserem
    if ($isPrimeiroLote) {
        $placeholders = implode(',', array_fill(0, count($empresasPermitidas), '?'));
        $paramsCancel = array_merge([$cdintegracao, $dtinicio, $dtfim], $empresasPermitidas);
        $sqlCancel = "DELETE FROM back.realizado
                      WHERE cdintegracao = ? AND florigem = 2
                        AND dtmovimento BETWEEN ? AND ?
                        AND cdempresa IN ($placeholders)";
        $pdo->prepare($sqlCancel)->execute($paramsCancel);
    }

    foreach ($registros as $reg) {
        $cdempresaOrigem = isset($reg['cdempresa']) ? (int)$reg['cdempresa'] : null;
        if (!$cdempresaOrigem || !isset($mapEmpresa[$cdempresaOrigem])) {
            $ignorados++;
            $ignoradosMotivos['empresa_origem_sem_mapeamento']++;
            continue;
        }

        $cdempresaHelm = $mapEmpresa[$cdempresaOrigem];

        if (!in_array($cdempresaHelm, $empresasPermitidas, true)) {
            $ignorados++;
            $ignoradosMotivos['empresa_nao_vinculada_integracao']++;
            continue;
        }

        $dtmovimento = $reg['dtmovimento'] ?? null;
        if (!$dtmovimento) {
            $ignorados++;
            $ignoradosMotivos['dtmovimento_vazio']++;
            continue;
        }

        try {
            $dt = new DateTime($dtmovimento);
        } catch (Exception $e) {
            $ignorados++;
            $ignoradosMotivos['dtmovimento_invalido']++;
            continue;
        }

        $ano = (int)$dt->format('Y');
        $mes = (int)$dt->format('m');

        // Garantir exercício para (empresa, ano)
        $keyEx = $cdempresaHelm . ':' . $ano;
        if (!isset($cacheExercicio[$keyEx])) {
            $stmtExSel->execute([':emp' => $cdempresaHelm, ':ano' => $ano]);
            $cdexercicio = $stmtExSel->fetchColumn();
            if (!$cdexercicio) {
                $stmtExIns->execute([':emp' => $cdempresaHelm, ':ano' => $ano, ':descricao' => 'Exercicio (Automatico) ' . $ano]);
                $cdexercicio = $stmtExIns->fetchColumn();
            }
            if (!$cdexercicio) {
                $ignorados++;
                $ignoradosMotivos['exercicio_nao_criado']++;
                continue;
            }
            $cacheExercicio[$keyEx] = (int)$cdexercicio;
        }
        $cdexercicio = $cacheExercicio[$keyEx];

        $cdorigem = isset($reg['cdorigem']) ? (int)$reg['cdorigem'] : null;
        $tpmovimento = strtoupper(trim($reg['tpmovimento'] ?? 'D'));
        if ($tpmovimento !== 'D' && $tpmovimento !== 'C') $tpmovimento = 'D';

        $valor = isset($reg['valor']) ? (float)$reg['valor'] : 0.0;
        $cdcentrocusto = isset($reg['cdcentrocusto']) ? (int)$reg['cdcentrocusto'] : null;
        $cdplanocontabil = isset($reg['cdplanocontabil']) ? (int)$reg['cdplanocontabil'] : null;
        $dscomplemento = $reg['dscomplemento'] ?? '';

        // idlote estável por (empresa, centro, ano)
        $chaveLote = $cdempresaHelm . ':' . ($cdcentrocusto ?? 0) . ':' . $ano;
        if (!isset($cacheLote[$chaveLote])) {
            $idloteExistente = null;
            if ($cdcentrocusto !== null) {
                $stmtLoteSel->execute([
                    ':emp' => $cdempresaHelm,
                    ':cc'  => $cdcentrocusto,
                    ':ano' => $ano,
                    ':cdintegracao' => $cdintegracao,
                ]);
                $idloteExistente = $stmtLoteSel->fetchColumn() ?: null;
            }
            $cacheLote[$chaveLote] = $idloteExistente ?: uuid_v4();
        }
        $idlote = $cacheLote[$chaveLote];

        $batch[] = [
            'cdempresa'       => $cdempresaHelm,
            'cdexercicio'     => $cdexercicio,
            'cdcentrocusto'   => $cdcentrocusto,
            'cdplanocontabil' => $cdplanocontabil,
            'mesref'          => $mes,
            'anoref'          => $ano,
            'tpmovimento'     => $tpmovimento,
            'valor'           => $valor,
            'dscomplemento'   => $dscomplemento,
            'dtmovimento'     => $dt->format('Y-m-d'),
            'cdintegracao'    => $cdintegracao,
            'cdorigem'        => $cdorigem,
            'json_origem'     => json_encode($reg, JSON_UNESCAPED_UNICODE),
            'dtcriacao'       => $dtcriacao,
            'idlote'          => $idlote,
        ];

        // Rastrear por empresa HELM
        if (!isset($statsPorEmpresa[$cdempresaHelm])) {
            $statsPorEmpresa[$cdempresaHelm] = [
                'inseridos' => 0, 'ignorados' => 0, 'motivos' => [], 'cdorigensErro' => [], 'idlote' => $idlote,
            ];
        }
        $statsPorEmpresa[$cdempresaHelm]['inseridos']++;
        $statsPorEmpresa[$cdempresaHelm]['idlote'] = $idlote;

        if (count($batch) >= $batchSize) {
            flushBatchInsertRealizado($pdo, $batch, $cdintegracao, 'api_push', 0, $inseridos);
        }
    }

    // Flush registros restantes
    if (!empty($batch)) {
        flushBatchInsertRealizado($pdo, $batch, $cdintegracao, 'api_push', 0, $inseridos);
    }

    $pdo->commit();

    // Refresh materialized views (fora da transação) - APENAS no último lote
    $consolidacao = ['ok' => true, 'skipped' => true, 'mensagens' => ['Aguardando último lote']];
    $cascadeResults = [];
    if ($isUltimoLote) {
        $consolidacao = refreshMaterializedViewsRealizado($pdo);

        // Atualizar última sincronização
        $pdo->prepare('UPDATE back.integracao SET ultima_sincronizacao = NOW() WHERE cdintegracao = :id')
            ->execute([':id' => $cdintegracao]);

        // ✨ ORIGENS: Processar origem_dados enviados pelo PS1 (executados localmente no ERP)
        // Lógica alinhada com origemExecutarSincronizacao() do schedule
        $origemPayload = $payload['origem_dados'] ?? [];
        if (!empty($origemPayload) && is_array($origemPayload)) {

            // Mapa de empresas ERP → Helm com chaves STRING (igual ao schedule)
            $mapaEmpresasStr = [];
            $stmtEmpStr = $pdo->query("SELECT cdempresa, cdintegracao FROM back.empresa WHERE cdintegracao IS NOT NULL AND cdintegracao != ''");
            if ($stmtEmpStr) {
                while ($rowEmpStr = $stmtEmpStr->fetch(PDO::FETCH_ASSOC)) {
                    $mapaEmpresasStr[trim($rowEmpStr['cdintegracao'])] = (int)$rowEmpStr['cdempresa'];
                }
            }

            // Se o PS1 enviou um único objeto em vez de array, normalizar
            if (!isset($origemPayload[0]) && isset($origemPayload['cdintegracao'])) {
                $origemPayload = [$origemPayload];
            }

            foreach ($origemPayload as $origemItem) {
                $orId   = (int)($origemItem['cdintegracao'] ?? 0);
                $orNome = (string)($origemItem['dsintegracao'] ?? '');
                $orCampoEmpresa = trim((string)($origemItem['campo_empresa'] ?? ''));

                // Normalizar registros: se veio objeto único, wrapping em array
                $orRegsRaw = $origemItem['registros'] ?? [];
                if (!is_array($orRegsRaw)) {
                    $orRegsRaw = [];
                }
                // Se é um assoc array (objeto único) em vez de array sequencial, envolver
                if (!empty($orRegsRaw) && !isset($orRegsRaw[0]) && !array_is_list($orRegsRaw)) {
                    $orRegsRaw = [$orRegsRaw];
                }

                if ($orId <= 0 || empty($orRegsRaw)) {
                    $cascadeResults[] = [
                        'cdintegracao' => $orId,
                        'dsintegracao' => $orNome,
                        'ok' => false,
                        'erro' => 'cdintegracao ou registros vazios (recebidos: ' . count($orRegsRaw) . ')',
                    ];
                    continue;
                }

                try {
                    // Gerar nrlote para esta origem (igual schedule: sequencial)
                    $stmtLoteOr = $pdo->prepare("SELECT COALESCE(MAX(nrlote), 0) + 1 FROM back.origem_dados WHERE cdintegracao = :id");
                    $stmtLoteOr->execute([':id' => $orId]);
                    $nrLoteOrigem = (int)$stmtLoteOr->fetchColumn();

                    // Verificar se esta origem tem pai fltipo='conexao'
                    // Se tiver, NÃO deletar dados anteriores (o schedule/conexão gerencia)
                    $stmtPaiConexao = $pdo->prepare("
                        SELECT COUNT(*) FROM back.integracao_conexao ic
                        JOIN back.integracao i ON i.cdintegracao = ic.cdintegracao_conexao
                        WHERE ic.cdintegracao = :id AND i.fltipo = 'conexao' AND i.flativo = true
                    ");
                    $stmtPaiConexao->execute([':id' => $orId]);
                    $temPaiConexao = ((int)$stmtPaiConexao->fetchColumn() > 0);

                    if (!$temPaiConexao) {
                        $pdo->prepare('DELETE FROM back.origem_dados WHERE cdintegracao = :id')
                            ->execute([':id' => $orId]);
                    }

                    // Detectar colunas reais (case-insensitive) a partir do primeiro registro
                    // Igual ao schedule: valida 2 colunas e detecta campo_empresa + coluna_valor
                    $primeiraLinha = $orRegsRaw[0];
                    if (!is_array($primeiraLinha)) {
                        $cascadeResults[] = [
                            'cdintegracao' => $orId,
                            'dsintegracao' => $orNome,
                            'ok' => false,
                            'erro' => 'Registro não é objeto/array válido: ' . gettype($primeiraLinha) . ' = ' . substr(json_encode($primeiraLinha), 0, 100),
                        ];
                        continue;
                    }

                    $colunasSelect = array_keys($primeiraLinha);
                    $campoEmpresaReal = null;
                    $colunaValor = null;

                    // Validar: SELECT deve retornar exatamente 2 colunas (campo_empresa + 1 valor)
                    if (count($colunasSelect) !== 2) {
                        $cascadeResults[] = [
                            'cdintegracao' => $orId,
                            'dsintegracao' => $orNome,
                            'ok' => false,
                            'erro' => 'SELECT deve retornar 2 colunas (campo empresa + valor), mas retornou ' . count($colunasSelect) . ': ' . implode(', ', $colunasSelect),
                        ];
                        continue;
                    }

                    // Detectar nome real da coluna empresa e coluna valor (case-insensitive, igual schedule)
                    foreach ($colunasSelect as $col) {
                        if ($orCampoEmpresa !== '' && strtoupper($col) === strtoupper($orCampoEmpresa)) {
                            $campoEmpresaReal = $col;
                        } else {
                            $colunaValor = $col;
                        }
                    }

                    if ($orCampoEmpresa !== '' && $campoEmpresaReal === null) {
                        $cascadeResults[] = [
                            'cdintegracao' => $orId,
                            'dsintegracao' => $orNome,
                            'ok' => false,
                            'erro' => "Coluna '$orCampoEmpresa' não encontrada. Colunas disponíveis: " . implode(', ', $colunasSelect),
                        ];
                        continue;
                    }

                    // Preparar INSERT (igual schedule)
                    $stmtOrigem = $pdo->prepare('INSERT INTO back.origem_dados (cdintegracao, cdempresa, dados, valor, nrlote) VALUES (:cd, :cdempresa, :dados, :valor, :nrlote)');

                    $orInseridos = 0;
                    $orIgnorados = 0;
                    $orErros     = 0;
                    $orErroDetalhe = '';

                    // Processar todos os registros (igual schedule: foreach $allRows)
                    foreach ($orRegsRaw as $reg) {
                        if (!is_array($reg)) {
                            $orErros++;
                            continue;
                        }
                        try {
                            // Resolver empresa via campo_empresa (igual schedule)
                            $cdempresa = null;
                            if ($campoEmpresaReal !== null && isset($reg[$campoEmpresaReal])) {
                                $valorEmpresaERP = trim((string)$reg[$campoEmpresaReal]);
                                $cdempresa = $mapaEmpresasStr[$valorEmpresaERP] ?? null;
                                if ($cdempresa === null) {
                                    $orIgnorados++; // Empresa do ERP não mapeada no Helm
                                    continue;
                                }
                            }

                            // Extrair valor da coluna de dados (a que NÃO é campo_empresa)
                            $valor = null;
                            if ($colunaValor !== null && isset($reg[$colunaValor])) {
                                $valor = (string)$reg[$colunaValor];
                            }

                            $stmtOrigem->execute([
                                ':cd'        => $orId,
                                ':cdempresa' => $cdempresa,
                                ':dados'     => json_encode($reg, JSON_UNESCAPED_UNICODE),
                                ':valor'     => $valor,
                                ':nrlote'    => $nrLoteOrigem,
                            ]);
                            $orInseridos++;
                        } catch (Throwable $re) {
                            $orErros++;
                            if ($orErroDetalhe === '') {
                                $orErroDetalhe = $re->getMessage(); // capturar primeiro erro para debug
                            }
                        }
                    }

                    // Atualizar última sincronização da origem
                    $pdo->prepare('UPDATE back.integracao SET ultima_sincronizacao = NOW() WHERE cdintegracao = :id')
                        ->execute([':id' => $orId]);

                    $result = [
                        'cdintegracao' => $orId,
                        'dsintegracao' => $orNome,
                        'ok' => true,
                        'total' => $orInseridos,
                        'ignorados' => $orIgnorados,
                        'erros' => $orErros,
                    ];
                    if ($orErroDetalhe !== '') {
                        $result['erro_detalhe'] = $orErroDetalhe;
                    }
                    $cascadeResults[] = $result;
                } catch (Throwable $oe) {
                    $cascadeResults[] = [
                        'cdintegracao' => $orId,
                        'dsintegracao' => $orNome,
                        'ok' => false,
                        'erro' => $oe->getMessage(),
                    ];
                }
            }
        }

        // Origens com conexão direta NÃO são disparadas aqui.
        // São gerenciadas exclusivamente pelo run_integracoes_schedule.php
    }

    // Log de execução — gravar em TODOS os lotes (por empresa HELM, igual conexão direta)
    $dtExecFim = date('Y-m-d H:i:s');
    
    // Log por empresa (garante que o dashboard/home encontre por cdempresa)
    foreach ($statsPorEmpresa as $empId => $stats) {
        $ins = $stats['inseridos'];
        $ign = $stats['ignorados'];
        $mot = $stats['motivos'];
        $cds = $stats['cdorigensErro'];
        $idl = $stats['idlote'];

        $statusExec = 'sucesso';
        if ($erros > 0 && $ins === 0) {
            $statusExec = 'erro';
        } elseif ($erros > 0 || $ign > 0) {
            $statusExec = 'parcial';
        }

        $mensagemExec = "API Push #{$cdintegracao} ({$dsintegracao}) empresa {$empId} - Lote {$loteNum}/{$totalLotes} - {$ins} inseridos, {$ign} ignorados.";

        registrarIntegracaoLogExecucao(
            $pdo, $cdintegracao, (int)$empId, 0, 'api_push', 2, $idl,
            $dtinicio, $dtfim, $dtExecInicio, $dtExecFim,
            $statusExec, $ins, $ign, $erros,
            $mot, $cds, $mensagemExec
        );
    }

    // Log adicional para ignorados sem empresa mapeada
    if ($ignoradosMotivos['empresa_origem_sem_mapeamento'] > 0) {
        $ignSemMap = $ignoradosMotivos['empresa_origem_sem_mapeamento'];
        registrarIntegracaoLogExecucao(
            $pdo, $cdintegracao, null, 0, 'api_push', 2, null,
            $dtinicio, $dtfim, $dtExecInicio, $dtExecFim,
            'parcial', 0, $ignSemMap, 0,
            ['empresa_origem_sem_mapeamento' => $ignSemMap], [],
            "API Push #{$cdintegracao} - {$ignSemMap} registros ignorados (empresa sem mapeamento). Lote {$loteNum}/{$totalLotes}."
        );
    }

    echo json_encode([
        'ok' => true,
        'inseridos' => $inseridos,
        'ignorados' => $ignorados,
        'erros' => $erros,
        'ignorados_motivos' => $ignoradosMotivos,
        'lote_num' => $loteNum,
        'total_lotes' => $totalLotes,
        'consolidacao' => $isUltimoLote ? ($consolidacao['ok'] ? 'OK' : 'Falhou') : 'pendente',
        'origens' => $cascadeResults,
        'mensagem' => "Lote {$loteNum}/{$totalLotes}: {$inseridos} inseridos, {$ignorados} ignorados.",
    ]);

} catch (Throwable $e) {
    if ($pdo->inTransaction()) {
        $pdo->rollBack();
    }

    // Tentar registrar log de erro — por empresa se possível
    try {
        $dtExecFim = date('Y-m-d H:i:s');
        $erroMsg = 'ERRO: ' . $e->getMessage();
        
        if (!empty($statsPorEmpresa)) {
            foreach ($statsPorEmpresa as $empId => $stats) {
                registrarIntegracaoLogExecucao(
                    $pdo, $cdintegracao, (int)$empId, 0, 'api_push', 2, $stats['idlote'] ?? null,
                    $dtinicio, $dtfim, $dtExecInicio, $dtExecFim,
                    'erro', $stats['inseridos'], $stats['ignorados'], 1,
                    $stats['motivos'], $stats['cdorigensErro'], $erroMsg
                );
            }
        } else {
            registrarIntegracaoLogExecucao(
                $pdo, $cdintegracao, null, 0, 'api_push', 2, null,
                $dtinicio, $dtfim, $dtExecInicio, $dtExecFim,
                'erro', $inseridos, $ignorados, 1,
                $ignoradosMotivos, [], $erroMsg
            );
        }
    } catch (Throwable $logErr) {
        error_log("API Push log error: " . $logErr->getMessage());
    }

    http_response_code(500);
    echo json_encode([
        'ok' => false,
        'erro' => 'Erro ao processar registros: ' . $e->getMessage(),
        'inseridos' => $inseridos,
        'ignorados' => $ignorados,
    ]);
}
