<?php
require_once(dirname(__DIR__) . '/config/db.php');
require_once(dirname(__DIR__) . '/lib/origem_sync_lib.php');

ini_set('display_errors', '0');
error_reporting(E_ALL);

$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);

function json_out($arr, $code = 200) {
  http_response_code($code);
  header('Content-Type: application/json; charset=utf-8');
  echo json_encode($arr, JSON_UNESCAPED_UNICODE);
  exit;
}

set_error_handler(function($severity, $message, $file, $line) {
  if (!(error_reporting() & $severity)) return false;
  throw new ErrorException($message, 0, $severity, $file, $line);
});

set_exception_handler(function($e) {
  $loc = basename($e->getFile()) . ':' . $e->getLine();
  json_out(['ok' => false, 'msg' => 'Erro interno (' . $loc . '): ' . $e->getMessage()], 500);
});

/**
 * Valida SQL de origem - APENAS SELECT é aceito
 * Bloqueado: INSERT, UPDATE, DELETE, DROP, ALTER, CREATE, EXEC, etc
 * Permitido: SELECT, WITH (CTEs)
 */
function validarSQLOrigem(string $sql): bool {
  $sqlTrim = rtrim(trim($sql), '; ');
  if ($sqlTrim === '') return false;
  if (strpos($sqlTrim, ';') !== false) return false;
  if (!preg_match('/^\s*(SELECT|WITH)\b/i', $sqlTrim)) return false;
  $bloqueados = ['INSERT','UPDATE','DELETE','MERGE','TRUNCATE','DROP','ALTER','CREATE','GRANT','REVOKE','COMMENT','EXEC','EXECUTE','CALL'];
  foreach ($bloqueados as $cmd) {
    if (preg_match('/\b' . $cmd . '\b/i', $sqlTrim)) return false;
  }
  return true;
}

function extrairColunasSelect(string $sql): array {
  if (!preg_match('/\bSELECT\b(.*?)\bFROM\b/is', $sql, $m)) return [];
  $lista = $m[1];
  $colunas = [];
  $buffer = '';
  $nivel = 0;
  
  for ($i = 0, $len = strlen($lista); $i < $len; $i++) {
    $ch = $lista[$i];
    if ($ch === '(') {
      $nivel++;
      $buffer .= $ch;
    } elseif ($ch === ')') {
      $nivel--;
      $buffer .= $ch;
    } elseif ($ch === ',' && $nivel === 0) {
      $expr = trim($buffer);
      if ($expr !== '') $colunas[] = $expr;
      $buffer = '';
    } else {
      $buffer .= $ch;
    }
  }
  if (trim($buffer) !== '') $colunas[] = trim($buffer);

  $nomes = [];
  foreach ($colunas as $expr) {
    if (preg_match('/\s+AS\s+([^\s,]+)$/i', $expr, $mAs)) {
      $alias = $mAs[1];
    } else {
      $tmp = preg_replace('/[\)]+$/', '', $expr);
      $partes = preg_split('/\s+/', trim($tmp));
      $ultimo = end($partes);
      $subPartes = explode('.', $ultimo);
      $alias = end($subPartes);
    }
    $alias = trim($alias, "`\"'[] ");
    if ($alias === '' || $alias === '*') continue;
    $nomes[] = strtolower($alias);
  }
  
  return array_values(array_unique($nomes));
}

function validarCamposObrigatoriosSQLOrigem(string $sql): array {
  $obrigatorios = [
    'cdorigem' => 'Código de origem do lançamento (identificador no ERP)',
    'cdcentrocusto' => 'Código do centro de custo',
    'cdplanocontabil' => 'Código do plano contábil',
    'dtmovimento' => 'Data do movimento (determina mesref/anoref/cdexercicio)',
    'tpmovimento' => 'Tipo de movimento: D (Débito) ou C (Crédito)',
    'valor' => 'Valor do lançamento',
    'dscomplemento' => 'Descrição/complemento',
    'cdempresa' => 'Código da empresa na origem (relacionamento com back.empresa.cdintegracao)'
  ];
  $colunas = extrairColunasSelect($sql);
  $faltando = [];
  
  foreach ($obrigatorios as $campo => $desc) {
    if (!in_array(strtolower($campo), $colunas)) {
      $faltando[] = "$campo ($desc)";
    }
  }
  
  return $faltando;
}

