<?php

declare(strict_types=1);

namespace App\Controllers;

use App\Core\Auth;
use App\Core\Controller;
use App\Core\Database;
use App\Core\Logger;
use App\Core\Request;
use App\Core\Response;
use App\Core\Session;
use App\Core\Tenant;
use App\Models\CompanySetting;
use App\Models\PaymentMethod;
use App\Services\AuditService;
use App\Services\BoldService;
use App\Services\CredibancoService;
use App\Services\SecretCipherService;

class IntegrationController extends Controller
{
    public function boldWebhook(Request $request): never
    {
        $rawBody = (string) file_get_contents('php://input');
        $payload = json_decode($rawBody, true);
        if (!is_array($payload)) {
            $payload = $request->all();
        }

        $eventType = $this->extractBoldWebhookValue($payload, [
            'event',
            'type',
            'event_type',
            'notification_type',
        ]);
        $reference = $this->extractBoldWebhookValue($payload, [
            'external_reference',
            'reference',
            'payment_reference',
            'order_reference',
            'merchant_reference',
        ]);
        $statusRaw = $this->extractBoldWebhookValue($payload, [
            'status',
            'state',
            'payment_status',
            'transaction_status',
        ]);
        if ($statusRaw === '') {
            $statusRaw = $eventType;
        }
        $status = $this->normalizeBoldWebhookStatus($statusRaw);
        $remoteIp = (string) $request->server('REMOTE_ADDR', '');

        $result = [
            'matched' => false,
            'updated' => false,
            'reason' => 'sin_referencia',
            'empresa_id' => null,
            'venta_id' => null,
            'estado_venta' => null,
            'pagado' => null,
            'total' => null,
        ];

        if ($reference !== '') {
            $result = $this->applyBoldWebhookToSale($reference, $status);
        }

        Logger::info('Webhook Bold recibido', [
            'event' => $eventType,
            'reference' => $reference,
            'status_raw' => $statusRaw,
            'status_normalized' => $status,
            'matched' => (bool) ($result['matched'] ?? false),
            'updated' => (bool) ($result['updated'] ?? false),
            'reason' => (string) ($result['reason'] ?? ''),
            'empresa_id' => $result['empresa_id'] ?? null,
            'venta_id' => $result['venta_id'] ?? null,
            'estado_venta' => $result['estado_venta'] ?? null,
            'pagado' => $result['pagado'] ?? null,
            'total' => $result['total'] ?? null,
            'remote_ip' => $remoteIp,
        ]);

        Response::json([
            'ok' => true,
            'message' => 'Webhook Bold recibido y procesado.',
            'result' => $result,
        ]);
    }

    public function credibancoWebhook(Request $request): never
    {
        $rawBody = (string) file_get_contents('php://input');
        $payload = json_decode($rawBody, true);
        if (!is_array($payload)) {
            $payload = $request->all();
        }

        $eventType = $this->extractBoldWebhookValue($payload, [
            'event',
            'type',
            'event_type',
            'notification_type',
            'authorization_status',
        ]);
        $reference = $this->extractBoldWebhookValue($payload, [
            'reference',
            'payment_reference',
            'external_reference',
            'order_reference',
            'merchant_reference',
            'transaction_id',
            'id_transaccion',
            'authorization_code',
        ]);
        $statusRaw = $this->extractBoldWebhookValue($payload, [
            'status',
            'state',
            'payment_status',
            'transaction_status',
            'authorization_status',
        ]);
        if ($statusRaw === '') {
            $statusRaw = $eventType;
        }
        $status = $this->normalizeBoldWebhookStatus($statusRaw);
        $remoteIp = (string) $request->server('REMOTE_ADDR', '');

        $result = [
            'matched' => false,
            'updated' => false,
            'reason' => 'sin_referencia',
            'empresa_id' => null,
            'venta_id' => null,
            'estado_venta' => null,
            'pagado' => null,
            'total' => null,
        ];

        if ($reference !== '') {
            $result = $this->applyGatewayWebhookToSale(
                $reference,
                $status,
                $this->credibancoPaymentCodes()
            );
        }

        Logger::info('Webhook Credibanco recibido', [
            'event' => $eventType,
            'reference' => $reference,
            'status_raw' => $statusRaw,
            'status_normalized' => $status,
            'matched' => (bool) ($result['matched'] ?? false),
            'updated' => (bool) ($result['updated'] ?? false),
            'reason' => (string) ($result['reason'] ?? ''),
            'empresa_id' => $result['empresa_id'] ?? null,
            'venta_id' => $result['venta_id'] ?? null,
            'estado_venta' => $result['estado_venta'] ?? null,
            'pagado' => $result['pagado'] ?? null,
            'total' => $result['total'] ?? null,
            'remote_ip' => $remoteIp,
        ]);

        Response::json([
            'ok' => true,
            'message' => 'Webhook Credibanco recibido y procesado.',
            'result' => $result,
        ]);
    }

