<?php
/**
 * Sincronizador ERP → HELM (Genérico)
 * Arquitetura: cdIntegracao (único) → config direto de back.integracao
 * 
 * Estratégia:
 * - DELETE: registros dos exercícios abertos OU período específico (ano/mês)
 * - INSERT: todos os dados mapeados da sql_origem
 */
class SincronizadorERP {
    private $pdoHelm;
    private $cdIntegracao;
    private $config;
    private $anoRef;   // opcional: restringe sincronização a um ano específico
    private $mesRef;   // opcional: restringe sincronização a um mês específico
    
    public function __construct($pdoHelm, $cdIntegracao, $anoRef = null, $mesRef = null) {
        $this->pdoHelm = $pdoHelm;
        $this->cdIntegracao = (int)$cdIntegracao;
        $this->anoRef = $anoRef ? (int)$anoRef : null;
        $this->mesRef = $mesRef ? (int)$mesRef : null;
        
        // Carregar configuração da integração
        $this->config = $this->carregarConfiguracao();
    }
    
    /**
     * Executar sincronização completa
     * @return array com estatísticas [ok, qtd_origem, qtd_deletados, qtd_inseridos, qtd_erros, tempo]
     */
    public function sincronizar() {
        $inicio = microtime(true);
        
        try {
            // 1. Carregar SQL da origem da config
            if (empty($this->config['sql_origem'])) {
                throw new Exception('SQL da origem não configurada');
            }
            
            // 2. Validar SQL (apenas SELECT/WITH)
            if (!$this->validarSQLLeituraApenas($this->config['sql_origem'])) {
                throw new Exception('SQL da origem deve ser apenas SELECT ou WITH (sem INSERT/UPDATE/DELETE/etc)');
            }
            
            // 3. Conectar à origem
            $pdoOrigem = $this->conectarOrigem();
            
            // 4. Buscar exercícios abertos
            $exercicios = $this->buscarExerciciosAbertos();
            if (empty($exercicios)) {
                throw new Exception('Nenhum exercício aberto encontrado');
            }
            
            // 5. Buscar dados da origem
            $registrosOrigem = $this->buscarDadosOrigem($pdoOrigem);
            $qtdOrigem = count($registrosOrigem);
            
            if ($qtdOrigem === 0) {
                return [
                    'ok' => true,
                    'qtd_origem' => 0,
                    'qtd_deletados' => 0,
                    'qtd_inseridos' => 0,
                    'qtd_erros' => 0,
                    'tempo' => round(microtime(true) - $inicio, 2)
                ];
            }
            
            // 6. Iniciar transação
            $this->pdoHelm->beginTransaction();
            
            // 7. DELETE: por período específico OU exercícios abertos
            if ($this->anoRef !== null && $this->mesRef !== null) {
                $qtdDeletados = $this->deletarPeriodo($this->anoRef, $this->mesRef);
            } else {
                $qtdDeletados = $this->deletarExerciciosAbertos($exercicios);
            }
            
            // 8. Mapear + Validar + Inserir
            $resultado = $this->processarRegistros($registrosOrigem);
            
            // 9. Commit
            $this->pdoHelm->commit();
            
            return [
                'ok' => true,
                'qtd_origem' => $qtdOrigem,
                'qtd_deletados' => $qtdDeletados,
                'qtd_inseridos' => $resultado['inseridos'],
                'qtd_erros' => $resultado['erros'],
                'tempo' => round(microtime(true) - $inicio, 2)
            ];
            
        } catch (Exception $e) {
            if ($this->pdoHelm->inTransaction()) {
                $this->pdoHelm->rollBack();
            }
            throw $e;
        }
    }
    