function validarCamposObrigatorios(array $d, bool $criacao): array {
  $erros = [];
  $fltipo = $d['fltipo'] ?? 'conexao';
  
  // Nome é sempre obrigatório
  if (empty($d['dsintegracao'])) $erros[] = 'Campo obrigatório: dsintegracao';
  
  if ($fltipo === 'api') {
    // API Push: só precisa de nome + SQL + empresas
    if (empty($d['sql_origem'])) $erros[] = 'Campo obrigatório: sql_origem';
  } elseif ($fltipo === 'origem') {
    // Origem: precisa de conexão(oes) de referência + campo empresa + SQL
    if (empty($d['conexoes_origem']) || !is_array($d['conexoes_origem']) || count($d['conexoes_origem']) === 0) $erros[] = 'Campo obrigatório: Selecione ao menos uma conexão de referência';
    if (empty($d['campo_empresa'])) $erros[] = 'Campo obrigatório: campo_empresa (coluna que relaciona com back.empresa)';
    if (empty($d['sql_origem'])) $erros[] = 'Campo obrigatório: sql_origem';
  } else {
    // Conexão direta: todos os campos de BD
    $req = ['tipo_banco','host','porta','dbname','usuario','sql_origem'];
    foreach ($req as $campo) if (empty($d[$campo])) $erros[] = "Campo obrigatório: $campo";
    if ($criacao && empty($d['senha'])) $erros[] = 'Senha obrigatória na criação';
  }
  if (!empty($d['sql_origem'])) {
    if (!validarSQLOrigem($d['sql_origem'])) {
      $erros[] = 'SQL de origem inválido. Apenas instruções SELECT são aceitas (sem INSERT, UPDATE, DELETE, DROP, ALTER, etc)';
    } elseif ($fltipo === 'origem') {
      // Origem: validar que o SELECT retorna exatamente 2 colunas (campo_empresa + 1 valor)
      $colunasOrigem = extrairColunasSelect($d['sql_origem']);
      if (count($colunasOrigem) !== 2) {
        $erros[] = 'O SELECT de uma integração "origem" deve retornar exatamente 2 colunas (campo empresa + 1 coluna de valor). Foram detectadas ' . count($colunasOrigem) . ' colunas: ' . implode(', ', $colunasOrigem);
      } elseif (!empty($d['campo_empresa'])) {
        $campoEmpLower = strtolower(trim($d['campo_empresa']));
        if (!in_array($campoEmpLower, $colunasOrigem)) {
          $erros[] = 'O campo empresa "' . $d['campo_empresa'] . '" não foi encontrado nas colunas do SELECT: ' . implode(', ', $colunasOrigem);
        }
      }
    } else {
      // Validação de colunas obrigatórias apenas para conexao e api (NÃO para origem)
      $faltando = validarCamposObrigatoriosSQLOrigem($d['sql_origem']);
      if (!empty($faltando)) {
        $erros[] = 'Campos obrigatórios ausentes no SELECT: ' . implode(', ', $faltando);
      }
    }
  }
  // Agendamento simplificado: apenas "mínimo de horas" (0 = manual)
  $minHoras = (int)($d['min_horas_atualizacao'] ?? 0);
  if ($minHoras < 0) $erros[] = 'Mínimo de horas inválido';
  return $erros;
}

function bindComuns(PDOStatement $stmt, array $d, bool $comSenha) {
  $fltipo = $d['fltipo'] ?? 'conexao';
  $minHoras = (int)($d['min_horas_atualizacao'] ?? 0);
  if ($minHoras < 0) $minHoras = 0;
  
  // API push não tem agendamento (controlado pelo ERP)
  if ($fltipo === 'api') {
    $autoAtivo = false;
    $minHoras = 0;
  } else {
    $autoAtivo = ($minHoras > 0);
  }

  // Origem e API não têm campos de conexão próprios
  $semConexao = ($fltipo === 'api' || $fltipo === 'origem');

  $stmt->bindValue(':fltipo', $fltipo);
  $stmt->bindValue(':dsintegracao', $d['dsintegracao']);
  $stmt->bindValue(':flativo', !empty($d['flativo']), PDO::PARAM_BOOL);
  $stmt->bindValue(':tipo_banco', $semConexao ? null : ($d['tipo_banco'] ?? null));
  $stmt->bindValue(':host', $semConexao ? null : ($d['host'] ?? null));
  $stmt->bindValue(':porta', $semConexao ? null : (int)($d['porta'] ?? 5432), PDO::PARAM_INT);
  $stmt->bindValue(':schema_banco', $semConexao ? null : ($d['schema_banco'] ?? 'public'));
  $stmt->bindValue(':dbname', $semConexao ? null : ($d['dbname'] ?? null));
  $stmt->bindValue(':usuario', $semConexao ? null : ($d['usuario'] ?? null));
  if ($comSenha) $stmt->bindValue(':senha_encrypted', $semConexao ? null : ($d['senha'] ?? ''));
  $stmt->bindValue(':sql_origem', $d['sql_origem']);
  $stmt->bindValue(':schedule_cron', $autoAtivo ? (string)$minHoras : null);
  $stmt->bindValue(':schedule_ativo', $autoAtivo, PDO::PARAM_BOOL);
  $stmt->bindValue(':campo_empresa', $fltipo === 'origem' ? ($d['campo_empresa'] ?? null) : null);
}

function salvarEmpresas(int $id, array $empresas) {
  global $pdo;
  $pdo->prepare('DELETE FROM back.integracao_empresa WHERE cdintegracao = :i')->execute([':i' => $id]);
  if (empty($empresas)) return;
  $stmt = $pdo->prepare('INSERT INTO back.integracao_empresa (cdintegracao, cdempresa) VALUES (:i, :e)');
  foreach ($empresas as $emp) {
    if ($emp === '' || $emp === null) continue;
    $stmt->execute([':i' => $id, ':e' => (int)$emp]);
  }
}

function salvarHorarios(int $id, array $horarios) {
  global $pdo;
  $pdo->prepare('DELETE FROM back.integracao_horarios WHERE cdintegracao = :i')->execute([':i' => $id]);
  if (empty($horarios)) return;
  $stmt = $pdo->prepare('INSERT INTO back.integracao_horarios (cdintegracao, hora, minuto, flativo) VALUES (:i, :h, :m, :f)');
  foreach ($horarios as $horario) {
    if (!isset($horario['hora']) || !isset($horario['minuto'])) continue;
    $hora = (int)$horario['hora'];
    $minuto = (int)$horario['minuto'];
    if ($hora < 0 || $hora > 23 || $minuto < 0 || $minuto > 59) continue;
    $ativo = isset($horario['flativo']) ? (bool)$horario['flativo'] : true;
    $stmt->execute([':i' => $id, ':h' => $hora, ':m' => $minuto, ':f' => $ativo]);
  }
}