    public function index(Request $request): void
    {
        $companyId = (int) (Tenant::companyId() ?? 0);
        if ($companyId <= 0) {
            flash('error', 'Debes seleccionar una empresa para gestionar integraciones.');
            redirect('empresas/seleccionar');
        }

        $bold = $this->loadBoldSettings($companyId);
        $paymentMethodModel = new PaymentMethod();
        $boldDataphoneMethod = $paymentMethodModel->findCompanyByCode($companyId, 'bold_dataphone');
        if ($boldDataphoneMethod === null) {
            // Compatibilidad con configuraciones antiguas.
            $boldDataphoneMethod = $paymentMethodModel->findCompanyByCode($companyId, 'bold');
        }
        $boldButtonMethod = $paymentMethodModel->findCompanyByCode($companyId, 'bold_button');
        $boldDataphoneMethodActive = $boldDataphoneMethod !== null && (int) ($boldDataphoneMethod['activo'] ?? 0) === 1;
        $boldButtonMethodActive = $boldButtonMethod !== null && (int) ($boldButtonMethod['activo'] ?? 0) === 1;
        $mode = $this->normalizeBoldMode((string) ($bold['mode'] ?? 'dataphone'));
        $dataphoneConfigured = $this->isBoldDataphoneConfigured($bold);
        $buttonConfigured = $this->isBoldButtonConfigured($bold);
        $selectedConfigured = $mode === 'button' ? $buttonConfigured : $dataphoneConfigured;

        $credibanco = $this->loadCredibancoSettings($companyId);
        $credibancoConfigured = $this->isCredibancoConfigured($credibanco);
        $credibancoMethod = $paymentMethodModel->findCompanyByCode($companyId, 'credibanco_datafono');
        if ($credibancoMethod === null) {
            $credibancoMethod = $paymentMethodModel->findCompanyByCode($companyId, 'credibanco_dataphone');
        }
        if ($credibancoMethod === null) {
            $credibancoMethod = $paymentMethodModel->findCompanyByCode($companyId, 'credibanco');
        }
        $credibancoMethodActive = $credibancoMethod !== null && (int) ($credibancoMethod['activo'] ?? 0) === 1;

        $this->render('integraciones/index', [
            'title' => 'Integraciones',
            'bold' => [
                'enabled' => (bool) ($bold['enabled'] ?? false),
                'mode' => $mode,
                'api_url' => (string) ($bold['api_url'] ?? ''),
                'has_api_key' => trim((string) ($bold['api_key'] ?? '')) !== '',
                'api_key_preview' => $this->maskSecretPreview((string) ($bold['api_key'] ?? '')),
                'button_checkout_url' => (string) ($bold['button_checkout_url'] ?? ''),
                'has_button_public_key' => trim((string) ($bold['button_public_key'] ?? '')) !== '',
                'has_button_secret_key' => trim((string) ($bold['button_secret_key'] ?? '')) !== '',
                'button_public_key_preview' => $this->maskSecretPreview((string) ($bold['button_public_key'] ?? '')),
                'button_secret_key_preview' => $this->maskSecretPreview((string) ($bold['button_secret_key'] ?? '')),
                'is_configured' => $selectedConfigured,
                'is_dataphone_configured' => $dataphoneConfigured,
                'is_button_configured' => $buttonConfigured,
                'method_active' => $boldDataphoneMethodActive || $boldButtonMethodActive,
                'method_active_dataphone' => $boldDataphoneMethodActive,
                'method_active_button' => $boldButtonMethodActive,
                'last_status' => (string) ($bold['last_status'] ?? ''),
                'last_message' => (string) ($bold['last_message'] ?? ''),
                'last_at' => (string) ($bold['last_at'] ?? ''),
            ],
            'credibanco' => [
                'enabled' => (bool) ($credibanco['enabled'] ?? false),
                'api_url' => (string) ($credibanco['api_url'] ?? ''),
                'token_url' => (string) ($credibanco['token_url'] ?? ''),
                'has_client_id' => trim((string) ($credibanco['client_id'] ?? '')) !== '',
                'client_id_preview' => $this->maskSecretPreview((string) ($credibanco['client_id'] ?? '')),
                'has_client_secret' => trim((string) ($credibanco['client_secret'] ?? '')) !== '',
                'client_secret_preview' => $this->maskSecretPreview((string) ($credibanco['client_secret'] ?? '')),
                'has_events_secret' => trim((string) ($credibanco['events_secret'] ?? '')) !== '',
                'events_secret_preview' => $this->maskSecretPreview((string) ($credibanco['events_secret'] ?? '')),
                'is_configured' => $credibancoConfigured,
                'method_active' => $credibancoMethodActive,
                'webhook_url' => url('webhooks/credibanco'),
                'last_status' => (string) ($credibanco['last_status'] ?? ''),
                'last_message' => (string) ($credibanco['last_message'] ?? ''),
                'last_at' => (string) ($credibanco['last_at'] ?? ''),
            ],
        ]);

        // Evita que old_input de formularios previos reabra el modal al volver a entrar.
        Session::clearOldInput();
    }

    public function updateBold(Request $request): never
    {
        $companyId = (int) (Tenant::companyId() ?? 0);
        if ($companyId <= 0) {
            flash('error', 'Debes seleccionar una empresa para actualizar Integraciones.');
            redirect('empresas/seleccionar');
        }

        Session::setOldInput($request->all());

        $enabled = trim((string) $request->input('bold_enabled', '0')) === '1';
        $mode = $this->normalizeBoldMode((string) $request->input('bold_mode', 'dataphone'));
        $apiUrlInput = $this->normalizeBoldApiBaseUrl((string) $request->input('bold_api_url', ''));
        $apiKeyInput = trim((string) $request->input('bold_api_key', ''));
        $buttonCheckoutInput = rtrim(trim((string) $request->input('bold_button_checkout_url', '')), '/');
        $buttonPublicInput = trim((string) $request->input('bold_button_public_key', ''));
        $buttonSecretInput = trim((string) $request->input('bold_button_secret_key', ''));

        $current = $this->loadBoldSettings($companyId);
        $apiUrl = $apiUrlInput !== '' ? $apiUrlInput : $this->normalizeBoldApiBaseUrl((string) ($current['api_url'] ?? ''));
        $apiKey = $apiKeyInput !== '' ? $apiKeyInput : (string) ($current['api_key'] ?? '');
        $buttonCheckoutUrl = $buttonCheckoutInput !== '' ? $buttonCheckoutInput : (string) ($current['button_checkout_url'] ?? '');
        $buttonPublic = $buttonPublicInput !== '' ? $buttonPublicInput : (string) ($current['button_public_key'] ?? '');
        $buttonSecret = $buttonSecretInput !== '' ? $buttonSecretInput : (string) ($current['button_secret_key'] ?? '');

        if ($enabled) {
            if ($mode === 'button') {
                if ($buttonCheckoutUrl !== '' && (!filter_var($buttonCheckoutUrl, FILTER_VALIDATE_URL) || !preg_match('/^https?:\/\//i', $buttonCheckoutUrl))) {
                    flash('error', 'La URL de botón de pago Bold debe iniciar por http:// o https:// y tener formato válido.');
                    $this->redirectIntegrations(true, $mode);
                }

                if ($buttonPublic === '' || $buttonSecret === '') {
                    flash('error', 'Para Botón de pagos debes completar llave pública y llave secreta de Bold.');
                    $this->redirectIntegrations(true, $mode);
                }
            } else {
                if ($apiUrl === '') {
                    flash('error', 'La URL API de Bold es obligatoria cuando la integración está activa en modo API datáfono.');
                    $this->redirectIntegrations(true, $mode);
                }

                if (!filter_var($apiUrl, FILTER_VALIDATE_URL) || !preg_match('/^https?:\/\//i', $apiUrl)) {
                    flash('error', 'La URL API de Bold debe iniciar por http:// o https:// y tener formato válido.');
                    $this->redirectIntegrations(true, $mode);
                }

                if ($apiKey === '') {
                    flash('error', 'La API Key de Bold es obligatoria cuando la integración está activa en modo API datáfono.');
                    $this->redirectIntegrations(true, $mode);
                }
            }
        }

        (new CompanySetting())->upsertMany($companyId, [
            'bold_enabled' => $enabled ? '1' : '0',
            'bold_mode' => $mode,
            'bold_api_url' => $apiUrl,
            'bold_api_key' => $apiKey !== '' ? SecretCipherService::encrypt($apiKey) : '',
            'bold_button_checkout_url' => $buttonCheckoutUrl,
            'bold_button_public_key' => $buttonPublic !== '' ? SecretCipherService::encrypt($buttonPublic) : '',
            'bold_button_secret_key' => $buttonSecret !== '' ? SecretCipherService::encrypt($buttonSecret) : '',
        ]);

        $dataphoneReady = $this->isBoldDataphoneConfigured([
            'api_url' => $apiUrl,
            'api_key' => $apiKey,
        ]);
        $buttonReady = $this->isBoldButtonConfigured([
            'button_public_key' => $buttonPublic,
            'button_secret_key' => $buttonSecret,
        ]);

        $this->syncBoldPaymentMethods(
            $companyId,
            $enabled && $dataphoneReady,
            $enabled && $buttonReady
        );

        Session::clearOldInput();
        AuditService::track(
            'integraciones.bold.actualizar',
            'Integración Bold actualizada para empresa ID: '
            . $companyId
            . ' (estado: ' . ($enabled ? 'activa' : 'inactiva')
            . ', modo: ' . $mode . ')'
        );

        flash('success', 'Integración Bold guardada correctamente.');
        $this->redirectIntegrations(false);
    }

