<?php
class MapeadorDados {
    private $pdo;
    private $cdintegracao;
    private $contexto;
    private $mapeamentos;
    private $cdEmpresaPadrao;
    private $anoRef;
    private $mesRef;

    public function __construct($pdo, $cdintegracao, $cdEmpresaPadrao = null, $anoRef = null, $mesRef = null) {
        $this->pdo = $pdo;
        $this->cdintegracao = $cdintegracao;
        $this->cdEmpresaPadrao = $cdEmpresaPadrao;
        $this->anoRef = $anoRef !== null ? (int)$anoRef : null;
        $this->mesRef = $mesRef !== null ? (int)$mesRef : null;
        $this->carregarContexto();
        $this->carregarMapeamentos();
    }

    private function carregarContexto() {
        $stmt = $this->pdo->prepare("SELECT campo_empresa_origem, campo_data_origem FROM back.integracao_contexto WHERE cdintegracao = :i AND flativo = true");
        $stmt->execute([':i' => $this->cdintegracao]);
        $this->contexto = $stmt->fetch(PDO::FETCH_ASSOC);
        if (!$this->contexto) {
            throw new Exception('Contexto de integração não configurado');
        }
    }

    private function carregarMapeamentos() {
        $stmt = $this->pdo->prepare("SELECT nome_campo_helm, nome_campo_origem, tipo_mapeamento, valor_constante, expressao_sql, configuracao_json, flobrigatorio FROM back.integracao_mapeamento WHERE cdintegracao = :i AND flativo = true");
        $stmt->execute([':i' => $this->cdintegracao]);
        $this->mapeamentos = $stmt->fetchAll(PDO::FETCH_ASSOC);
    }

