<?php

declare(strict_types=1);

namespace App\Services;

use App\Integrations\SicacredClient;
use Exception;
use PDO;

/**
 * Service para integração com Sicacred
 * Monta os dados no formato esperado e envia para a API
 */
class SicacredService
{
    private PDO $db;
    private int $companyId;
    private string $codFonte;
    private array $config;

    /**
     * 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}] [SicacredService] {$message}" . PHP_EOL;

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

    public function __construct(PDO $db, int $companyId, string $codFonte, array $config)
    {
        $this->db = $db;
        $this->companyId = $companyId;
        $this->codFonte = $codFonte;
        $this->config = $config;
    }

    /**
     * Consulta datas sugeridas para envio
     */
    public function consultarDatas(): array
    {
        $this->log("Iniciando consulta de datas. Company ID: {$this->companyId}, CodFonte: {$this->codFonte}");

        try {
            $this->log("Criando instância do SicacredClient...");
            $client = new SicacredClient($this->config);
            $this->log("SicacredClient criado com sucesso");

            $this->log("Chamando consultarDatas no cliente...");
            $result = $client->consultarDatas($this->codFonte);
            $this->log("Resposta recebida do cliente");

            $this->log("Consulta de datas concluída com sucesso. CodigoRetorno: " . ($result['CodigoRetorno'] ?? 'N/A'));
            return $result;
        } catch (Exception $e) {
            $this->log("Erro ao consultar datas: " . $e->getMessage(), 'ERROR');
            $this->log("Stack trace: " . $e->getTraceAsString(), 'ERROR');
            throw $e;
        }
    }

    /**
     * Monta e envia dados para o Sicacred
     */
    public function enviarDados(string $dataInicial, string $dataFinal, ?string $emailResponsavel = null): array
    {
        $this->log("Iniciando envio de dados. Company ID: {$this->companyId}, Período: {$dataInicial} a {$dataFinal}");

        try {
            $hoje = date('Y-m-d');

            // Montar SicaPrm
            $sicaPrm = [
                'CODFONTE' => $this->codFonte,
                'DT_ATU' => $hoje,
                'DT_INI' => $dataInicial,
                'DT_FIM' => $dataFinal,
                'CodGrpEmp' => (int)$this->config['cod_grp_emp'],
                'EmlRspFte' => $emailResponsavel ?? ''
            ];

            $this->log("SicaPrm montado");

            // Montar SicaFmp (formas de pagamento padrão)
            $sicaFmp = $this->montarSicaFmp();
            $this->log("SicaFmp montado: " . count($sicaFmp) . " formas de pagamento");

            // Montar SicaFmpMap (mapeamento de formas de pagamento)
            $sicaFmpMap = $this->montarSicaFmpMap();
            $this->log("SicaFmpMap montado: " . count($sicaFmpMap) . " mapeamentos");

            // Selecionar clientes elegíveis
            $clientesIds = $this->selecionarClientesElegiveis($dataInicial, $dataFinal);
            $this->log("Clientes elegíveis encontrados: " . count($clientesIds));

            // Montar SicaCli (clientes)
            $sicaCli = $this->montarSicaCli($clientesIds);
            $this->log("SicaCli montado: " . count($sicaCli) . " clientes");

            // Montar SicaCpr (compras)
            $sicaCpr = $this->montarSicaCpr($clientesIds, $dataInicial, $dataFinal);
            $this->log("SicaCpr montado: " . count($sicaCpr) . " compras");

            // Montar SicaDoc (documentos)
            $sicaDoc = $this->montarSicaDoc($clientesIds, $dataInicial, $dataFinal);
            $this->log("SicaDoc montado: " . count($sicaDoc) . " documentos");

            // Montar payload completo
            $payload = [
                'Producao' => $this->config['producao'] ?? false,
                'SicaPrm' => $sicaPrm,
                'SicaFmp' => $sicaFmp,
                'SicaFmpMap' => $sicaFmpMap,
                'SicaCli' => $sicaCli,
                'SicaCpr' => $sicaCpr,
                'SicaDoc' => $sicaDoc
            ];

            $this->log("Payload completo montado. Enviando para API...");

            // Enviar para API
            $client = new SicacredClient($this->config);
            $result = $client->enviarDados($payload);

            $codigoRetorno = $result['CodigoRetorno'] ?? 'N/A';
            $this->log("Envio concluído com sucesso. CodigoRetorno: {$codigoRetorno}");

            // Adicionar totais ao resultado para facilitar gravação no banco
            $result['total_clientes'] = count($sicaCli);
            $result['total_compras'] = count($sicaCpr);
            $result['total_documentos'] = count($sicaDoc);

            return $result;
        } catch (Exception $e) {
            $this->log("Erro ao enviar dados: " . $e->getMessage(), 'ERROR');
            $this->log("Stack trace: " . $e->getTraceAsString(), 'ERROR');
            throw $e;
        }
    }