    public function updateCredibanco(Request $request): never
    {
        $companyId = (int) (Tenant::companyId() ?? 0);
        if ($companyId <= 0) {
            flash('error', 'Debes seleccionar una empresa para actualizar Integraciones.');
            redirect('empresas/seleccionar');
        }

        Session::setOldInput($request->all());

        $enabled = trim((string) $request->input('credibanco_enabled', '0')) === '1';
        $apiUrlInput = $this->normalizeCredibancoApiBaseUrl((string) $request->input('credibanco_api_url', ''));
        $tokenUrlInput = trim((string) $request->input('credibanco_token_url', ''));
        $clientIdInput = trim((string) $request->input('credibanco_client_id', ''));
        $clientSecretInput = trim((string) $request->input('credibanco_client_secret', ''));
        $eventsSecretInput = trim((string) $request->input('credibanco_events_secret', ''));

        $current = $this->loadCredibancoSettings($companyId);
        $apiUrl = $apiUrlInput !== '' ? $apiUrlInput : $this->normalizeCredibancoApiBaseUrl((string) ($current['api_url'] ?? ''));
        $tokenUrl = $tokenUrlInput !== '' ? rtrim($tokenUrlInput, '/') : rtrim((string) ($current['token_url'] ?? ''), '/');
        $clientId = $clientIdInput !== '' ? $clientIdInput : (string) ($current['client_id'] ?? '');
        $clientSecret = $clientSecretInput !== '' ? $clientSecretInput : (string) ($current['client_secret'] ?? '');
        $eventsSecret = $eventsSecretInput !== '' ? $eventsSecretInput : (string) ($current['events_secret'] ?? '');

        if ($enabled) {
            if ($apiUrl === '') {
                flash('error', 'La URL API de Credibanco es obligatoria cuando la integración está activa.');
                $this->redirectCredibancoIntegrations(true);
            }

            if (!filter_var($apiUrl, FILTER_VALIDATE_URL) || !preg_match('/^https?:\/\//i', $apiUrl)) {
                flash('error', 'La URL API de Credibanco debe iniciar por http:// o https:// y tener formato válido.');
                $this->redirectCredibancoIntegrations(true);
            }

            if ($tokenUrl !== '' && (!filter_var($tokenUrl, FILTER_VALIDATE_URL) || !preg_match('/^https?:\/\//i', $tokenUrl))) {
                flash('error', 'La URL SSO token de Credibanco debe iniciar por http:// o https:// y tener formato válido.');
                $this->redirectCredibancoIntegrations(true);
            }

            if ($clientId === '') {
                flash('error', 'El Client ID de Credibanco es obligatorio cuando la integración está activa.');
                $this->redirectCredibancoIntegrations(true);
            }

            if ($clientSecret === '') {
                flash('error', 'El Client Secret de Credibanco es obligatorio cuando la integración está activa.');
                $this->redirectCredibancoIntegrations(true);
            }
        }

        (new CompanySetting())->upsertMany($companyId, [
            'credibanco_enabled' => $enabled ? '1' : '0',
            'credibanco_api_url' => $apiUrl,
            'credibanco_token_url' => $tokenUrl,
            'credibanco_client_id' => $clientId !== '' ? SecretCipherService::encrypt($clientId) : '',
            'credibanco_client_secret' => $clientSecret !== '' ? SecretCipherService::encrypt($clientSecret) : '',
            // Compatibilidad con versiones previas que aún leen credibanco_api_key.
            'credibanco_api_key' => $clientSecret !== '' ? SecretCipherService::encrypt($clientSecret) : '',
            'credibanco_events_secret' => $eventsSecret !== '' ? SecretCipherService::encrypt($eventsSecret) : '',
        ]);

        $configured = $this->isCredibancoConfigured([
            'api_url' => $apiUrl,
            'client_id' => $clientId,
            'client_secret' => $clientSecret,
        ]);
        $this->syncCredibancoPaymentMethods($companyId, $enabled && $configured);

        Session::clearOldInput();
        AuditService::track(
            'integraciones.credibanco.actualizar',
            'Integración Credibanco actualizada para empresa ID: '
            . $companyId
            . ' (estado: ' . ($enabled ? 'activa' : 'inactiva') . ')'
        );

        flash('success', 'Integración Credibanco guardada correctamente.');
        $this->redirectCredibancoIntegrations(false);
    }

