<?php

declare(strict_types=1);

namespace App\Integrations;

use Exception;

/**
 * Cliente para integração com Sicacred (Serviço de Informação de Cadastro Atacadista)
 *
 * Documentação: docs/sicacred.txt
 * URL Base: https://sicacred.com.br:7076/api/
 */
class SicacredClient
{
    private const BASE_URL = 'https://sicacred.com.br:7076/api';
    private const ENDPOINT_GET_TOKEN = '/gettoken';
    private const ENDPOINT_CONSULTA_DATAS = '/SicaIntegration/ConsultaDatas';
    private const ENDPOINT_POST_FILE = '/SicaIntegration/PostFile';

    private string $codGrpEmp;
    private string $codigoErp;
    private string $chave;
    private bool $producao;
    private ?string $accessToken = null;
    private ?int $tokenExpiresAt = null;

    /**
     * Escreve log em arquivo dedicado
     */
    private function log(string $message, string $level = 'INFO'): void
    {
        $logFile = (defined('ROOT_PATH') ? ROOT_PATH : dirname(__DIR__, 2)) . '/storage/logs/sicacred-' . date('Y-m-d') . '.log';
        $logDir = dirname($logFile);

        if (!is_dir($logDir)) {
            @mkdir($logDir, 0775, true);
        }

        $timestamp = date('Y-m-d H:i:s');
        $logMessage = "[{$timestamp}] [{$level}] {$message}" . PHP_EOL;

        @file_put_contents($logFile, $logMessage, FILE_APPEND | LOCK_EX);
    }

    public function __construct(array $config)
    {
        $this->codGrpEmp = $config['cod_grp_emp'] ?? '';
        $this->codigoErp = $config['codigo_erp'] ?? '';
        $this->chave = $config['chave'] ?? '';
        $this->producao = $config['producao'] ?? false;

        // Validação: codigo_erp pode ser vazio (não é obrigatório em alguns casos)
        if (empty($this->codGrpEmp) || empty($this->chave)) {
            throw new Exception('Credenciais do Sicacred não configuradas. Configure CodGrpEmp e Chave.');
        }
    }