/**
 * Salva as conexões de referência para integrações do tipo "origem" (múltiplas)
 */
function salvarConexoesOrigem(int $id, array $conexoes) {
  global $pdo;
  $pdo->prepare('DELETE FROM back.integracao_conexao WHERE cdintegracao = :i')->execute([':i' => $id]);
  if (empty($conexoes)) return;
  $stmt = $pdo->prepare('INSERT INTO back.integracao_conexao (cdintegracao, cdintegracao_conexao) VALUES (:i, :c)');
  foreach ($conexoes as $cdconexao) {
    if ($cdconexao === '' || $cdconexao === null) continue;
    $stmt->execute([':i' => $id, ':c' => (int)$cdconexao]);
  }
}

function conectarTeste(array $cfg): array {
  $tipo = $cfg['tipo_banco'] ?? '';
  $porta = (int)($cfg['porta'] ?? 0);
  $host = $cfg['host'] ?? '';
  $dbname = $cfg['dbname'] ?? '';
  $usuario = $cfg['usuario'] ?? '';
  $senha = $cfg['senha'] ?? '';
  $schema = $cfg['schema_banco'] ?? '';

  if (!$tipo || !$host || !$dbname || !$usuario) return ['ok' => false, 'msg' => 'Campos de conexão incompletos'];

  try {
    if ($tipo === 'pgsql') {
      $dsn = "pgsql:host={$host};port=" . ($porta ?: 5432) . ";dbname={$dbname}" . ($schema ? ";options='--search_path=" . $schema . "'" : '');
    } elseif ($tipo === 'mysql') {
      $dsn = "mysql:host={$host};port=" . ($porta ?: 3306) . ";dbname={$dbname}";
    } elseif ($tipo === 'sqlsrv') {
      $dsn = "sqlsrv:Server={$host}," . ($porta ?: 1433) . ";Database={$dbname}";
    } else {
      return ['ok' => false, 'msg' => 'Tipo de banco não suportado'];
    }

    $tmp = new PDO($dsn, $usuario, $senha, [
      PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
      PDO::ATTR_TIMEOUT => 5,
    ]);
    unset($tmp);
    return ['ok' => true, 'msg' => 'Conexão bem-sucedida'];
  } catch (Exception $e) {
    return ['ok' => false, 'msg' => 'Falha: ' . $e->getMessage()];
  }
}

/**
 * Refresh das Materialized Views para consolidação de dados
 * Executado após sincronização bem-sucedida
 */
function refreshMaterializedViews(): array {
  global $pdo;
  
  $views = [
    'back.v_realizado_conta_mes_por_dia',
    'back.v_realizado_conta_mes',
    'back.v_orcado_conta_mes'
  ];
  
  $resultado = [
    'ok' => true,
    'views_atualizadas' => 0,
    'mensagens' => []
  ];
  
  foreach ($views as $view) {
    try {
      try {
        $sql = "REFRESH MATERIALIZED VIEW CONCURRENTLY {$view}";
        $pdo->exec($sql);
        $resultado['views_atualizadas']++;
        $resultado['mensagens'][] = "✓ {$view} consolidada";
      } catch (Exception $eConcurrent) {
        // Fallback: sem CONCURRENTLY (evita falhas por índice único ausente
        // ou erro de transaction block; pode bloquear leitores durante o refresh)
        $sql = "REFRESH MATERIALIZED VIEW {$view}";
        $pdo->exec($sql);
        $resultado['views_atualizadas']++;
        $resultado['mensagens'][] = "⚠ {$view} consolidada (fallback sem CONCURRENTLY)";
      }
    } catch (Exception $e) {
      $resultado['ok'] = false;
      $resultado['mensagens'][] = "✗ Erro ao consolidar {$view}: " . $e->getMessage();
    }
  }
  
  return $resultado;
}

$ajax = $_GET['ajax'] ?? '';
if ($ajax === '') { http_response_code(204); exit; }

switch ($ajax) {
  case 'criar': criarIntegracao(); break;
  case 'editar': editarIntegracao(); break;
  case 'obter': obterIntegracao(); break;
  case 'deletar': deletarIntegracao(); break;
  case 'testar': testarConexaoExistente(); break;
  case 'testar_temp': testarConexaoTemp(); break;
  case 'obter_integracoes': obterIntegracoes(); break;
  case 'sincronizar_agora': sincronizarAgora(); break;
  case 'sincronizar_origem': sincronizarOrigemAgora(); break;
  case 'verificar_dados': verificarDados(); break;
  case 'gerar_token': gerarToken(); break;
  default: json_out(['ok' => false, 'msg' => 'Ação inválida'], 400);
}