    public function testCredibancoTransaction(Request $request): never
    {
        $companyId = (int) (Tenant::companyId() ?? 0);
        if ($companyId <= 0) {
            flash('error', 'Debes seleccionar una empresa para probar Integraciones.');
            redirect('empresas/seleccionar');
        }

        Session::setOldInput($request->all());
        $resolved = $this->resolveCredibancoRuntimeSettings($companyId);
        if (!(bool) ($resolved['ok'] ?? false)) {
            flash('error', (string) ($resolved['message'] ?? 'No fue posible validar la configuración de Credibanco.'));
            $this->redirectCredibancoIntegrations(true);
        }

        $merchantCode = trim((string) $request->input('credibanco_test_merchant_code', '010601557'));
        $startAt = trim((string) $request->input('credibanco_test_start_at', '2025-04-24 00:00:00'));
        $endAt = trim((string) $request->input('credibanco_test_end_at', '2025-04-24 23:59:59'));
        $rowsPerPage = max(1, min(500, (int) $request->input('credibanco_test_rows_per_page', 10)));
        $page = max(1, (int) $request->input('credibanco_test_page', 1));
        $terminal = trim((string) $request->input('credibanco_test_terminal', ''));
        $authorizationCode = trim((string) $request->input('credibanco_test_authorization_code', ''));
        $reference = trim((string) $request->input('credibanco_test_reference', ''));
        $manualToken = trim((string) $request->input('credibanco_test_access_token', ''));

        $filters = [
            'valorCodigoComercio' => $merchantCode,
            'fechaInicio' => $startAt,
            'fechaFin' => $endAt,
            'filasPorPagina' => $rowsPerPage,
            'pagina' => $page,
            'valorNumericoTerminal' => $terminal,
            'codAutorizacion' => $authorizationCode,
            'referencia' => $reference,
        ];

        $service = new CredibancoService();
        if ($manualToken !== '') {
            $query = $service->queryTransactions(
                is_array($resolved['data'] ?? null) ? $resolved['data'] : [],
                $manualToken,
                $filters
            );
            if ((bool) ($query['ok'] ?? false)) {
                $probe = [
                    'ok' => true,
                    'message' => 'Consulta de transacciones Credibanco validada correctamente con token manual.',
                    'rows_count' => (int) ($query['rows_count'] ?? 0),
                    'token_expires_in' => 0,
                    'data' => is_array($query['data'] ?? null) ? $query['data'] : [],
                ];
            } else {
                $probe = $query;
            }
        } else {
            $probe = $service->probeTokenAndTransaction(
                is_array($resolved['data'] ?? null) ? $resolved['data'] : [],
                $filters
            );
        }

        $status = (bool) ($probe['ok'] ?? false) ? 'ok' : 'error';
        $message = trim((string) ($probe['message'] ?? ''));
        (new CompanySetting())->upsertMany($companyId, [
            'credibanco_last_validation_status' => $status,
            'credibanco_last_validation_message' => $message,
            'credibanco_last_validation_at' => date('Y-m-d H:i:s'),
        ]);

        if ((bool) ($probe['ok'] ?? false)) {
            Session::clearOldInput();

            $rowsCount = max(0, (int) ($probe['rows_count'] ?? 0));
            $tokenExpiresIn = max(0, (int) ($probe['token_expires_in'] ?? 0));

            flash(
                'success',
                'Credibanco validado correctamente. '
                . 'Token SSO generado'
                . ($tokenExpiresIn > 0 ? ' (vigencia aprox: ' . $tokenExpiresIn . 's)' : '')
                . ' y consulta de transacciones ejecutada '
                . '(registros detectados: ' . $rowsCount . ').'
            );

            AuditService::track(
                'integraciones.credibanco.probar',
                'Prueba token/consulta Credibanco exitosa para empresa ID: '
                . $companyId
                . ' (CU: ' . $merchantCode . ')'
            );
            $this->redirectCredibancoIntegrations(true);
        }

        flash('error', 'La validación de Credibanco falló: ' . ($message !== '' ? $message : 'Error desconocido.'));
        AuditService::track(
            'integraciones.credibanco.probar',
            'Prueba token/consulta Credibanco fallida para empresa ID: '
            . $companyId
            . ' (CU: ' . $merchantCode . ')'
        );
        $this->redirectCredibancoIntegrations(true);
    }

    public function testBoldTransaction(Request $request): never
    {
        $companyId = (int) (Tenant::companyId() ?? 0);
        if ($companyId <= 0) {
            flash('error', 'Debes seleccionar una empresa para probar Integraciones.');
            redirect('empresas/seleccionar');
        }

        Session::setOldInput($request->all());
        $resolved = $this->resolveBoldRuntimeSettings($companyId);
        if (!(bool) ($resolved['ok'] ?? false)) {
            flash('error', (string) ($resolved['message'] ?? 'No fue posible validar la configuración de Bold.'));
            $this->redirectIntegrations(true, 'dataphone');
        }

        $probe = (new BoldService())->probeDataphoneConnection(
            is_array($resolved['data'] ?? null) ? $resolved['data'] : []
        );

        $status = (bool) ($probe['ok'] ?? false) ? 'ok' : 'error';
        $message = trim((string) ($probe['message'] ?? ''));
        (new CompanySetting())->upsertMany($companyId, [
            'bold_last_validation_status' => $status,
            'bold_last_validation_message' => $message,
            'bold_last_validation_at' => date('Y-m-d H:i:s'),
        ]);

        if ((bool) ($probe['ok'] ?? false)) {
            Session::clearOldInput();
            $methodsCount = max(0, (int) ($probe['methods_count'] ?? 0));
            $terminalsCount = max(0, (int) ($probe['terminals_count'] ?? 0));
            $terminalsMissing = (bool) ($probe['terminals_missing'] ?? false);
            if ($terminalsMissing) {
                flash(
                    'success',
                    'Conexión API datáfono validada. '
                    . 'Métodos: ' . $methodsCount . ' · Terminales vinculadas: ' . $terminalsCount . '. '
                    . 'Aún no hay terminales disponibles en Bold; vincula al menos una para operar pagos por datáfono.'
                );
            } else {
                flash(
                    'success',
                    'Conexión API datáfono validada correctamente. '
                    . 'Métodos: ' . $methodsCount . ' · Terminales: ' . $terminalsCount . '.'
                );
            }
            AuditService::track(
                'integraciones.bold.probar',
                'Prueba API datáfono Bold exitosa para empresa ID: ' . $companyId
            );
            $this->redirectIntegrations(true, 'dataphone');
        }

        flash('error', 'La validación con Bold falló: ' . ($message !== '' ? $message : 'Error desconocido.'));
        AuditService::track(
            'integraciones.bold.probar',
            'Prueba API datáfono Bold fallida para empresa ID: ' . $companyId
        );
        $this->redirectIntegrations(true, 'dataphone');
    }