    /**
     * Monta formas de pagamento padrão do Sicacred
     */
    private function montarSicaFmp(): array
    {
        return [
            ['COD_FMP' => 'AV', 'NOM_FMP' => 'A VISTA'],
            ['COD_FMP' => 'CH', 'NOM_FMP' => 'CHEQUE PRÉ-DATADO'],
            ['COD_FMP' => 'CD', 'NOM_FMP' => 'CHEQUE DEVOLVIDO'],
            ['COD_FMP' => 'DP', 'NOM_FMP' => 'DUPLICATA'],
            ['COD_FMP' => 'NP', 'NOM_FMP' => 'NOTA PROMISSÓRIA'],
            ['COD_FMP' => 'BL', 'NOM_FMP' => 'BOLETO BANCARIO'],
            ['COD_FMP' => 'DB', 'NOM_FMP' => 'DEPÓSITO BANCARIO'],
            ['COD_FMP' => 'CA', 'NOM_FMP' => 'CARTÃO'],
            ['COD_FMP' => 'CT', 'NOM_FMP' => 'CARTEIRA']
        ];
    }

    /**
     * Monta mapeamento de formas de pagamento
     * TODO: Buscar do banco de dados ou configuração
     */
    private function montarSicaFmpMap(): array
    {
        // Por enquanto, retorna mapeamento padrão
        // Deve ser configurado no sistema
        return [];
    }

    /**
     * Seleciona clientes elegíveis para envio
     */
    private function selecionarClientesElegiveis(string $dataInicial, string $dataFinal): array
    {
        $this->log("Selecionando clientes elegíveis no período {$dataInicial} a {$dataFinal}");

        // Verificar estrutura da tabela vendas
        $stmtCheck = $this->db->query("SHOW COLUMNS FROM vendas");
        $colunasVendas = $stmtCheck->fetchAll(PDO::FETCH_COLUMN);
        $temPaymentMethodId = in_array('payment_method_id', $colunasVendas);
        $temSaleDate = in_array('sale_date', $colunasVendas);
        $temCreatedAt = in_array('created_at', $colunasVendas);

        // Determinar campo de data: sale_date > created_at > não usar
        $campoDataVenda = null;
        if ($temSaleDate) {
            $campoDataVenda = 'sale_date';
        } elseif ($temCreatedAt) {
            $campoDataVenda = 'created_at';
        }

        $campoPaymentMethod = $temPaymentMethodId ? 'payment_method_id' : 'payment_method';
        $temPaymentMethod = in_array($campoPaymentMethod, $colunasVendas);

        // Construir condições dinamicamente
        $condicoes = [];

        // Condição 1: Compra à prazo no período (apenas se tiver campo de data e payment_method)
        if ($campoDataVenda && $temPaymentMethod) {
            $condicaoCompra = $temPaymentMethodId
                ? "v.{$campoPaymentMethod} IS NOT NULL AND v.{$campoPaymentMethod} != 1"
                : "v.{$campoPaymentMethod} IS NOT NULL";

            $condicoes[] = "
                EXISTS (
                    SELECT 1 FROM vendas v
                    WHERE v.customer_id = p.id
                      AND v.company_id = :company_id_v1
                      AND DATE(v.{$campoDataVenda}) >= :data_inicial_v1
                      AND DATE(v.{$campoDataVenda}) <= :data_final_v1
                      AND {$condicaoCompra}
                )
            ";
        }

        // Condição 2: Documento pago no período
        $condicoes[] = "
            EXISTS (
                SELECT 1 FROM contas_receber cr
                WHERE cr.customer_id = p.id
                  AND cr.company_id = :company_id_v2
                  AND cr.amount_received > 0
                  AND DATE(cr.updated_at) >= :data_inicial_v2
                  AND DATE(cr.updated_at) <= :data_final_v2
            )
        ";

        // Condição 3: Documento em aberto
        $condicoes[] = "
            EXISTS (
                SELECT 1 FROM contas_receber cr
                WHERE cr.customer_id = p.id
                  AND cr.company_id = :company_id_v3
                  AND cr.amount_remaining > 0
                  AND DATE(cr.due_date) <= :data_final_v3
            )
        ";

        if (empty($condicoes)) {
            $this->log("Nenhuma condição pode ser aplicada. Retornando array vazio.");
            return [];
        }

        $query = "
            SELECT DISTINCT p.id
            FROM pessoas p
            WHERE p.company_id = :company_id
              AND p.type IN ('cliente', 'ambos')
              AND (" . implode(' OR ', $condicoes) . ")
        ";

        // Preparar parâmetros
        $params = [
            'company_id' => $this->companyId,
            'company_id_v2' => $this->companyId,
            'company_id_v3' => $this->companyId,
            'data_inicial_v2' => $dataInicial,
            'data_final_v2' => $dataFinal,
            'data_final_v3' => $dataFinal
        ];

        // Adicionar parâmetros da condição de vendas se existir
        if ($campoDataVenda && $temPaymentMethod) {
            $params['company_id_v1'] = $this->companyId;
            $params['data_inicial_v1'] = $dataInicial;
            $params['data_final_v1'] = $dataFinal;
        }

        $stmt = $this->db->prepare($query);
        $stmt->execute($params);

        $clientesIds = $stmt->fetchAll(PDO::FETCH_COLUMN);
        $this->log("Clientes elegíveis encontrados: " . count($clientesIds));

        return $clientesIds;
    }