function criarIntegracao() {
  global $pdo;
  $data = json_decode(file_get_contents('php://input'), true) ?? [];
  $erros = validarCamposObrigatorios($data, true);
  if ($erros) json_out(['ok' => false, 'msg' => implode(' | ', $erros)], 400);

  try {
    $sql = "INSERT INTO back.integracao (fltipo, dsintegracao, flativo, tipo_banco, host, porta, schema_banco, dbname, usuario, senha_encrypted, sql_origem, schedule_cron, schedule_ativo, campo_empresa)
            VALUES (:fltipo, :dsintegracao, :flativo, :tipo_banco, :host, :porta, :schema_banco, :dbname, :usuario, :senha_encrypted, :sql_origem, :schedule_cron, :schedule_ativo, :campo_empresa)
            RETURNING cdintegracao";
    $stmt = $pdo->prepare($sql);
    bindComuns($stmt, $data, true);
    $stmt->execute();
    $id = (int)$stmt->fetchColumn();

    // Origem herda empresas/horários das integrações de referência — não salva próprias
    if (($data['fltipo'] ?? '') !== 'origem') {
      salvarEmpresas($id, $data['empresas'] ?? []);
      salvarHorarios($id, $data['horarios'] ?? []);
    }
    
    // Salvar conexões de referência para tipo "origem"
    if (($data['fltipo'] ?? '') === 'origem' && !empty($data['conexoes_origem'])) {
      salvarConexoesOrigem($id, $data['conexoes_origem']);
    }
    
    json_out(['ok' => true, 'msg' => 'Integração criada', 'cdintegracao' => $id]);
  } catch (Exception $e) {
    json_out(['ok' => false, 'msg' => 'Erro ao criar: ' . $e->getMessage()], 500);
  }
}

function editarIntegracao() {
  global $pdo;
  $id = (int)($_GET['cdintegracao'] ?? 0);
  if ($id <= 0) json_out(['ok' => false, 'msg' => 'ID inválido'], 400);
  $data = json_decode(file_get_contents('php://input'), true) ?? [];
  $erros = validarCamposObrigatorios($data, false);
  if ($erros) json_out(['ok' => false, 'msg' => implode(' | ', $erros)], 400);

  try {
    $campos = "fltipo = :fltipo, dsintegracao = :dsintegracao, flativo = :flativo, tipo_banco = :tipo_banco, host = :host, porta = :porta, schema_banco = :schema_banco, dbname = :dbname, usuario = :usuario, sql_origem = :sql_origem, schedule_cron = :schedule_cron, schedule_ativo = :schedule_ativo, campo_empresa = :campo_empresa";
    $incluiSenha = !empty($data['senha']);
    if ($incluiSenha) $campos .= ", senha_encrypted = :senha_encrypted";

    $sql = "UPDATE back.integracao SET $campos WHERE cdintegracao = :id";
    $stmt = $pdo->prepare($sql);
    bindComuns($stmt, $data, $incluiSenha);
    $stmt->bindValue(':id', $id, PDO::PARAM_INT);
    $stmt->execute();

    // Origem herda empresas/horários das integrações de referência — não salva próprias
    if (($data['fltipo'] ?? '') !== 'origem') {
      salvarEmpresas($id, $data['empresas'] ?? []);
      salvarHorarios($id, $data['horarios'] ?? []);
    } else {
      // Limpa empresas/horários próprias se mudou para origem
      salvarEmpresas($id, []);
      salvarHorarios($id, []);
    }
    
    // Salvar/atualizar conexões de referência para tipo "origem"
    if (($data['fltipo'] ?? '') === 'origem') {
      salvarConexoesOrigem($id, $data['conexoes_origem'] ?? []);
    } else {
      // Se mudou de tipo, limpar as referências de conexão
      salvarConexoesOrigem($id, []);
    }
    
    json_out(['ok' => true, 'msg' => 'Integração atualizada']);
  } catch (Exception $e) {
    json_out(['ok' => false, 'msg' => 'Erro ao atualizar: ' . $e->getMessage()], 500);
  }
}

function obterIntegracao() {
  global $pdo;
  $id = (int)($_GET['cdintegracao'] ?? 0);
  if ($id <= 0) json_out(['ok' => false, 'msg' => 'ID inválido'], 400);

  try {
    $stmt = $pdo->prepare("SELECT cdintegracao, dsintegracao, fltipo, flativo, tipo_banco, host, porta, schema_banco, dbname, usuario, sql_origem, schedule_cron, schedule_ativo, ultima_sincronizacao, campo_empresa, CASE WHEN api_token_hash IS NOT NULL AND api_token_hash != '' THEN true ELSE false END AS has_token FROM back.integracao WHERE cdintegracao = :id");
    $stmt->execute([':id' => $id]);
    $data = $stmt->fetch(PDO::FETCH_ASSOC);
    
    if (!$data) {
      error_log("Integração não encontrada: ID=$id");
      json_out(['ok' => false, 'msg' => 'Integração não encontrada'], 404);
    }

    $stmtEmp = $pdo->prepare('SELECT cdempresa FROM back.integracao_empresa WHERE cdintegracao = :i');
    $stmtEmp->execute([':i' => $id]);
    $data['empresas'] = $stmtEmp->fetchAll(PDO::FETCH_COLUMN);
    
    // Carregar horários agendados
    $stmtHor = $pdo->prepare('SELECT cdhorario, hora, minuto, flativo FROM back.integracao_horarios WHERE cdintegracao = :i ORDER BY hora, minuto');
    $stmtHor->execute([':i' => $id]);
    $data['horarios'] = $stmtHor->fetchAll(PDO::FETCH_ASSOC);
    
    // Carregar conexões de referência (para tipo "origem") - múltiplas
    $stmtConex = $pdo->prepare('SELECT cdintegracao_conexao FROM back.integracao_conexao WHERE cdintegracao = :i');
    $stmtConex->execute([':i' => $id]);
    $data['conexoes_origem'] = $stmtConex->fetchAll(PDO::FETCH_COLUMN);
    
    $data['schedule_ativo'] = ($data['schedule_ativo'] === true || $data['schedule_ativo'] === 't' || $data['schedule_ativo'] === 1 || $data['schedule_ativo'] === '1');
    $data['has_token'] = ($data['has_token'] === true || $data['has_token'] === 't' || $data['has_token'] === 1 || $data['has_token'] === '1');

    // Exposição do campo simplificado para o front
    $minHoras = 0;
    if ($data['schedule_ativo']) {
      $minHoras = (int)($data['schedule_cron'] ?? 0);
      if ($minHoras <= 0) $minHoras = 24;
    }
    $data['min_horas_atualizacao'] = $minHoras;

    error_log("Integração carregada: ID=$id, Data=" . json_encode($data));
    json_out(['ok' => true, 'data' => $data]);
  } catch (Exception $e) {
    error_log("Erro ao obter integração ID=$id: " . $e->getMessage());
    json_out(['ok' => false, 'msg' => 'Erro: ' . $e->getMessage()], 500);
  }
}