    public function testBoldButtonCheckout(Request $request): never
    {
        $companyId = (int) (Tenant::companyId() ?? 0);
        if ($companyId <= 0) {
            flash('error', 'Debes seleccionar una empresa para probar Integraciones.');
            redirect('empresas/seleccionar');
        }

        $oldInput = $request->all();
        $oldInput['bold_mode'] = 'button';
        Session::setOldInput($oldInput);

        $amountTypeInput = strtolower(trim((string) $request->input('bold_button_test_amount_type', 'close')));
        $amountType = in_array($amountTypeInput, ['open', 'close'], true) ? strtoupper($amountTypeInput) : 'CLOSE';

        $currency = strtoupper(trim((string) $request->input('bold_button_test_currency', 'COP')));
        if ($currency === '') {
            $currency = 'COP';
        }

        $reference = trim((string) $request->input('bold_button_test_reference', ''));
        if ($reference === '') {
            $reference = 'VX-' . date('YmdHis') . '-' . random_int(1000, 9999);
        }

        $description = trim((string) $request->input('bold_button_test_description', ''));
        if ($description === '') {
            $description = 'Pago de prueba Veltrix POS';
        }

        $amountRaw = max(0, (float) $request->input('bold_button_test_amount', 0));
        $amountTotal = (int) round($amountRaw, 0);
        if ($amountType === 'CLOSE' && $amountTotal <= 0) {
            flash('error', 'Para monto cerrado debes ingresar un valor mayor a 0.');
            $this->redirectIntegrations(true, 'button');
        }

        $resolved = $this->resolveBoldButtonRuntimeSettings($companyId);
        if (!(bool) ($resolved['ok'] ?? false)) {
            flash('error', (string) ($resolved['message'] ?? 'No fue posible validar la configuración de Botón de pagos Bold.'));
            $this->redirectIntegrations(true, 'button');
        }

        $payload = [
            'amount_type' => $amountType,
            'reference' => $reference,
            'description' => $description,
            'currency' => $currency,
            'total_amount' => $amountTotal,
        ];

        $probe = (new BoldService())->createPaymentLink(
            is_array($resolved['data'] ?? null) ? $resolved['data'] : [],
            $payload
        );

        $status = (bool) ($probe['ok'] ?? false) ? 'ok' : 'error';
        $message = trim((string) ($probe['message'] ?? ''));
        (new CompanySetting())->upsertMany($companyId, [
            'bold_last_validation_status' => $status,
            'bold_last_validation_message' => $message,
            'bold_last_validation_at' => date('Y-m-d H:i:s'),
        ]);

        if ((bool) ($probe['ok'] ?? false)) {
            $checkoutUrl = trim((string) ($probe['url'] ?? ''));
            $paymentLink = trim((string) ($probe['payment_link'] ?? ''));
            $displayReference = trim((string) ($probe['reference'] ?? $reference));

            Session::setOldInput(array_merge($oldInput, [
                'bold_mode' => 'button',
                'bold_button_test_reference' => $displayReference,
                'bold_button_generated_url' => $checkoutUrl,
                'bold_button_generated_payment_link' => $paymentLink,
            ]));

            flash(
                'success',
                'Link de pago Bold creado correctamente'
                . ($displayReference !== '' ? ' (ref: ' . $displayReference . ')' : '')
                . '.'
            );

            if ($checkoutUrl !== '') {
                Session::flash('success_actions', [
                    [
                        'label' => 'Abrir checkout de prueba',
                        'url' => $checkoutUrl,
                        'target' => '_blank',
                    ],
                ]);
            }

            AuditService::track(
                'integraciones.bold.probar_boton',
                'Prueba Botón de pagos Bold exitosa para empresa ID: '
                . $companyId
                . ' (ref: ' . $displayReference . ')'
            );

            $this->redirectIntegrations(true, 'button');
        }

        flash('error', 'No se pudo crear el link de pago Bold: ' . ($message !== '' ? $message : 'Error desconocido.'));
        AuditService::track(
            'integraciones.bold.probar_boton',
            'Prueba Botón de pagos Bold fallida para empresa ID: '
            . $companyId
            . ' (ref: ' . $reference . ')'
        );
        $this->redirectIntegrations(true, 'button');
    }

    private function resolveBoldRuntimeSettings(int $companyId): array
    {
        $settings = $this->loadBoldSettings($companyId);
        if (!(bool) ($settings['enabled'] ?? false)) {
            return [
                'ok' => false,
                'message' => 'La integración Bold está inactiva. Actívala antes de probar.',
            ];
        }

        $apiUrl = trim((string) ($settings['api_url'] ?? ''));
        $apiKey = trim((string) ($settings['api_key'] ?? ''));

        if ($apiUrl === '' || $apiKey === '') {
            return [
                'ok' => false,
                'message' => 'Completa URL API y API Key de Bold antes de probar la transacción.',
            ];
        }

        if (!filter_var($apiUrl, FILTER_VALIDATE_URL) || !preg_match('/^https?:\/\//i', $apiUrl)) {
            return [
                'ok' => false,
                'message' => 'La URL API de Bold no es válida.',
            ];
        }

        return [
            'ok' => true,
            'data' => [
                'api_url' => $this->normalizeBoldApiBaseUrl($apiUrl),
                'api_key' => $apiKey,
            ],
        ];
    }

    private function resolveBoldButtonRuntimeSettings(int $companyId): array
    {
        $settings = $this->loadBoldSettings($companyId);
        if (!(bool) ($settings['enabled'] ?? false)) {
            return [
                'ok' => false,
                'message' => 'La integración Bold está inactiva. Actívala antes de probar.',
            ];
        }

        $publicKey = trim((string) ($settings['button_public_key'] ?? ''));
        $secretKey = trim((string) ($settings['button_secret_key'] ?? ''));
        if ($publicKey === '' || $secretKey === '') {
            return [
                'ok' => false,
                'message' => 'Completa llave pública y llave secreta de Botón de pagos antes de crear un link.',
            ];
        }

        return [
            'ok' => true,
            'data' => [
                'button_public_key' => $publicKey,
                'button_secret_key' => $secretKey,
            ],
        ];
    }

    private function resolveCredibancoRuntimeSettings(int $companyId): array
    {
        $settings = $this->loadCredibancoSettings($companyId);
        if (!(bool) ($settings['enabled'] ?? false)) {
            return [
                'ok' => false,
                'message' => 'La integración Credibanco está inactiva. Actívala antes de probar.',
            ];
        }

        $apiUrl = trim((string) ($settings['api_url'] ?? ''));
        $clientId = trim((string) ($settings['client_id'] ?? ''));
        $clientSecret = trim((string) ($settings['client_secret'] ?? ''));

        if ($apiUrl === '' || $clientId === '' || $clientSecret === '') {
            return [
                'ok' => false,
                'message' => 'Completa URL API, Client ID y Client Secret de Credibanco antes de probar.',
            ];
        }

        if (!filter_var($apiUrl, FILTER_VALIDATE_URL) || !preg_match('/^https?:\/\//i', $apiUrl)) {
            return [
                'ok' => false,
                'message' => 'La URL API de Credibanco no es válida.',
            ];
        }

        return [
            'ok' => true,
            'data' => [
                'api_url' => $this->normalizeCredibancoApiBaseUrl($apiUrl),
                'token_url' => rtrim(trim((string) ($settings['token_url'] ?? '')), '/'),
                'client_id' => $clientId,
                'client_secret' => $clientSecret,
            ],
        ];
    }