    /**
     * Monta dados dos clientes (SicaCli)
     */
    private function montarSicaCli(array $clientesIds): array
    {
        if (empty($clientesIds)) {
            return [];
        }

        // Verificar estrutura da tabela pessoas
        $stmtCheckPessoas = $this->db->query("SHOW COLUMNS FROM pessoas");
        $colunasPessoas = $stmtCheckPessoas->fetchAll(PDO::FETCH_COLUMN);

        // Verificar estrutura da tabela vendas para determinar campo de data
        $stmtCheckVendas = $this->db->query("SHOW COLUMNS FROM vendas");
        $colunasVendas = $stmtCheckVendas->fetchAll(PDO::FETCH_COLUMN);
        $temSaleDate = in_array('sale_date', $colunasVendas);
        $temCreatedAt = in_array('created_at', $colunasVendas);

        // Determinar campo de data da venda
        $campoDataVenda = null;
        if ($temSaleDate) {
            $campoDataVenda = 'v.sale_date';
        } elseif ($temCreatedAt) {
            $campoDataVenda = 'v.created_at';
        }

        $placeholders = implode(',', array_fill(0, count($clientesIds), '?'));

        // Construir campo DT_CAD dinamicamente
        if ($campoDataVenda) {
            $campoDT_CAD = "COALESCE(p.created_at, MIN({$campoDataVenda}))";
            $joinVendas = "LEFT JOIN vendas v ON v.customer_id = p.id AND v.company_id = p.company_id";
        } else {
            $campoDT_CAD = "p.created_at";
            $joinVendas = "";
        }

        // Verificar quais colunas existem e construir SELECT dinamicamente
        $campos = [
            'p.id as COD_CLI',
            "{$campoDT_CAD} as DT_CAD",
            in_array('name', $colunasPessoas) ? 'p.name as R_SOCIAL' : "'' as R_SOCIAL",
            in_array('trade_name', $colunasPessoas) ? 'p.trade_name as N_FANTAS' : "'' as N_FANTAS",
            in_array('document', $colunasPessoas) ? 'p.document as CNPJ_CPF' : "'' as CNPJ_CPF",
            in_array('state_registration', $colunasPessoas) ? 'p.state_registration as INS_ESTADUAL' :
            (in_array('rg_ie', $colunasPessoas) ? 'p.rg_ie as INS_ESTADUAL' : "'' as INS_ESTADUAL"),
            in_array('address', $colunasPessoas) ? 'p.address as END_CLI' : "'' as END_CLI",
            in_array('district', $colunasPessoas) ? 'p.district as BAIRRO' :
            (in_array('bairro', $colunasPessoas) ? 'p.bairro as BAIRRO' : "'' as BAIRRO"),
            in_array('city', $colunasPessoas) ? 'p.city as CIDADE' : "'' as CIDADE",
            in_array('state', $colunasPessoas) ? 'p.state as UF' : "'' as UF",
            in_array('zip_code', $colunasPessoas) ? 'p.zip_code as CEPCLI' :
            (in_array('cep', $colunasPessoas) ? 'p.cep as CEPCLI' : "'' as CEPCLI"),
            in_array('phone', $colunasPessoas) ? 'p.phone as FONE1' : "'' as FONE1",
            in_array('mobile', $colunasPessoas) ? 'p.mobile as FONE2' : "'' as FONE2",
            in_array('email', $colunasPessoas) ? 'p.email as EMAIL' : "'' as EMAIL",
            in_array('is_active', $colunasPessoas) ?
                "CASE WHEN p.is_active = 1 THEN 'Ativo' ELSE 'Inativo' END as SITUACAO" :
                "'Ativo' as SITUACAO"
        ];

        $query = "
            SELECT
                " . implode(",\n                ", $campos) . ",
                '' as OBS,
                '' as FORMAS_P,
                '' as PRZ_PGTO,
                0.00 as LIMITE
            FROM pessoas p
            {$joinVendas}
            WHERE p.id IN ({$placeholders})
              AND p.company_id = ?
            " . ($campoDataVenda ? "GROUP BY p.id" : "");

        $stmt = $this->db->prepare($query);
        $params = array_merge($clientesIds, [$this->companyId]);
        $stmt->execute($params);
        $clientes = $stmt->fetchAll(PDO::FETCH_ASSOC);

        $result = [];
        foreach ($clientes as $cliente) {
            $result[] = [
                'CODFONTE' => $this->codFonte,
                'COD_CLI' => (string)$cliente['COD_CLI'],
                'DT_CAD' => date('Y-m-d', strtotime($cliente['DT_CAD'])),
                'R_SOCIAL' => $cliente['R_SOCIAL'] ?? '',
                'N_FANTAS' => $cliente['N_FANTAS'] ?? '',
                'CNPJ_CPF' => preg_replace('/\D/', '', $cliente['CNPJ_CPF'] ?? ''),
                'INS_ESTADUAL' => $cliente['INS_ESTADUAL'] ?? '',
                'END_CLI' => $cliente['END_CLI'] ?? '',
                'BAIRRO' => $cliente['BAIRRO'] ?? '',
                'CIDADE' => $cliente['CIDADE'] ?? '',
                'UF' => strtoupper(substr($cliente['UF'] ?? '', 0, 2)),
                'CEPCLI' => preg_replace('/\D/', '', $cliente['CEPCLI'] ?? ''),
                'FONE1' => preg_replace('/\D/', '', $cliente['FONE1'] ?? ''),
                'FONE2' => preg_replace('/\D/', '', $cliente['FONE2'] ?? ''),
                'EMAIL' => $cliente['EMAIL'] ?? '',
                'SITUACAO' => $cliente['SITUACAO'] ?? 'Ativo',
                'OBS' => '',
                'FORMAS_P' => '',
                'PRZ_PGTO' => '',
                'LIMITE' => 0.0
            ];
        }

        return $result;
    }