function obterIntegracoes() {
  global $pdo;
  $res = $pdo->query("SELECT cdintegracao, dsintegracao FROM back.integracao WHERE flativo = true ORDER BY dsintegracao")->fetchAll(PDO::FETCH_ASSOC);
  json_out(['ok' => true, 'data' => $res]);
}

function deletarIntegracao() {
  global $pdo;
  $id = (int)($_GET['cdintegracao'] ?? 0);
  if ($id <= 0) json_out(['ok' => false, 'msg' => 'ID inválido'], 400);
  try {
    $pdo->beginTransaction();
    $pdo->prepare('DELETE FROM back.origem_dados WHERE cdintegracao = :i')->execute([':i' => $id]);
    $pdo->prepare('DELETE FROM back.integracao_conexao WHERE cdintegracao = :i')->execute([':i' => $id]);
    $pdo->prepare('DELETE FROM back.integracao_horarios WHERE cdintegracao = :i')->execute([':i' => $id]);
    $pdo->prepare('DELETE FROM back.integracao_empresa WHERE cdintegracao = :i')->execute([':i' => $id]);
    $pdo->prepare('DELETE FROM back.integracao WHERE cdintegracao = :i')->execute([':i' => $id]);
    $pdo->commit();
    json_out(['ok' => true, 'msg' => 'Integração removida']);
  } catch (Exception $e) {
    if ($pdo->inTransaction()) $pdo->rollBack();
    json_out(['ok' => false, 'msg' => 'Erro ao remover: ' . $e->getMessage()], 500);
  }
}

function testarConexaoExistente() {
  global $pdo;
  $id = (int)($_GET['cdintegracao'] ?? 0);
  if ($id <= 0) json_out(['ok' => false, 'msg' => 'ID inválido'], 400);

  $stmt = $pdo->prepare('SELECT tipo_banco, host, porta, dbname, usuario, senha_encrypted, schema_banco FROM back.integracao WHERE cdintegracao = :id');
  $stmt->execute([':id' => $id]);
  $cfg = $stmt->fetch(PDO::FETCH_ASSOC);
  if (!$cfg) json_out(['ok' => false, 'msg' => 'Integração não encontrada'], 404);
  $cfg['senha'] = $cfg['senha_encrypted'] ?? '';
  $res = conectarTeste($cfg);
  json_out($res, $res['ok'] ? 200 : 400);
}

function testarConexaoTemp() {
  $data = json_decode(file_get_contents('php://input'), true) ?? [];
  $res = conectarTeste($data);
  json_out($res, $res['ok'] ? 200 : 400);
}