    private function loadBoldSettings(int $companyId): array
    {
        $raw = (new CompanySetting())->getMany($companyId, [
            'bold_enabled',
            'bold_mode',
            'bold_api_url',
            'bold_api_key',
            'bold_button_checkout_url',
            'bold_button_public_key',
            'bold_button_secret_key',
            'bold_last_validation_status',
            'bold_last_validation_message',
            'bold_last_validation_at',
        ]);

        $storedApiKey = trim((string) ($raw['bold_api_key'] ?? ''));
        if ($storedApiKey !== '' && !SecretCipherService::isEncrypted($storedApiKey)) {
            $encrypted = SecretCipherService::encrypt($storedApiKey);
            if ($encrypted !== '' && $encrypted !== $storedApiKey) {
                (new CompanySetting())->upsertMany($companyId, [
                    'bold_api_key' => $encrypted,
                ]);
                $storedApiKey = $encrypted;
            }
        }

        $storedButtonPublic = trim((string) ($raw['bold_button_public_key'] ?? ''));
        if ($storedButtonPublic !== '' && !SecretCipherService::isEncrypted($storedButtonPublic)) {
            $encrypted = SecretCipherService::encrypt($storedButtonPublic);
            if ($encrypted !== '' && $encrypted !== $storedButtonPublic) {
                (new CompanySetting())->upsertMany($companyId, [
                    'bold_button_public_key' => $encrypted,
                ]);
                $storedButtonPublic = $encrypted;
            }
        }

        $storedButtonSecret = trim((string) ($raw['bold_button_secret_key'] ?? ''));
        if ($storedButtonSecret !== '' && !SecretCipherService::isEncrypted($storedButtonSecret)) {
            $encrypted = SecretCipherService::encrypt($storedButtonSecret);
            if ($encrypted !== '' && $encrypted !== $storedButtonSecret) {
                (new CompanySetting())->upsertMany($companyId, [
                    'bold_button_secret_key' => $encrypted,
                ]);
                $storedButtonSecret = $encrypted;
            }
        }

        return [
            'enabled' => trim((string) ($raw['bold_enabled'] ?? '0')) === '1',
            'mode' => $this->normalizeBoldMode((string) ($raw['bold_mode'] ?? 'dataphone')),
            'api_url' => $this->normalizeBoldApiBaseUrl((string) ($raw['bold_api_url'] ?? '')),
            'api_key' => SecretCipherService::decrypt($storedApiKey),
            'button_checkout_url' => rtrim(trim((string) ($raw['bold_button_checkout_url'] ?? '')), '/'),
            'button_public_key' => SecretCipherService::decrypt($storedButtonPublic),
            'button_secret_key' => SecretCipherService::decrypt($storedButtonSecret),
            'last_status' => trim((string) ($raw['bold_last_validation_status'] ?? '')),
            'last_message' => trim((string) ($raw['bold_last_validation_message'] ?? '')),
            'last_at' => trim((string) ($raw['bold_last_validation_at'] ?? '')),
        ];
    }

    private function loadCredibancoSettings(int $companyId): array
    {
        $raw = (new CompanySetting())->getMany($companyId, [
            'credibanco_enabled',
            'credibanco_api_url',
            'credibanco_token_url',
            'credibanco_client_id',
            'credibanco_client_secret',
            'credibanco_api_key',
            'credibanco_events_secret',
            'credibanco_last_validation_status',
            'credibanco_last_validation_message',
            'credibanco_last_validation_at',
        ]);

        $updates = [];

        $storedClientId = trim((string) ($raw['credibanco_client_id'] ?? ''));
        if ($storedClientId !== '' && !SecretCipherService::isEncrypted($storedClientId)) {
            $encrypted = SecretCipherService::encrypt($storedClientId);
            if ($encrypted !== '' && $encrypted !== $storedClientId) {
                $updates['credibanco_client_id'] = $encrypted;
                $storedClientId = $encrypted;
            }
        }

        $storedClientSecret = trim((string) ($raw['credibanco_client_secret'] ?? ''));
        if ($storedClientSecret !== '' && !SecretCipherService::isEncrypted($storedClientSecret)) {
            $encrypted = SecretCipherService::encrypt($storedClientSecret);
            if ($encrypted !== '' && $encrypted !== $storedClientSecret) {
                $updates['credibanco_client_secret'] = $encrypted;
                $storedClientSecret = $encrypted;
            }
        }

        $storedLegacyApiKey = trim((string) ($raw['credibanco_api_key'] ?? ''));
        if ($storedLegacyApiKey !== '' && !SecretCipherService::isEncrypted($storedLegacyApiKey)) {
            $encrypted = SecretCipherService::encrypt($storedLegacyApiKey);
            if ($encrypted !== '' && $encrypted !== $storedLegacyApiKey) {
                $updates['credibanco_api_key'] = $encrypted;
                $storedLegacyApiKey = $encrypted;
            }
        }

        if ($storedClientSecret === '' && $storedLegacyApiKey !== '') {
            $storedClientSecret = $storedLegacyApiKey;
            $updates['credibanco_client_secret'] = $storedLegacyApiKey;
        }

        $storedEventsSecret = trim((string) ($raw['credibanco_events_secret'] ?? ''));
        if ($storedEventsSecret !== '' && !SecretCipherService::isEncrypted($storedEventsSecret)) {
            $encrypted = SecretCipherService::encrypt($storedEventsSecret);
            if ($encrypted !== '' && $encrypted !== $storedEventsSecret) {
                $updates['credibanco_events_secret'] = $encrypted;
                $storedEventsSecret = $encrypted;
            }
        }

        if ($updates !== []) {
            (new CompanySetting())->upsertMany($companyId, $updates);
        }

        $clientSecret = SecretCipherService::decrypt($storedClientSecret);
        if ($clientSecret === '' && $storedLegacyApiKey !== '') {
            $clientSecret = SecretCipherService::decrypt($storedLegacyApiKey);
        }

        return [
            'enabled' => trim((string) ($raw['credibanco_enabled'] ?? '0')) === '1',
            'api_url' => $this->normalizeCredibancoApiBaseUrl((string) ($raw['credibanco_api_url'] ?? '')),
            'token_url' => rtrim(trim((string) ($raw['credibanco_token_url'] ?? '')), '/'),
            'client_id' => SecretCipherService::decrypt($storedClientId),
            'client_secret' => $clientSecret,
            // Alias para compatibilidad interna con despliegues anteriores.
            'api_key' => $clientSecret,
            'events_secret' => SecretCipherService::decrypt($storedEventsSecret),
            'last_status' => trim((string) ($raw['credibanco_last_validation_status'] ?? '')),
            'last_message' => trim((string) ($raw['credibanco_last_validation_message'] ?? '')),
            'last_at' => trim((string) ($raw['credibanco_last_validation_at'] ?? '')),
        ];
    }

    private function normalizeBoldMode(string $mode): string
    {
        $mode = strtolower(trim($mode));
        return in_array($mode, ['dataphone', 'button'], true) ? $mode : 'dataphone';
    }

    private function normalizeBoldApiBaseUrl(string $url): string
    {
        $url = trim($url);
        if ($url === '') {
            return '';
        }

        $url = rtrim($url, '/');
        $parts = parse_url($url);
        if (!is_array($parts)) {
            return $url;
        }

        $scheme = strtolower((string) ($parts['scheme'] ?? ''));
        $host = strtolower((string) ($parts['host'] ?? ''));
        if ($scheme === '' || $host === '') {
            return $url;
        }

        $normalized = $scheme . '://' . $host;
        if (isset($parts['port']) && (int) $parts['port'] > 0) {
            $normalized .= ':' . (int) $parts['port'];
        }

        return $normalized;
    }

