<?php

namespace Modules\SPED\Services;

use PDO;
use Exception;

/**
 * Serviço para geração e validação de SPED Fiscal (EFD-ICMS/IPI)
 */
class SPEDFiscalService {
    private PDO $pdo;
    private string $tenantId;

    public function __construct(string $tenantId) {
        $this->tenantId = $tenantId;
        $this->pdo = \Gestoreasy\Core\Tenant\ConnectionManager::forTenant($tenantId);
    }

    /**
     * Gera arquivo SPED Fiscal
     */
    public function gerar(int $ano, int $mes, array $opcoes = []): array {
        try {
            // Usar gerador existente do sistema
            $gen = new \Gestoreasy\Core\Fiscal\Sped\SpedFiscalGenerator($this->pdo);
            $period = new \Gestoreasy\Core\Fiscal\Sped\Period($ano, $mes);
            
            $opts = array_merge([
                'perfil' => $opcoes['perfil'] ?? 'A',
                'ind_ativ' => $opcoes['ind_ativ'] ?? 1,
                'cod_fin' => $opcoes['cod_fin'] ?? 0,
                'cod_ver' => $opcoes['cod_ver'] ?? '017',
            ], $opcoes);

            $lines = $gen->generate($period, $opts);
            $txt = $gen->toTxt($lines);

            // Salvar arquivo
            $arquivoId = $this->salvarArquivo('fiscal', $ano, $mes, $txt, $opts);

            // Registrar log
            $this->registrarLog($arquivoId, 'geracao', 'sucesso', 'SPED Fiscal gerado com sucesso', [
                'ano' => $ano,
                'mes' => $mes,
                'total_linhas' => count($lines)
            ]);

            return [
                'arquivo_id' => $arquivoId,
                'total_linhas' => count($lines),
                'tamanho_bytes' => strlen($txt),
                'conteudo' => $txt
            ];
        } catch (Exception $e) {
            $this->registrarLog(null, 'geracao', 'erro', $e->getMessage());
            throw $e;
        }
    }