function sincronizarAgora() {
  global $pdo;
  $id = (int)($_GET['cdintegracao'] ?? 0);
  if ($id <= 0) json_out(['ok' => false, 'msg' => 'ID inválido'], 400);

  try {
    // Buscar configuração da integração
    $stmt = $pdo->prepare('SELECT tipo_banco, host, porta, dbname, usuario, senha_encrypted, schema_banco, sql_origem FROM back.integracao WHERE cdintegracao = :id AND flativo = true');
    $stmt->execute([':id' => $id]);
    $cfg = $stmt->fetch(PDO::FETCH_ASSOC);
    if (!$cfg) json_out(['ok' => false, 'msg' => 'Integração não encontrada ou inativa'], 404);
    
    // Buscar empresas vinculadas (serão validadas contra cdempresa da origem)
    $stmtEmp = $pdo->prepare('SELECT cdempresa FROM back.integracao_empresa WHERE cdintegracao = :id');
    $stmtEmp->execute([':id' => $id]);
    $empresasPermitidas = $stmtEmp->fetchAll(PDO::FETCH_COLUMN);
    if (empty($empresasPermitidas)) json_out(['ok' => false, 'msg' => 'Nenhuma empresa vinculada'], 400);

    // Conectar no banco de origem
    $tipo = $cfg['tipo_banco'];
    $porta = (int)$cfg['porta'];
    $host = $cfg['host'];
    $dbname = $cfg['dbname'];
    $usuario = $cfg['usuario'];
    $senha = $cfg['senha_encrypted'] ?? '';
    $schema = $cfg['schema_banco'] ?? '';
    
    if ($tipo === 'pgsql') {
      $dsn = "pgsql:host={$host};port=" . ($porta ?: 5432) . ";dbname={$dbname}" . ($schema ? ";options='--search_path={$schema}'" : '');
    } elseif ($tipo === 'mysql') {
      $dsn = "mysql:host={$host};port=" . ($porta ?: 3306) . ";dbname={$dbname}";
    } elseif ($tipo === 'sqlsrv') {
      $dsn = "sqlsrv:Server={$host}," . ($porta ?: 1433) . ";Database={$dbname}";
    } else {
      json_out(['ok' => false, 'msg' => 'Tipo de banco não suportado'], 400);
    }

    $pdoOrigem = new PDO($dsn, $usuario, $senha, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
    
    // Construir SQL final - APENAS SELECT permitido (validado em validarSQLOrigem)
    $sqlOrigem = $cfg['sql_origem'];
    
    // Revalidar segurança antes de executar (defesa em profundidade - nada pode ser alterado na origem)
    if (!preg_match('/^\s*(SELECT|WITH)\b/i', trim($sqlOrigem))) {
      json_out(['ok' => false, 'msg' => 'Erro: SQL inválido'], 400);
    }
    
    $stmtOrigem = $pdoOrigem->query($sqlOrigem);
    $registros = $stmtOrigem->fetchAll(PDO::FETCH_ASSOC);
    
    if (empty($registros)) json_out(['ok' => true, 'msg' => 'Nenhum registro encontrado na origem', 'total' => 0]);
    
    $inseridos = 0;
    $erros = [];
    $ignorados = 0;
    
    // Gerar hash único para idlote (para controle interno desta sincronização)
    $idlote = md5($id . '_' . time() . '_' . count($registros));
    
    // Preparar insert no destino
    $sqlInsert = "INSERT INTO back.realizado (cdempresa, cdexercicio, cdcentrocusto, cdplanocontabil, mesref, anoref, tpmovimento, valor, dscomplemento, dtmovimento, cdintegracao, florigem, json_origem, flcancelado, dtcriacao, cdpessoa, idlote)
                  VALUES (:cdempresa, :cdexercicio, :cdcentrocusto, :cdplanocontabil, :mesref, :anoref, :tpmovimento, :valor, :dscomplemento, :dtmovimento, :cdintegracao, :florigem, :json_origem, :flcancelado, NOW(), :cdpessoa, :idlote)";
    $stmtInsert = $pdo->prepare($sqlInsert);
    
    foreach ($registros as $reg) {
      try {
        // Validar cdempresa da origem
        $cdempresa = $reg['cdempresa'] ?? null;
        if (!$cdempresa) {
          $erros[] = "Registro sem cdempresa: " . json_encode($reg);
          continue;
        }
        
        // Validar se empresa está vinculada à integração
        if (!in_array($cdempresa, $empresasPermitidas)) {
          $ignorados++;
          continue;
        }
        
        // Extrair ano e mês da data de movimento
        $dtmovimento = $reg['dtmovimento'] ?? null;
        if (!$dtmovimento) {
          $erros[] = "Registro sem dtmovimento para empresa {$cdempresa}";
          continue;
        }
        
        $dt = new DateTime($dtmovimento);
        $ano = (int)$dt->format('Y');
        $mes = (int)$dt->format('m');
        
        // Buscar exercício ativo para essa empresa e ano (baseado em dtmovimento)
        $stmtExerc = $pdo->prepare('SELECT cdexercicio FROM back.exercicio WHERE cdempresa = :emp AND anoref = :ano AND flativo = 0 LIMIT 1');
        $stmtExerc->execute([':emp' => $cdempresa, ':ano' => $ano]);
        $cdexercicio = $stmtExerc->fetchColumn();
        
        if (!$cdexercicio) {
          $erros[] = "Exercício não encontrado para empresa {$cdempresa}, ano {$ano} (origem: dtmovimento={$dtmovimento})";
          continue;
        }
          
        // Validar tipo de movimento
        $tpmovimento = strtoupper(trim($reg['tpmovimento'] ?? ''));
        if (!in_array($tpmovimento, ['D', 'C'])) {
          $erros[] = "Tipo de movimento inválido para empresa {$cdempresa}: '{$tpmovimento}' (deve ser D ou C)";
          continue;
        }
        
        // Inserir registro
        $stmtInsert->execute([
          ':cdempresa' => $cdempresa,
          ':cdexercicio' => $cdexercicio,
          ':cdcentrocusto' => $reg['cdcentrocusto'] ?? null,
          ':cdplanocontabil' => $reg['cdplanocontabil'] ?? null,
          ':mesref' => $mes,
          ':anoref' => $ano,
          ':tpmovimento' => $tpmovimento,
          ':valor' => $reg['valor'] ?? 0,
          ':dscomplemento' => $reg['dscomplemento'] ?? '',
          ':dtmovimento' => $dtmovimento,
          ':cdintegracao' => $id,
          ':florigem' => 2,
          ':json_origem' => json_encode($reg, JSON_UNESCAPED_UNICODE),
          ':flcancelado' => 0,
          ':cdpessoa' => 0,
          ':idlote' => $idlote
        ]);
        
        $inseridos++;
      } catch (Exception $e) {
        $erros[] = "Erro ao inserir registro empresa {$cdempresa}: " . $e->getMessage();
      }
    }
    
    // ✨ ETAPA 2: Consolidação dos dados (Refresh das Materialized Views)
    // Só considerar a execução finalizada quando o refresh retornar OK.
    $consolidacao = refreshMaterializedViews();

    // Atualizar data de última sincronização apenas quando consolidou com sucesso
    if (!empty($consolidacao['ok'])) {
      $pdo->prepare('UPDATE back.integracao SET ultima_sincronizacao = NOW() WHERE cdintegracao = :id')->execute([':id' => $id]);
    }
    
    $msg = "Sincronização concluída: {$inseridos} registros inseridos";
    if ($ignorados > 0) $msg .= ", {$ignorados} ignorados (empresa não vinculada)";
    if (!empty($erros)) {
      $msg .= ". Erros: " . implode('; ', array_slice($erros, 0, 5));
      if (count($erros) > 5) $msg .= " (+" . (count($erros) - 5) . " erros)";
    }
    
    // Adicionar status da consolidação
    $msg .= "\n\n📊 Consolidação de Dados:\n";
    $msg .= implode("\n", $consolidacao['mensagens']);
    
    // ✨ ETAPA 3: Cascading — sincronizar origens vinculadas a esta integração
    $cascadeResults = [];
    $origensVinculadas = buscarOrigensVinculadas($id);
    if (!empty($origensVinculadas)) {
      $msg .= "\n\n🔗 Origens vinculadas (" . count($origensVinculadas) . "):";
      foreach ($origensVinculadas as $origem) {
        $orId = (int)$origem['cdintegracao'];
        $orNome = $origem['dsintegracao'];
        try {
          $resOrigem = executarSincronizacaoOrigem($orId);
          $cascadeResults[] = [
            'cdintegracao' => $orId,
            'dsintegracao' => $orNome,
            'ok' => !empty($resOrigem['ok']),
            'total' => $resOrigem['total'] ?? 0,
            'ignorados' => $resOrigem['ignorados'] ?? 0,
            'erros' => $resOrigem['erros'] ?? 0,
            'msg' => $resOrigem['msg'] ?? ''
          ];
          if (!empty($resOrigem['ok'])) {
            $msg .= "\n✓ {$orNome}: {$resOrigem['total']} registros";
            if (($resOrigem['ignorados'] ?? 0) > 0) $msg .= " ({$resOrigem['ignorados']} ignorados)";
          } else {
            $msg .= "\n✗ {$orNome}: " . ($resOrigem['msg'] ?? 'Erro');
          }
        } catch (Exception $oe) {
          $cascadeResults[] = [
            'cdintegracao' => $orId,
            'dsintegracao' => $orNome,
            'ok' => false,
            'msg' => $oe->getMessage()
          ];
          $msg .= "\n✗ {$orNome}: " . $oe->getMessage();
        }
      }
    }
    
    json_out([
      'ok' => (bool)$consolidacao['ok'], 
      'msg' => $msg, 
      'total' => $inseridos, 
      'ignorados' => $ignorados, 
      'erros' => count($erros),
      'consolidacao' => $consolidacao,
      'cascade_origens' => $cascadeResults
    ]);
    
  } catch (Exception $e) {
    json_out(['ok' => false, 'msg' => 'Erro na sincronização: ' . $e->getMessage()], 500);
  }
}

function verificarDados() {
  global $pdo;
  $id = (int)($_GET['cdintegracao'] ?? 0);
  if ($id <= 0) json_out(['ok' => false, 'msg' => 'ID inválido'], 400);

  try {
    // Buscar configuração da integração
    $stmt = $pdo->prepare('SELECT fltipo, tipo_banco, host, porta, dbname, usuario, senha_encrypted, schema_banco, sql_origem FROM back.integracao WHERE cdintegracao = :id');
    $stmt->execute([':id' => $id]);
    $cfg = $stmt->fetch(PDO::FETCH_ASSOC);
    if (!$cfg) json_out(['ok' => false, 'msg' => 'Integração não encontrada'], 404);

    // Se tipo "origem", buscar primeira conexão DB (não API) da integração referenciada
    if (($cfg['fltipo'] ?? '') === 'origem') {
      $conexoes = obterConexoesOrigem($id);
      $cfg = null;
      foreach ($conexoes as $c) {
        if (!empty($c['tipo_banco'])) { $cfg = $c; break; }
      }
      if (!$cfg) json_out(['ok' => false, 'msg' => 'Nenhuma conexão de referência com banco de dados encontrada (conexões API não possuem banco próprio)'], 404);
    }

    // Conectar no banco de origem
    $tipo = $cfg['tipo_banco'];
    $porta = (int)$cfg['porta'];
    $host = $cfg['host'];
    $dbname = $cfg['dbname'];
    $usuario = $cfg['usuario'];
    $senha = $cfg['senha_encrypted'] ?? '';
    $schema = $cfg['schema_banco'] ?? '';
    
    if ($tipo === 'pgsql') {
      $dsn = "pgsql:host={$host};port=" . ($porta ?: 5432) . ";dbname={$dbname}" . ($schema ? ";options='--search_path={$schema}'" : '');
    } elseif ($tipo === 'mysql') {
      $dsn = "mysql:host={$host};port=" . ($porta ?: 3306) . ";dbname={$dbname}";
    } elseif ($tipo === 'sqlsrv') {
      $dsn = "sqlsrv:Server={$host}," . ($porta ?: 1433) . ";Database={$dbname}";
    } else {
      json_out(['ok' => false, 'msg' => 'Tipo de banco desconhecido'], 400);
    }
    
    try {
      $pdoOrigin = new PDO($dsn, $usuario, $senha);
      $pdoOrigin->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);
    } catch (Exception $e) {
      json_out(['ok' => false, 'msg' => 'Erro ao conectar na origem: ' . $e->getMessage()], 500);
    }
    
    // Buscar última linha do SQL de origem
    $data = json_decode(file_get_contents('php://input'), true) ?? [];
    $sql = trim($data['sql_origem'] ?? '');
    
    if (!$sql || !validarSQLOrigem($sql)) {
      json_out(['ok' => false, 'msg' => 'SQL de origem inválido'], 400);
    }
    
    // Revalidar segurança antes de executar
    if (!preg_match('/^\s*(SELECT|WITH)\b/i', trim($sql))) {
      json_out(['ok' => false, 'msg' => 'Erro: SQL inválido'], 400);
    }
    
    $sqlFinal = "$sql ORDER BY 1 DESC LIMIT 1";
    
    $stmtData = $pdoOrigin->prepare($sqlFinal);
    $stmtData->execute();
    $registro = $stmtData->fetch(PDO::FETCH_ASSOC);
    
    if ($registro === false) {
      json_out(['ok' => true, 'msg' => 'Nenhum registro encontrado', 'data' => []]);
    }
    
    // Para tipo "origem": buscar integração original para validar 2 colunas
    $stmtTipo = $pdo->prepare('SELECT fltipo FROM back.integracao WHERE cdintegracao = :id');
    $stmtTipo->execute([':id' => $id]);
    $tipoInteg = ($stmtTipo->fetchColumn()) ?: '';
    
    if ($tipoInteg === 'origem' && $registro !== false) {
      $nColunas = count(array_keys($registro));
      if ($nColunas !== 2) {
        json_out(['ok' => false, 'msg' => "O SELECT deve retornar exatamente 2 colunas (campo empresa + 1 coluna de valor), mas retornou {$nColunas} colunas: " . implode(', ', array_keys($registro))], 400);
      }
    }
    
    json_out(['ok' => true, 'msg' => 'Último registro carregado', 'data' => $registro]);
    
  } catch (Exception $e) {
    json_out(['ok' => false, 'msg' => 'Erro ao buscar dados: ' . $e->getMessage()], 500);
  }
}