    /**
     * Obtém token de autenticação
     */
    private function getToken(): string
    {
        // Se já tem token válido, retorna
        if ($this->accessToken && $this->tokenExpiresAt && time() < $this->tokenExpiresAt) {
            $this->log("Usando token em cache (válido até " . date('Y-m-d H:i:s', $this->tokenExpiresAt) . ")");
            return $this->accessToken;
        }

        $url = self::BASE_URL . self::ENDPOINT_GET_TOKEN;
        $this->log("Obtendo novo token de autenticação. URL: {$url}");

        $data = [
            'grant_type' => 'password',
            'username' => $this->codGrpEmp,
            'password' => $this->chave
        ];

        // codigo_erp é opcional (adicionar apenas se fornecido)
        if (!empty($this->codigoErp)) {
            $data['codigoerp'] = $this->codigoErp;
        }

        $this->log("Iniciando requisição cURL para obter token...");
        $ch = curl_init($url);
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
            CURLOPT_POSTFIELDS => http_build_query($data),
            CURLOPT_HTTPHEADER => [
                'Content-Type: application/x-www-form-urlencoded'
            ],
            CURLOPT_SSL_VERIFYPEER => true,
            CURLOPT_SSL_VERIFYHOST => 2,
            CURLOPT_TIMEOUT => 30,
            CURLOPT_CONNECTTIMEOUT => 10
        ]);

        $this->log("Executando requisição cURL (timeout: 30s)...");
        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $error = curl_error($ch);
        $curlInfo = curl_getinfo($ch);
        curl_close($ch);

        $this->log("Requisição concluída. HTTP Code: {$httpCode}, Tempo total: " . ($curlInfo['total_time'] ?? 'N/A') . "s");

        if ($error) {
            $this->log("Erro ao obter token: {$error}", 'ERROR');
            throw new Exception("Erro ao obter token: {$error}");
        }

        if ($httpCode !== 200) {
            $this->log("Erro ao obter token. HTTP {$httpCode}: {$response}", 'ERROR');
            throw new Exception("Erro ao obter token. HTTP {$httpCode}: {$response}");
        }

        $result = json_decode($response, true);

        if (!isset($result['access_token'])) {
            $this->log("Token não retornado na resposta: {$response}", 'ERROR');
            throw new Exception("Token não retornado na resposta: {$response}");
        }

        $this->accessToken = $result['access_token'];
        // Token expira em 1 hora (padrão OAuth2)
        $this->tokenExpiresAt = time() + 3600;

        $this->log("Token obtido com sucesso. Expira em: " . date('Y-m-d H:i:s', $this->tokenExpiresAt));

        return $this->accessToken;
    }

    /**
     * Faz requisição autenticada
     */
    private function request(string $method, string $endpoint, array $data = []): array
    {
        $this->log("Iniciando requisição autenticada. Método: {$method}, Endpoint: {$endpoint}");

        $this->log("Obtendo token de autenticação...");
        $token = $this->getToken();
        $this->log("Token obtido com sucesso");

        $url = self::BASE_URL . $endpoint;
        $this->log("URL completa: {$url}");

        $this->log("Iniciando requisição cURL...");
        $ch = curl_init($url);
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_HTTPHEADER => [
                'Content-Type: application/json',
                'Authorization: Bearer ' . $token
            ],
            CURLOPT_SSL_VERIFYPEER => true,
            CURLOPT_SSL_VERIFYHOST => 2,
            CURLOPT_TIMEOUT => 60,
            CURLOPT_CONNECTTIMEOUT => 10
        ]);

        if ($method === 'POST') {
            curl_setopt($ch, CURLOPT_POST, true);
            $payload = json_encode($data, JSON_UNESCAPED_UNICODE);
            curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
            $this->log("Payload enviado: " . substr($payload, 0, 500) . (strlen($payload) > 500 ? '...' : ''));
        }

        $this->log("Executando requisição cURL (timeout: 60s)...");
        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $error = curl_error($ch);
        $curlInfo = curl_getinfo($ch);
        curl_close($ch);

        $this->log("Requisição concluída. HTTP Code: {$httpCode}, Tempo total: " . ($curlInfo['total_time'] ?? 'N/A') . "s");

        if ($error) {
            $this->log("Erro na requisição: {$error}", 'ERROR');
            throw new Exception("Erro na requisição: {$error}");
        }

        $result = json_decode($response, true);

        if ($httpCode !== 200) {
            $message = $result['MensagemRetorno'] ?? $result['message'] ?? "HTTP {$httpCode}";
            $this->log("Erro na API Sicacred: {$message} (HTTP {$httpCode})", 'ERROR');
            $this->log("Resposta completa: " . substr($response, 0, 1000), 'ERROR');

            // Verificar se é erro do servidor Sicacred (5xx) vs erro de dados (4xx)
            if ($httpCode >= 500) {
                throw new Exception("Erro no servidor Sicacred (infraestrutura): {$message}. Entre em contato com o suporte do Sicacred.");
            } else {
                throw new Exception("Erro na API Sicacred: {$message}");
            }
        }

        // Verificar se há erro na resposta mesmo com HTTP 200
        if (isset($result['CodigoRetorno']) && $result['CodigoRetorno'] !== 100) {
            $message = $result['MensagemRetorno'] ?? 'Erro desconhecido';
            $this->log("Erro retornado pela API Sicacred (CodigoRetorno: {$result['CodigoRetorno']}): {$message}", 'ERROR');

            // Se a mensagem contém erros de SQL Server, é problema de infraestrutura deles
            if (stripos($message, 'SQL Server') !== false ||
                stripos($message, 'SqlException') !== false ||
                stripos($message, 'conexão') !== false && stripos($message, 'SQL') !== false) {
                throw new Exception("Erro de infraestrutura no servidor Sicacred: {$message}. Entre em contato com o suporte do Sicacred.");
            }

            throw new Exception("Erro retornado pelo Sicacred: {$message}");
        }

        $this->log("Requisição bem-sucedida. Resposta: " . substr($response, 0, 500));

        return $result;
    }

    /**
     * Consulta datas sugeridas para envio
     */
    public function consultarDatas(string $codFonte): array
    {
        $this->log("Consultando datas sugeridas. CodFonte: {$codFonte}, Producao: " . ($this->producao ? 'true' : 'false'));

        $data = [
            'CodFonte' => $codFonte,
            'CodGrpEmp' => $this->codGrpEmp,
            'Chave' => $this->chave,
            'Producao' => $this->producao
        ];

        $this->log("Chamando método request()...");
        $result = $this->request('POST', self::ENDPOINT_CONSULTA_DATAS, $data);
        $this->log("Método request() concluído");

        $this->log("Consulta de datas concluída. CodigoRetorno: " . ($result['CodigoRetorno'] ?? 'N/A'));

        return $result;
    }

    /**
     * Envia dados para o Sicacred
     */
    public function enviarDados(array $dados): array
    {
        $totalClientes = count($dados['SicaCli'] ?? []);
        $totalCompras = count($dados['SicaCpr'] ?? []);
        $totalDocumentos = count($dados['SicaDoc'] ?? []);

        $this->log("Enviando dados para Sicacred. Clientes: {$totalClientes}, Compras: {$totalCompras}, Documentos: {$totalDocumentos}");

        $result = $this->request('POST', self::ENDPOINT_POST_FILE, $dados);

        $codigoRetorno = $result['CodigoRetorno'] ?? 'N/A';
        $mensagem = $result['MensagemRetorno'] ?? 'N/A';

        $this->log("Envio concluído. CodigoRetorno: {$codigoRetorno}, Mensagem: {$mensagem}");

        return $result;
    }
}