    private function normalizeCredibancoApiBaseUrl(string $url): string
    {
        $url = trim($url);
        if ($url === '') {
            return '';
        }

        return rtrim($url, '/');
    }

    private function isBoldDataphoneConfigured(array $settings): bool
    {
        $apiUrl = trim((string) ($settings['api_url'] ?? ''));
        $apiKey = trim((string) ($settings['api_key'] ?? ''));
        if ($apiUrl === '' || $apiKey === '') {
            return false;
        }

        return (bool) (filter_var($apiUrl, FILTER_VALIDATE_URL) && preg_match('/^https?:\/\//i', $apiUrl));
    }

    private function isBoldButtonConfigured(array $settings): bool
    {
        $public = trim((string) ($settings['button_public_key'] ?? ''));
        $secret = trim((string) ($settings['button_secret_key'] ?? ''));
        return $public !== '' && $secret !== '';
    }

    private function isCredibancoConfigured(array $settings): bool
    {
        $apiUrl = trim((string) ($settings['api_url'] ?? ''));
        $clientId = trim((string) ($settings['client_id'] ?? ''));
        $clientSecret = trim((string) ($settings['client_secret'] ?? ''));
        if ($clientSecret === '') {
            $clientSecret = trim((string) ($settings['api_key'] ?? ''));
        }

        if ($apiUrl === '' || $clientId === '' || $clientSecret === '') {
            return false;
        }

        return (bool) (filter_var($apiUrl, FILTER_VALIDATE_URL) && preg_match('/^https?:\/\//i', $apiUrl));
    }

    private function syncBoldPaymentMethods(int $companyId, bool $dataphoneActive, bool $buttonActive): void
    {
        $this->syncPaymentMethodByCode($companyId, 'bold_dataphone', 'Bold Datáfono', $dataphoneActive);
        $this->syncPaymentMethodByCode($companyId, 'bold_button', 'Bold Botón', $buttonActive);

        // Mantener legado oculto para no duplicar métodos en POS.
        $this->syncPaymentMethodByCode($companyId, 'bold', 'Bold', false);
    }

    private function syncCredibancoPaymentMethods(int $companyId, bool $active): void
    {
        $this->syncPaymentMethodByCode($companyId, 'credibanco_datafono', 'Credibanco Datáfono', $active);

        // Alias legacy para evitar duplicados visuales.
        $this->syncPaymentMethodByCode($companyId, 'credibanco_dataphone', 'Credibanco Dataphone', false);
        $this->syncPaymentMethodByCode($companyId, 'credibanco', 'Credibanco', false);
    }

    private function syncPaymentMethodByCode(int $companyId, string $code, string $name, bool $active): void
    {
        $pdo = Database::connection();

        $existingStmt = $pdo->prepare(
            'SELECT id
             FROM metodos_pago
             WHERE empresa_id = :empresa_id
               AND codigo = :codigo
             ORDER BY id DESC'
        );
        $existingStmt->execute([
            ':empresa_id' => $companyId,
            ':codigo' => $code,
        ]);
        $existing = $existingStmt->fetchAll() ?: [];

        if ($existing === []) {
            $insertStmt = $pdo->prepare(
                'INSERT INTO metodos_pago (
                    empresa_id, nombre, codigo, activo, created_at
                ) VALUES (
                    :empresa_id, :nombre, :codigo, :activo, NOW()
                )'
            );
            $insertStmt->execute([
                ':empresa_id' => $companyId,
                ':nombre' => $name,
                ':codigo' => $code,
                ':activo' => $active ? 1 : 0,
            ]);
            return;
        }