/**
 * Obtém os dados de conexão de TODAS as integrações referenciadas por uma integração "origem"
 * Delegado para lib compartilhada
 */
function obterConexoesOrigem(int $cdintegracao): array {
  global $pdo;
  return origemObterConexoes($cdintegracao, $pdo);
}

/**
 * Obtém a PRIMEIRA conexão ativa (compatível com verificarDados que precisa de uma só)
 */
function obterConexaoOrigem(int $cdintegracao): ?array {
  $conexoes = obterConexoesOrigem($cdintegracao);
  return !empty($conexoes) ? $conexoes[0] : null;
}

/**
 * Busca integrações do tipo "origem" que referenciam uma integração conexão/api
 * Delegado para lib compartilhada
 */
function buscarOrigensVinculadas(int $cdintegracao): array {
  global $pdo;
  return origemBuscarVinculadas($cdintegracao, $pdo);
}

/**
 * Core da sincronização de integrações do tipo "origem"
 * Delegado para lib compartilhada
 */
function executarSincronizacaoOrigem(int $id): array {
  global $pdo;
  return origemExecutarSincronizacao($id, $pdo);
}

/**
 * Endpoint AJAX: Sincronização de integrações do tipo "origem"
 */
function sincronizarOrigemAgora() {
  $id = (int)($_GET['cdintegracao'] ?? 0);
  if ($id <= 0) json_out(['ok' => false, 'msg' => 'ID inválido'], 400);

  $res = executarSincronizacaoOrigem($id);
  json_out($res, !empty($res['ok']) ? 200 : 400);
}