    /**
     * Monta dados das compras (SicaCpr)
     */
    private function montarSicaCpr(array $clientesIds, string $dataInicial, string $dataFinal): array
    {
        if (empty($clientesIds)) {
            return [];
        }

        // Verificar estrutura da tabela vendas
        $stmtCheck = $this->db->query("SHOW COLUMNS FROM vendas");
        $colunasVendas = $stmtCheck->fetchAll(PDO::FETCH_COLUMN);
        $temPaymentMethodId = in_array('payment_method_id', $colunasVendas);
        $temSaleDate = in_array('sale_date', $colunasVendas);
        $campoDataVenda = $temSaleDate ? 'sale_date' : 'date';
        $campoPaymentMethod = $temPaymentMethodId ? 'payment_method_id' : 'payment_method';

        $placeholders = implode(',', array_fill(0, count($clientesIds), '?'));

        $query = "
            SELECT
                v.id as COD_CPR,
                v.customer_id as COD_CLI,
                v.{$campoDataVenda} as DT_CPR,
                v.total as VL_CPR,
                CASE
                    WHEN v.{$campoPaymentMethod} IS NOT NULL AND v.{$campoPaymentMethod} != 1 THEN 'S'
                    ELSE 'N'
                END as FOI_PRAZ,
                COALESCE(v.{$campoPaymentMethod}, 1) as CodFmpFte
            FROM vendas v
            WHERE v.customer_id IN ({$placeholders})
              AND v.company_id = ?
              AND v.{$campoDataVenda} >= ?
              AND v.{$campoDataVenda} <= ?
            ORDER BY v.{$campoDataVenda} ASC
        ";

        $stmt = $this->db->prepare($query);
        $params = array_merge($clientesIds, [$this->companyId, $dataInicial, $dataFinal]);
        $stmt->execute($params);
        $compras = $stmt->fetchAll(PDO::FETCH_ASSOC);

        $result = [];
        foreach ($compras as $compra) {
            $result[] = [
                'CODFONTE' => $this->codFonte,
                'COD_CPR' => (string)$compra['COD_CPR'],
                'COD_CLI' => (string)$compra['COD_CLI'],
                'DT_CPR' => date('Y-m-d', strtotime($compra['DT_CPR'])),
                'VL_CPR' => (float)$compra['VL_CPR'],
                'FOI_PRAZ' => $compra['FOI_PRAZ'],
                'CodFmpFte' => (string)$compra['CodFmpFte']
            ];
        }

        return $result;
    }