    public function mapear(array $origem): array {
        $codigoEmpresa = $this->obterCampo($origem, $this->contexto['campo_empresa_origem']);
        if ($codigoEmpresa === null || $codigoEmpresa === '') {
            // Para algumas integrações de realizado, a empresa é fixa
            // pelo contexto (empresa logada); nesse caso, usamos o
            // cdEmpresaPadrao informado pelo sincronizador.
            if ($this->cdEmpresaPadrao !== null) {
                $cdempresa = (int)$this->cdEmpresaPadrao;
            } else {
                throw new Exception('Código de empresa na origem não informado');
            }
        } else {
            $cdempresa = $this->resolverEmpresa($codigoEmpresa);
            if (!$cdempresa) {
                throw new Exception('Empresa não encontrada para código de integração: ' . $codigoEmpresa);
            }
        }

        $dataOrigemStr = $this->obterCampo($origem, $this->contexto['campo_data_origem']);
        if (!empty($dataOrigemStr)) {
            $time = strtotime($dataOrigemStr);
            if ($time === false) {
                throw new Exception('Data inválida na origem: ' . $dataOrigemStr);
            }
            $ano = (int)date('Y', $time);
            $mes = (int)date('n', $time);
        } else {
            // Se a origem não traz data, usamos o período informado
            // na sincronização (ano/mês) apenas para fins de
            // classificação e dtmovimento.
            if ($this->anoRef === null || $this->mesRef === null) {
                throw new Exception('Campo de data na origem não informado');
            }
            $ano = $this->anoRef;
            $mes = $this->mesRef;
            $time = strtotime(sprintf('%04d-%02d-01', $ano, $mes));
        }
        $cdexercicio = $this->resolverExercicio($cdempresa, $ano);

        $dest = [
            'cdempresa' => $cdempresa,
            'cdexercicio' => $cdexercicio,
            'anoref' => $ano,
            'mesref' => $mes,
            'dtmovimento' => date('Y-m-d', $time),
            'florigem' => 2,
            'flcancelado' => 0,
            'cdpessoa' => '0',
        ];

        foreach ($this->mapeamentos as $m) {
            $campoHelm = $m['nome_campo_helm'];
            $valor = null;
            switch ($m['tipo_mapeamento']) {
                case 'direto':
                    $valor = $this->obterCampo($origem, $m['nome_campo_origem']);
                    break;
                case 'constante':
                    $valor = $m['valor_constante'];
                    break;
                case 'expressao':
                    $valor = $this->avaliarExpressao($origem, $m['expressao_sql'], $m['nome_campo_origem']);
                    break;
                case 'lookup':
                    $valor = $this->executarLookup($origem, $m['configuracao_json']);
                    break;
                case 'json':
                    $valor = json_encode($origem, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
                    break;
                default:
                    throw new Exception('Tipo de mapeamento não suportado: ' . $m['tipo_mapeamento']);
            }
            if ($m['flobrigatorio'] && ($valor === null || $valor === '')) {
                throw new Exception('Campo obrigatório não mapeado: ' . $campoHelm);
            }
            $dest[$campoHelm] = $valor;
        }

        if (!isset($dest['id_integracao'])) {
            $dest['id_integracao'] = $this->gerarIdIntegracaoPadrao($origem);
        }
        $dest['json_origem'] = json_encode($origem, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
        // idlote e campos de período (mesref/anoref) ficam totalmente a cargo do mapeamento
        return $dest;
    }

    private function obterCampo(array $origem, string $caminho = null) {
        if (!$caminho) return null;
        return array_key_exists($caminho, $origem) ? $origem[$caminho] : null;
    }

    private function resolverEmpresa(string $codigoIntegracao) {
        $stmt = $this->pdo->prepare("SELECT cdempresa FROM back.empresa WHERE cdintegracao = :c LIMIT 1");
        $stmt->execute([':c' => $codigoIntegracao]);
        return $stmt->fetchColumn();
    }

    private function resolverExercicio(int $cdempresa, int $ano) {
        $stmt = $this->pdo->prepare("SELECT cdexercicio FROM back.exercicio WHERE cdempresa = :e AND anoref = :a LIMIT 1");
        $stmt->execute([':e' => $cdempresa, ':a' => $ano]);
        return $stmt->fetchColumn();
    }

    private function avaliarExpressao(array $origem, ?string $expr, ?string $campoBase) {
        if (!$expr) return null;
        if (stripos($expr, 'EXTRACT(MONTH') !== false) {
            $v = $this->obterCampo($origem, $campoBase);
            $t = strtotime($v);
            return $t ? (int)date('n', $t) : null;
        }
        if (stripos($expr, 'EXTRACT(YEAR') !== false) {
            $v = $this->obterCampo($origem, $campoBase);
            $t = strtotime($v);
            return $t ? (int)date('Y', $t) : null;
        }
        return null;
    }

    private function executarLookup(array $origem, ?string $configJson) {
        if (!$configJson) return null;
        $cfg = json_decode($configJson, true);
        if (!$cfg || empty($cfg['tabela']) || empty($cfg['coluna_pk']) || empty($cfg['coluna_origem'])) return null;
        $valorOrigem = $this->obterCampo($origem, $cfg['coluna_origem']);
        if ($valorOrigem === null || $valorOrigem === '') return null;
        $sql = "SELECT {$cfg['coluna_pk']} FROM {$cfg['tabela']} WHERE {$cfg['coluna_pk']} = :v LIMIT 1";
        if (!empty($cfg['coluna_codigo'])) {
            $sql = "SELECT {$cfg['coluna_pk']} FROM {$cfg['tabela']} WHERE {$cfg['coluna_codigo']} = :v LIMIT 1";
        }
        $stmt = $this->pdo->prepare($sql);
        $stmt->execute([':v' => $valorOrigem]);
        return $stmt->fetchColumn();
    }

    private function gerarIdIntegracaoPadrao(array $origem) {
        foreach (['id','idlancamento','codigo','chave'] as $k) {
            if (array_key_exists($k, $origem) && $origem[$k] !== '') return (string)$origem[$k];
        }
        return bin2hex(random_bytes(8));
    }
}
?>