    /**
     * Valida arquivo SPED Fiscal
     */
    public function validar(int $arquivoId): array {
        try {
            // Buscar arquivo
            $stmt = $this->pdo->prepare('SELECT * FROM sped_arquivos WHERE id = ? AND tenant_id = ?');
            $stmt->execute([$arquivoId, $this->tenantId]);
            $arquivo = $stmt->fetch(PDO::FETCH_ASSOC);

            if (!$arquivo) {
                throw new Exception('Arquivo não encontrado');
            }

            // Ler arquivo
            $conteudo = file_get_contents($arquivo['caminho_arquivo']);
            $lines = explode("\n", $conteudo);

            // Validar usando validador existente
            $issues = \Gestoreasy\Core\Fiscal\Sped\Validation\SpedValidator::validateSpedLines($lines);

            $erros = [];
            $avisos = [];
            foreach ($issues as $issue) {
                if ($issue['level'] === 'error') {
                    $erros[] = $issue;
                } else {
                    $avisos[] = $issue;
                }
            }

            // Atualizar arquivo
            $stmt = $this->pdo->prepare('
                UPDATE sped_arquivos
                SET validado = ?,
                    data_validacao = NOW(),
                    erros_validacao = ?,
                    status = CASE WHEN ? = 0 THEN "validado" ELSE "erro" END
                WHERE id = ?
            ');
            $stmt->execute([
                empty($erros),
                !empty($erros) ? json_encode($erros) : null,
                count($erros),
                $arquivoId
            ]);

            // Registrar log
            $this->registrarLog($arquivoId, 'validacao', empty($erros) ? 'sucesso' : 'erro', 
                empty($erros) ? 'Validação concluída sem erros' : 'Validação encontrou erros',
                ['erros' => count($erros), 'avisos' => count($avisos)]
            );

            return [
                'valido' => empty($erros),
                'erros' => $erros,
                'avisos' => $avisos,
                'total_erros' => count($erros),
                'total_avisos' => count($avisos)
            ];
        } catch (Exception $e) {
            $this->registrarLog($arquivoId, 'validacao', 'erro', $e->getMessage());
            throw $e;
        }
    }

    /**
     * Salva arquivo SPED
     */
    private function salvarArquivo(string $tipo, int $ano, int $mes, string $conteudo, array $opcoes = []): int {
        $base = getenv('SPED_EXPORT_DIR') ?: (__DIR__ . '/../../../storage/sped');
        $dir = rtrim($base, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $this->tenantId . DIRECTORY_SEPARATOR . sprintf('%04d-%02d', $ano, $mes);
        
        if (!is_dir($dir)) {
            mkdir($dir, 0775, true);
        }

        $nomeArquivo = "sped_{$tipo}_{$ano}{$mes}_" . date('Ymd_His') . '.txt';
        $caminho = $dir . DIRECTORY_SEPARATOR . $nomeArquivo;
        
        file_put_contents($caminho, $conteudo);

        $userContext = \Gestoreasy\Core\Auth\UserContext::getCurrent();

        $stmt = $this->pdo->prepare('
            INSERT INTO sped_arquivos (
                tenant_id, tipo, ano, mes, nome_arquivo, caminho_arquivo,
                tamanho_bytes, versao_layout, perfil, created_by
            ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
        ');
        $stmt->execute([
            $this->tenantId,
            $tipo,
            $ano,
            $mes,
            $nomeArquivo,
            $caminho,
            strlen($conteudo),
            $opcoes['cod_ver'] ?? '017',
            $opcoes['perfil'] ?? 'A',
            $userContext?->userId
        ]);

        return (int)$this->pdo->lastInsertId();
    }

    /**
     * Envia arquivo SPED Fiscal para SEFAZ
     * Nota: O envio real é feito através do PVA (Programa Validador e Assinador)
     * Este método prepara o arquivo e registra o envio
     */
    public function enviarParaSEFAZ(int $arquivoId): array {
        try {
            // Buscar arquivo
            $stmt = $this->pdo->prepare('SELECT * FROM sped_arquivos WHERE id = ? AND tenant_id = ?');
            $stmt->execute([$arquivoId, $this->tenantId]);
            $arquivo = $stmt->fetch(PDO::FETCH_ASSOC);

            if (!$arquivo) {
                throw new Exception('Arquivo não encontrado');
            }

            if ($arquivo['tipo'] !== 'fiscal') {
                throw new Exception('Apenas arquivos SPED Fiscal podem ser enviados');
            }

            // Validar antes de enviar
            $validacao = $this->validar($arquivoId);
            if (!$validacao['valido']) {
                throw new Exception('Arquivo não pode ser enviado: possui erros de validação');
            }

            // Buscar dados da empresa para obter UF e ambiente
            $stmt = $this->pdo->query('SELECT estado, cnpj FROM empresas ORDER BY id ASC LIMIT 1');
            $empresa = $stmt->fetch(PDO::FETCH_ASSOC);
            $uf = $empresa['estado'] ?? 'SP';
            $cnpj = $empresa['cnpj'] ?? '';

            // Preparar arquivo para envio
            $arquivoPath = $arquivo['caminho_arquivo'];
            $conteudo = file_get_contents($arquivoPath);

            // Gerar hash do arquivo para verificação
            $hashArquivo = hash('sha256', $conteudo);

            // Preparar dados de envio
            $dadosEnvio = [
                'arquivo_id' => $arquivoId,
                'cnpj' => $cnpj,
                'uf' => $uf,
                'ano' => $arquivo['ano'],
                'mes' => $arquivo['mes'],
                'versao_layout' => $arquivo['versao_layout'],
                'hash' => $hashArquivo,
                'tamanho_bytes' => $arquivo['tamanho_bytes'],
                'data_preparacao' => date('Y-m-d H:i:s')
            ];

            // Tentar envio via PVA (simulado - em produção, usar integração real)
            $resultadoEnvio = $this->enviarViaPVA($arquivoPath, $dadosEnvio);

            // Atualizar status do arquivo
            $stmt = $this->pdo->prepare('
                UPDATE sped_arquivos
                SET status = ?,
                    data_envio = NOW(),
                    protocolo_envio = ?,
                    retorno_envio = ?
                WHERE id = ?
            ');
            $stmt->execute([
                $resultadoEnvio['status'],
                $resultadoEnvio['protocolo'] ?? null,
                json_encode($resultadoEnvio),
                $arquivoId
            ]);

            // Registrar log
            $this->registrarLog($arquivoId, 'envio', 
                $resultadoEnvio['status'] === 'aceito' ? 'sucesso' : 'erro',
                $resultadoEnvio['mensagem'],
                $resultadoEnvio
            );

            return $resultadoEnvio;
        } catch (Exception $e) {
            $this->registrarLog($arquivoId, 'envio', 'erro', $e->getMessage());
            throw $e;
        }
    }

    /**
     * Envia arquivo via PVA (Programa Validador e Assinador)
     * Nota: Em produção, isso deve ser feito através de integração com o PVA
     * ou sistema certificado da Receita Federal
     */
    private function enviarViaPVA(string $arquivoPath, array $dados): array {
        // Verificar se arquivo existe
        if (!file_exists($arquivoPath)) {
            throw new Exception('Arquivo não encontrado no caminho especificado: ' . $arquivoPath);
        }

        // Validar tamanho do arquivo (máximo 50MB)
        $tamanho = filesize($arquivoPath);
        if ($tamanho > 50 * 1024 * 1024) {
            throw new Exception('Arquivo muito grande. Tamanho máximo: 50MB');
        }

        // Validar formato do arquivo (deve ser .txt)
        if (pathinfo($arquivoPath, PATHINFO_EXTENSION) !== 'txt') {
            throw new Exception('Arquivo deve ser no formato .txt');
        }

        // Ler conteúdo do arquivo
        $conteudo = file_get_contents($arquivoPath);
        if ($conteudo === false) {
            throw new Exception('Erro ao ler arquivo');
        }

        // Validar estrutura básica do arquivo SPED
        if (!$this->validarEstruturaSped($conteudo)) {
            throw new Exception('Arquivo SPED com estrutura inválida');
        }

        // Em produção, aqui seria feita a integração real com o PVA ou ReceitaWS
        // Por enquanto, simular o processo de envio
        
        // Verificar se há configuração de PVA
        $pvaConfig = $this->getPVAConfig();
        
        if (empty($pvaConfig['habilitado'])) {
            // Modo simulado - apenas preparar arquivo
            return [
                'status' => 'preparado',
                'mensagem' => 'Arquivo preparado para envio. Configure o PVA para envio real.',
                'protocolo' => null,
                'arquivo_preparado' => true,
                'caminho_arquivo' => $arquivoPath,
                'instrucoes' => [
                    '1. Abra o PVA (Programa Validador e Assinador)',
                    '2. Importe o arquivo: ' . $arquivoPath,
                    '3. Valide e assine o arquivo',
                    '4. Envie para a SEFAZ através do PVA'
                ]
            ];
        }

        // Se PVA estiver configurado, tentar envio automático
        // (Isso requer integração com API do PVA ou sistema certificado)
        try {
            $resultado = $this->enviarViaAPI($arquivoPath, $dados, $pvaConfig);
            return $resultado;
        } catch (Exception $e) {
            // Se falhar, retornar modo preparado
            return [
                'status' => 'erro',
                'mensagem' => 'Erro ao enviar via API: ' . $e->getMessage(),
                'protocolo' => null,
                'arquivo_preparado' => true,
                'caminho_arquivo' => $arquivoPath,
                'erro' => $e->getMessage()
            ];
        }
    }

    /**
     * Envia via API (quando PVA estiver configurado)
     */
    private function enviarViaAPI(string $arquivoPath, array $dados, array $config): array {
        // URL do serviço de envio SPED (exemplo - varia por UF)
        $uf = $dados['uf'];
        $endpoint = $this->getEndpointEnvio($uf, $config['ambiente'] ?? 'homologacao');

        // Preparar requisição
        $conteudo = file_get_contents($arquivoPath);
        $base64 = base64_encode($conteudo);

        $payload = [
            'cnpj' => $dados['cnpj'],
            'ano' => $dados['ano'],
            'mes' => $dados['mes'],
            'versao_layout' => $dados['versao_layout'],
            'arquivo_base64' => $base64,
            'hash' => $dados['hash']
        ];

        // Fazer requisição (requer certificado digital)
        $ch = curl_init($endpoint);
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
            CURLOPT_POSTFIELDS => json_encode($payload),
            CURLOPT_HTTPHEADER => [
                'Content-Type: application/json',
                'Authorization: Bearer ' . ($config['token'] ?? '')
            ],
            CURLOPT_SSL_VERIFYPEER => true,
            CURLOPT_SSLCERT => $config['cert_path'] ?? null,
            CURLOPT_SSLKEY => $config['key_path'] ?? null,
            CURLOPT_TIMEOUT => 60
        ]);

        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $error = curl_error($ch);
        curl_close($ch);

        if ($error) {
            throw new Exception("Erro na requisição: {$error}");
        }

        if ($httpCode >= 400) {
            $errorData = json_decode($response, true);
            throw new Exception($errorData['message'] ?? "Erro HTTP {$httpCode}");
        }

        $resultado = json_decode($response, true);

        return [
            'status' => $resultado['status'] ?? 'aceito',
            'mensagem' => $resultado['mensagem'] ?? 'Arquivo enviado com sucesso',
            'protocolo' => $resultado['protocolo'] ?? null,
            'data_recebimento' => $resultado['data_recebimento'] ?? date('Y-m-d H:i:s')
        ];
    }

    /**
     * Obtém configuração do PVA
     */
    private function getPVAConfig(): array {
        $stmt = $this->pdo->prepare('
            SELECT 
                pva_habilitado, pva_endpoint, pva_token, pva_cert_path, pva_key_path, pva_ambiente
            FROM sped_config
            WHERE tenant_id = ?
        ');
        $stmt->execute([$this->tenantId]);
        $config = $stmt->fetch(PDO::FETCH_ASSOC);

        if (!$config) {
            return ['habilitado' => false];
        }

        return [
            'habilitado' => (bool)($config['pva_habilitado'] ?? false),
            'endpoint' => $config['pva_endpoint'] ?? null,
            'token' => $config['pva_token'] ?? null,
            'cert_path' => $config['pva_cert_path'] ?? null,
            'key_path' => $config['pva_key_path'] ?? null,
            'ambiente' => $config['pva_ambiente'] ?? 'homologacao'
        ];
    }

    /**
     * Obtém endpoint de envio por UF
     */
    private function getEndpointEnvio(string $uf, string $ambiente): string {
        // Endpoints de envio SPED (exemplos - podem variar)
        $endpoints = [
            'SP' => [
                'producao' => 'https://nfe.fazenda.sp.gov.br/ws/sped/envio',
                'homologacao' => 'https://homologacao.nfe.fazenda.sp.gov.br/ws/sped/envio'
            ],
            'RJ' => [
                'producao' => 'https://nfe.fazenda.rj.gov.br/ws/sped/envio',
                'homologacao' => 'https://homologacao.nfe.fazenda.rj.gov.br/ws/sped/envio'
            ],
            // Adicionar outras UFs conforme necessário
        ];

        $uf = strtoupper($uf);
        if (isset($endpoints[$uf])) {
            return $endpoints[$uf][$ambiente] ?? $endpoints[$uf]['homologacao'];
        }

        // Endpoint genérico (não recomendado em produção)
        return 'https://sped.fazenda.gov.br/envio';
    }

    /**
     * Valida estrutura básica do arquivo SPED
     */
    private function validarEstruturaSped(string $conteudo): bool {
        if (empty($conteudo)) {
            return false;
        }

        // Verificar se contém registro inicial (0000 ou 0001)
        if (strpos($conteudo, '|0000|') === false && strpos($conteudo, '|0001|') === false) {
            return false;
        }

        // Verificar se contém registro final (9999)
        if (strpos($conteudo, '|9999|') === false) {
            return false;
        }

        // Verificar formato (deve usar pipe como delimitador)
        $linhas = explode("\n", $conteudo);
        if (count($linhas) < 3) {
            return false;
        }

        // Verificar se pelo menos uma linha tem formato válido (começa e termina com |)
        $linhaValida = false;
        foreach ($linhas as $linha) {
            $linha = trim($linha);
            if (empty($linha)) {
                continue;
            }
            if (strpos($linha, '|') === 0 && substr($linha, -1) === '|') {
                $linhaValida = true;
                break;
            }
        }

        return $linhaValida;
    }

    /**
     * Registra log
     */
    private function registrarLog(?int $arquivoId, string $operacao, string $status, string $mensagem, array $detalhes = []): void {
        $stmt = $this->pdo->prepare('
            INSERT INTO sped_logs (
                tenant_id, arquivo_id, operacao, status, mensagem, detalhes_json
            ) VALUES (?, ?, ?, ?, ?, ?)
        ');
        $stmt->execute([
            $this->tenantId,
            $arquivoId,
            $operacao,
            $status,
            $mensagem,
            !empty($detalhes) ? json_encode($detalhes) : null
        ]);
    }
}