    /**
     * Carregar configuração da integração do back.integracao
     */
    private function carregarConfiguracao() {
        $stmt = $this->pdoHelm->prepare("
            SELECT 
                cdintegracao, 
                dsintegracao, 
                tipo_banco, 
                host, 
                porta, 
                dbname, 
                usuario, 
                senha_encrypted, 
                schema_banco, 
                sql_origem
            FROM back.integracao
            WHERE cdintegracao = :id AND flativo = true
        ");
        $stmt->execute([':id' => $this->cdIntegracao]);
        $config = $stmt->fetch(PDO::FETCH_ASSOC);
        
        if (!$config) {
            throw new Exception('Integração não encontrada ou inativa');
        }
        
        return $config;
    }
    
    /**
     * Conectar à origem (ERP)
     */
    private function conectarOrigem() {
        try {
            $senha = base64_decode($this->config['senha_encrypted']);
            
            // Determinar DSN conforme tipo de banco
            switch ($this->config['tipo_banco']) {
                case 'pgsql':
                    $dsn = sprintf(
                        "pgsql:host=%s;port=%d;dbname=%s",
                        $this->config['host'],
                        (int)$this->config['porta'],
                        $this->config['dbname']
                    );
                    break;
                case 'mysql':
                    $dsn = sprintf(
                        "mysql:host=%s;port=%d;dbname=%s",
                        $this->config['host'],
                        (int)$this->config['porta'],
                        $this->config['dbname']
                    );
                    break;
                case 'sqlsrv':
                    $dsn = sprintf(
                        "sqlsrv:Server=%s,%d;Database=%s",
                        $this->config['host'],
                        (int)$this->config['porta'],
                        $this->config['dbname']
                    );
                    break;
                default:
                    throw new Exception('Tipo de banco não suportado: ' . $this->config['tipo_banco']);
            }
            
            $pdo = new PDO($dsn, $this->config['usuario'], $senha, [
                PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
                PDO::ATTR_TIMEOUT => 300 // 5 min
            ]);
            
            // Definir schema para PostgreSQL
            if ($this->config['tipo_banco'] === 'pgsql' && !empty($this->config['schema_banco'])) {
                $pdo->exec("SET search_path TO {$this->config['schema_banco']}, public");
            }
            
            return $pdo;
            
        } catch (PDOException $e) {
            throw new Exception('Erro ao conectar origem: ' . $e->getMessage());
        }
    }
    
    /**
     * Validar que SQL contém apenas SELECT/WITH (leitura)
     */
    private function validarSQLLeituraApenas($sql) {
        // Remover comentários e espaços
        $sql = preg_replace('/--[^\n]*/', '', $sql);
        $sql = preg_replace('/\/\*.*?\*\//s', '', $sql);
        $sql = trim($sql);
        
        // Deve começar com SELECT ou WITH
        if (!preg_match('/^(WITH|SELECT)\s/i', $sql)) {
            return false;
        }
        
        // Comandos perigosos não permitidos
        $dangerous = [
            'INSERT', 'UPDATE', 'DELETE', 'MERGE',
            'TRUNCATE', 'DROP', 'ALTER', 'CREATE',
            'GRANT', 'REVOKE', 'COMMENT',
            'EXEC', 'EXECUTE', 'CALL'
        ];
        
        foreach ($dangerous as $cmd) {
            if (preg_match('/\b' . $cmd . '\b/i', $sql)) {
                return false;
            }
        }
        
        return true;
    }
    
    /**
     * Buscar dados da origem executando sql_origem
     */
    private function buscarDadosOrigem($pdoOrigem) {
        try {
            $stmt = $pdoOrigem->query($this->config['sql_origem']);
            return $stmt->fetchAll(PDO::FETCH_ASSOC);
        } catch (PDOException $e) {
            throw new Exception('Erro ao buscar dados da origem: ' . $e->getMessage());
        }
    }
    
    /**
     * Buscar exercícios abertos
     */
    private function buscarExerciciosAbertos() {
        $stmt = $this->pdoHelm->query("
            SELECT cdexercicio 
            FROM back.exercicio 
            WHERE flativo = false
        ");
        return $stmt->fetchAll(PDO::FETCH_COLUMN);
    }
    
    /**
     * Deletar registros de um período específico (ano/mês)
     */
    private function deletarPeriodo($ano, $mes) {
        $stmt = $this->pdoHelm->prepare("
            DELETE FROM back.realizado
            WHERE cdintegracao = :id
              AND anoref = :ano
              AND mesref = :mes
        ");
        $stmt->execute([
            ':id' => $this->cdIntegracao,
            ':ano' => (int)$ano,
            ':mes' => (int)$mes
        ]);
        
        return $stmt->rowCount();
    }
    
    /**
     * Deletar registros dos exercícios abertos
     */
    private function deletarExerciciosAbertos($exercicios) {
        $ids = implode(',', array_map('intval', $exercicios));
        
        $stmt = $this->pdoHelm->prepare("
            DELETE FROM back.realizado 
            WHERE cdintegracao = :id
              AND cdexercicio IN ($ids)
        ");
        $stmt->execute([':id' => $this->cdIntegracao]);
        
        return $stmt->rowCount();
    }
    
    /**
     * Processar registros: mapear + validar + inserir
     */
    private function processarRegistros($registros) {
        require_once __DIR__ . '/MapeadorDados.php';
        
        $mapeador = new MapeadorDados($this->pdoHelm, $this->cdIntegracao);
        $validos = [];
        $erros = [];
        
        // Mapear, filtrar por período e validar
        foreach ($registros as $origem) {
            try {
                $mapeado = $mapeador->mapear($origem);
                
                // Se sincronização limitada a período específico, descartar outros
                if ($this->anoRef !== null && (!isset($mapeado['anoref']) || (int)$mapeado['anoref'] !== $this->anoRef)) {
                    continue;
                }
                if ($this->mesRef !== null && (!isset($mapeado['mesref']) || (int)$mapeado['mesref'] !== $this->mesRef)) {
                    continue;
                }
                
                // Validar
                $erroValidacao = $this->validarRealizado($mapeado);
                if ($erroValidacao) {
                    $erros[] = [
                        'id_origem' => $mapeado['id_origem'] ?? 'desconhecido',
                        'erro' => $erroValidacao
                    ];
                    continue;
                }
                
                $validos[] = $mapeado;
                
            } catch (Exception $e) {
                $erros[] = [
                    'id_origem' => $origem['id_origem'] ?? 'desconhecido',
                    'erro' => $e->getMessage()
                ];
            }
        }
        
        // Inserir em lotes
        $inseridos = 0;
        foreach (array_chunk($validos, 1000) as $lote) {
            $inseridos += $this->inserirLote($lote);
        }
        
        return [
            'inseridos' => $inseridos,
            'erros' => count($erros)
        ];
    }
    
    /**
     * Validar registro mapeado
     */
    private function validarRealizado($dados) {
        if (empty($dados['cdempresa'])) return 'cdempresa obrigatório';
        if (empty($dados['cdexercicio'])) return 'cdexercicio obrigatório';
        if (empty($dados['cdcentrocusto'])) return 'cdcentrocusto obrigatório';
        if (empty($dados['cdplanocontabil'])) return 'cdplanocontabil obrigatório';
        if (!isset($dados['mesref']) || $dados['mesref'] < 1 || $dados['mesref'] > 12) return 'mesref inválido';
        if (empty($dados['anoref']) || $dados['anoref'] < 2000) return 'anoref inválido';
        if (!isset($dados['valor'])) return 'valor obrigatório';
        
        // Validar exercício existe e está ativo
        $stmt = $this->pdoHelm->prepare("
            SELECT 1 FROM back.exercicio 
            WHERE cdexercicio = :id AND flativo = false
        ");
        $stmt->execute([':id' => $dados['cdexercicio']]);
        if (!$stmt->fetchColumn()) {
            return 'Exercício não encontrado ou inativo';
        }
        
        return null;
    }
    
    /**
     * Inserir lote de registros
     */
    private function inserirLote($lote) {
        $stmt = $this->pdoHelm->prepare("
            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
            )
        ");
        
        $count = 0;
        $idlote = md5($this->cdIntegracao . '_' . time() . '_' . count($lote));
        
        foreach ($lote as $r) {
            $stmt->execute([
                ':cdempresa' => $r['cdempresa'],
                ':cdexercicio' => $r['cdexercicio'],
                ':cdcentrocusto' => $r['cdcentrocusto'],
                ':cdplanocontabil' => $r['cdplanocontabil'],
                ':mesref' => $r['mesref'],
                ':anoref' => $r['anoref'],
                ':tpmovimento' => $r['tpmovimento'] ?? 'D',
                ':valor' => $r['valor'],
                ':dscomplemento' => $r['dscomplemento'] ?? null,
                ':dtmovimento' => $r['dtmovimento'] ?? date('Y-m-d'),
                ':cdintegracao' => $this->cdIntegracao,
                ':florigem' => 2, // 2 = sincronizado de origem
                ':json_origem' => json_encode($r),
                ':flcancelado' => 0,
                ':cdpessoa' => 0,
                ':idlote' => $idlote
            ]);
            $count++;
        }
        
        return $count;
    }
}