    /**
     * Monta dados dos documentos (SicaDoc)
     */
    private function montarSicaDoc(array $clientesIds, string $dataInicial, string $dataFinal): array
    {
        if (empty($clientesIds)) {
            return [];
        }

        $placeholders = implode(',', array_fill(0, count($clientesIds), '?'));

        // Documentos pagos no período
        $queryPagos = "
            SELECT
                cr.id,
                cr.customer_id as COD_CLI,
                cr.created_at as DT_EMI,
                cr.due_date as DT_VENC,
                cr.updated_at as DT_PGTO,
                cr.amount as VL_DOC,
                cr.amount_received as VL_PGT,
                COALESCE(cr.payment_method, 1) as CodFmpFte
            FROM contas_receber cr
            WHERE cr.customer_id IN ({$placeholders})
              AND cr.company_id = ?
              AND cr.amount_received > 0
              AND DATE(cr.updated_at) >= ?
              AND DATE(cr.updated_at) <= ?
        ";

        // Documentos em aberto
        $queryAbertos = "
            SELECT
                cr.id,
                cr.customer_id as COD_CLI,
                cr.created_at as DT_EMI,
                cr.due_date as DT_VENC,
                NULL as DT_PGTO,
                cr.amount_remaining as VL_DOC,
                0.0 as VL_PGT,
                COALESCE(cr.payment_method, 1) as CodFmpFte
            FROM contas_receber cr
            WHERE cr.customer_id IN ({$placeholders})
              AND cr.company_id = ?
              AND cr.amount_remaining > 0
              AND DATE(cr.due_date) <= ?
        ";

        $result = [];

        // Buscar documentos pagos
        $stmt = $this->db->prepare($queryPagos);
        $params = array_merge($clientesIds, [$this->companyId, $dataInicial, $dataFinal]);
        $stmt->execute($params);
        $docsPagos = $stmt->fetchAll(PDO::FETCH_ASSOC);

        foreach ($docsPagos as $doc) {
            $result[] = [
                'CODFONTE' => $this->codFonte,
                'COD_DOC' => (string)$doc['id'],
                'COD_CLI' => (string)$doc['COD_CLI'],
                'DT_EMI' => date('Y-m-d', strtotime($doc['DT_EMI'])),
                'DT_VENC' => date('Y-m-d', strtotime($doc['DT_VENC'])),
                'DT_PGTO' => $doc['DT_PGTO'] ? date('Y-m-d', strtotime($doc['DT_PGTO'])) : null,
                'VL_DOC' => (float)$doc['VL_DOC'],
                'VL_PGT' => (float)$doc['VL_PGT'],
                'CodFmpFte' => (string)$doc['CodFmpFte']
            ];
        }

        // Buscar documentos em aberto
        $stmt = $this->db->prepare($queryAbertos);
        $params = array_merge($clientesIds, [$this->companyId, $dataFinal]);
        $stmt->execute($params);
        $docsAbertos = $stmt->fetchAll(PDO::FETCH_ASSOC);

        foreach ($docsAbertos as $doc) {
            $result[] = [
                'CODFONTE' => $this->codFonte,
                'COD_DOC' => (string)$doc['id'],
                'COD_CLI' => (string)$doc['COD_CLI'],
                'DT_EMI' => date('Y-m-d', strtotime($doc['DT_EMI'])),
                'DT_VENC' => date('Y-m-d', strtotime($doc['DT_VENC'])),
                'DT_PGTO' => null,
                'VL_DOC' => (float)$doc['VL_DOC'],
                'VL_PGT' => 0.0,
                'CodFmpFte' => (string)$doc['CodFmpFte']
            ];
        }

        return $result;
    }
}