function gerarToken() {
  global $pdo;
  $id = (int)($_GET['cdintegracao'] ?? 0);
  if ($id <= 0) json_out(['ok' => false, 'msg' => 'ID inválido'], 400);
  
  try {
    // Verificar se integração existe
    $stmt = $pdo->prepare('SELECT cdintegracao, fltipo FROM back.integracao WHERE cdintegracao = :id');
    $stmt->execute([':id' => $id]);
    $integ = $stmt->fetch(PDO::FETCH_ASSOC);
    if (!$integ) json_out(['ok' => false, 'msg' => 'Integração não encontrada'], 404);
    
    // Gerar token seguro: 64 caracteres hex (256 bits)
    $tokenPlain = bin2hex(random_bytes(32));
    
    // Armazenar hash bcrypt (token nunca é armazenado em texto plano)
    $tokenHash = password_hash($tokenPlain, PASSWORD_BCRYPT);
    
    $stmtUpdate = $pdo->prepare('UPDATE back.integracao SET api_token_hash = :hash WHERE cdintegracao = :id');
    $stmtUpdate->execute([':hash' => $tokenHash, ':id' => $id]);
    
    // Retornar token em texto plano UMA ÚNICA VEZ
    json_out([
      'ok' => true, 
      'msg' => 'Token gerado com sucesso',
      'token' => $tokenPlain,
      'cdintegracao' => $id
    ]);
  } catch (Exception $e) {
    json_out(['ok' => false, 'msg' => 'Erro ao gerar token: ' . $e->getMessage()], 500);
  }
}
?>