        $updateStmt = $pdo->prepare(
            'UPDATE metodos_pago
             SET nombre = :nombre,
                 activo = :activo
             WHERE empresa_id = :empresa_id
               AND codigo = :codigo'
        );
        $updateStmt->execute([
            ':nombre' => $name,
            ':activo' => $active ? 1 : 0,
            ':empresa_id' => $companyId,
            ':codigo' => $code,
        ]);
    }

    private function maskSecretPreview(string $secret): string
    {
        $secret = trim($secret);
        if ($secret === '') {
            return '';
        }

        $length = strlen($secret);
        if ($length <= 8) {
            return str_repeat('*', max(6, $length));
        }

        if (preg_match('/^[a-z]{3}_/i', $secret) === 1) {
            $prefix = substr($secret, 0, 4);
            $suffix = substr($secret, -4);
            $maskLength = max(8, $length - 8);
            return $prefix . str_repeat('*', $maskLength) . $suffix;
        }

        $prefix = substr($secret, 0, 4);
        $suffix = substr($secret, -4);
        $maskLength = max(6, $length - 8);
        return $prefix . str_repeat('*', $maskLength) . $suffix;
    }

    private function extractBoldWebhookValue(array $payload, array $keys): string
    {
        $bags = [$payload];
        foreach (['data', 'payment', 'transaction', 'payload', 'result', 'notification'] as $bagKey) {
            if (isset($payload[$bagKey]) && is_array($payload[$bagKey])) {
                $bags[] = $payload[$bagKey];
            }
        }

        foreach ($bags as $bag) {
            foreach ($keys as $key) {
                $value = trim((string) ($bag[$key] ?? ''));
                if ($value !== '') {
                    return $value;
                }
            }
        }

        return '';
    }

    private function normalizeBoldWebhookStatus(string $status): string
    {
        $status = strtolower(trim($status));
        if ($status === '') {
            return 'desconocido';
        }

        if (
            str_contains($status, 'approv')
            || str_contains($status, 'aprob')
            || str_contains($status, 'paid')
            || str_contains($status, 'success')
            || str_contains($status, 'captur')
            || str_contains($status, 'complet')
            || str_contains($status, 'authoriz')
        ) {
            return 'approved';
        }

        if (
            str_contains($status, 'reject')
            || str_contains($status, 'declin')
            || str_contains($status, 'fail')
            || str_contains($status, 'error')
            || str_contains($status, 'cancel')
            || str_contains($status, 'void')
        ) {
            return 'rejected';
        }

        if (
            str_contains($status, 'pend')
            || str_contains($status, 'process')
            || str_contains($status, 'wait')
            || str_contains($status, 'review')
            || str_contains($status, 'init')
            || str_contains($status, 'hold')
        ) {
            return 'pending';
        }

        return $status;
    }

    private function applyBoldWebhookToSale(string $reference, string $status): array
    {
        return $this->applyGatewayWebhookToSale($reference, $status, [
            'bold',
            'bold_dataphone',
            'bold_button',
        ]);
    }

    private function credibancoPaymentCodes(): array
    {
        return [
            'credibanco',
            'credibanco_dataphone',
            'credibanco_datafono',
        ];
    }

    private function applyGatewayWebhookToSale(string $reference, string $status, array $paymentCodes): array
    {
        $paymentCodes = $this->normalizePaymentCodes($paymentCodes);
        if ($paymentCodes === []) {
            return [
                'matched' => false,
                'updated' => false,
                'reason' => 'metodo_pago_no_configurado',
                'empresa_id' => null,
                'venta_id' => null,
                'estado_venta' => null,
                'pagado' => null,
                'total' => null,
            ];
        }

        return Database::transaction(function () use ($reference, $status, $paymentCodes): array {
            $pdo = Database::connection();

            $methodPlaceholders = [];
            $queryParams = [':referencia' => $reference];
            foreach (array_values($paymentCodes) as $index => $code) {
                $placeholder = ':codigo_' . $index;
                $methodPlaceholders[] = $placeholder;
                $queryParams[$placeholder] = $code;
            }

            $paymentStmt = $pdo->prepare(
                'SELECT
                    vp.id AS pago_id,
                    vp.venta_id,
                    vp.empresa_id,
                    vp.metodo_pago,
                    vp.referencia,
                    v.total,
                    v.estado AS estado_venta
                 FROM venta_pagos vp
                 INNER JOIN ventas v
                    ON v.id = vp.venta_id
                   AND v.empresa_id = vp.empresa_id
                 WHERE vp.referencia = :referencia
                   AND LOWER(vp.metodo_pago) IN (' . implode(', ', $methodPlaceholders) . ')
                 ORDER BY vp.id DESC
                 FOR UPDATE'
            );
            $paymentStmt->execute($queryParams);
            $rows = $paymentStmt->fetchAll() ?: [];

            if ($rows === []) {
                return [
                    'matched' => false,
                    'updated' => false,
                    'reason' => 'referencia_no_encontrada',
                    'empresa_id' => null,
                    'venta_id' => null,
                    'estado_venta' => null,
                    'pagado' => null,
                    'total' => null,
                ];
            }

            $bySale = [];
            foreach ($rows as $row) {
                $saleId = (int) ($row['venta_id'] ?? 0);
                $companyId = (int) ($row['empresa_id'] ?? 0);
                if ($saleId <= 0 || $companyId <= 0) {
                    continue;
                }

                $saleKey = $companyId . ':' . $saleId;
                if (!isset($bySale[$saleKey])) {
                    $bySale[$saleKey] = $row;
                }
            }

            if ($bySale === []) {
                return [
                    'matched' => false,
                    'updated' => false,
                    'reason' => 'referencia_invalida',
                    'empresa_id' => null,
                    'venta_id' => null,
                    'estado_venta' => null,
                    'pagado' => null,
                    'total' => null,
                ];
            }

            if (count($bySale) > 1) {
                return [
                    'matched' => false,
                    'updated' => false,
                    'reason' => 'referencia_ambigua',
                    'empresa_id' => null,
                    'venta_id' => null,
                    'estado_venta' => null,
                    'pagado' => null,
                    'total' => null,
                ];
            }

            $row = array_values($bySale)[0];
            $saleId = (int) ($row['venta_id'] ?? 0);
            $companyId = (int) ($row['empresa_id'] ?? 0);
            $saleTotal = (float) ($row['total'] ?? 0);
            $saleStatus = trim((string) ($row['estado_venta'] ?? ''));

            $sumStmt = $pdo->prepare(
                'SELECT COALESCE(SUM(monto), 0)
                 FROM venta_pagos
                 WHERE venta_id = :venta_id
                   AND empresa_id = :empresa_id'
            );
            $sumStmt->execute([
                ':venta_id' => $saleId,
                ':empresa_id' => $companyId,
            ]);
            $paidAmount = (float) $sumStmt->fetchColumn();

            if ($status !== 'approved') {
                return [
                    'matched' => true,
                    'updated' => false,
                    'reason' => 'estado_no_aprobado',
                    'empresa_id' => $companyId,
                    'venta_id' => $saleId,
                    'estado_venta' => $saleStatus,
                    'pagado' => round($paidAmount, 2),
                    'total' => round($saleTotal, 2),
                ];
            }

            if ($saleTotal <= 0 || ($paidAmount + 0.0001) < $saleTotal) {
                return [
                    'matched' => true,
                    'updated' => false,
                    'reason' => 'monto_insuficiente',
                    'empresa_id' => $companyId,
                    'venta_id' => $saleId,
                    'estado_venta' => $saleStatus,
                    'pagado' => round($paidAmount, 2),
                    'total' => round($saleTotal, 2),
                ];
            }

            if ($saleStatus === 'pagada') {
                return [
                    'matched' => true,
                    'updated' => false,
                    'reason' => 'ya_pagada',
                    'empresa_id' => $companyId,
                    'venta_id' => $saleId,
                    'estado_venta' => $saleStatus,
                    'pagado' => round($paidAmount, 2),
                    'total' => round($saleTotal, 2),
                ];
            }

            $updateSaleStmt = $pdo->prepare(
                'UPDATE ventas
                 SET estado = "pagada",
                     updated_at = NOW()
                 WHERE id = :venta_id
                   AND empresa_id = :empresa_id'
            );
            $updateSaleStmt->execute([
                ':venta_id' => $saleId,
                ':empresa_id' => $companyId,
            ]);

            return [
                'matched' => true,
                'updated' => $updateSaleStmt->rowCount() > 0,
                'reason' => 'venta_marcada_pagada',
                'empresa_id' => $companyId,
                'venta_id' => $saleId,
                'estado_venta' => 'pagada',
                'pagado' => round($paidAmount, 2),
                'total' => round($saleTotal, 2),
            ];
        });
    }

    private function normalizePaymentCodes(array $paymentCodes): array
    {
        $normalized = [];
        foreach ($paymentCodes as $code) {
            $value = strtolower(trim((string) $code));
            if ($value === '' || preg_match('/^[a-z0-9_]{1,50}$/', $value) !== 1) {
                continue;
            }
            $normalized[$value] = true;
        }

        return array_keys($normalized);
    }

    private function redirectIntegrations(bool $openBoldModal = false, ?string $openMode = null): never
    {
        if ($openBoldModal) {
            Session::flash('integraciones_modal', 'bold');
            $normalizedMode = $this->normalizeBoldMode((string) $openMode);
            Session::flash('integraciones_bold_open_mode', $normalizedMode);
        }
        redirect('integraciones');
    }

    private function redirectCredibancoIntegrations(bool $openModal = false): never
    {
        if ($openModal) {
            Session::flash('integraciones_modal', 'credibanco');
        }
        redirect('integraciones');
    }
}
