<?php

declare(strict_types=1);

namespace App\Services;

final class FactusService
{
    public function probeConnection(array $settings): array
    {
        $auth = $this->authenticate($settings);
        if (!(bool) ($auth['ok'] ?? false)) {
            return [
                'ok' => false,
                'message' => (string) ($auth['message'] ?? 'No fue posible autenticar con Factus.'),
            ];
        }

        return [
            'ok' => true,
            'message' => 'Conexión Factus correcta. Autenticación validada.',
        ];
    }

    public function issueTestInvoice(array $settings, array $context = []): array
    {
        $auth = $this->authenticate($settings);
        if (!(bool) ($auth['ok'] ?? false)) {
            return [
                'ok' => false,
                'message' => (string) ($auth['message'] ?? 'No fue posible autenticar con Factus.'),
            ];
        }

        $apiUrl = (string) ($auth['api_url'] ?? '');
        $accessToken = (string) ($auth['token'] ?? '');
        if ($apiUrl === '' || $accessToken === '') {
            return [
                'ok' => false,
                'message' => 'No se recibió token de acceso desde Factus.',
            ];
        }

        $municipalityId = (int) ($settings['municipality_id'] ?? 0);
        if ($municipalityId <= 0) {
            return [
                'ok' => false,
                'message' => 'Debes configurar un Municipality ID de Factus válido para emitir factura de prueba.',
            ];
        }

        $configuredNumberingRangeId = (int) ($settings['numbering_range_id'] ?? 0);
        $resolvedNumbering = $this->resolveActiveNumberingRangeId($apiUrl, $accessToken, $configuredNumberingRangeId);
        if (!(bool) ($resolvedNumbering['ok'] ?? false)) {
            return [
                'ok' => false,
                'message' => (string) ($resolvedNumbering['message'] ?? 'No se pudo resolver el rango de numeración en Factus.'),
            ];
        }
        $numberingRangeId = (int) ($resolvedNumbering['id'] ?? 0);

        if ($numberingRangeId <= 0) {
            return [
                'ok' => false,
                'message' => 'No se pudo determinar un Numbering Range ID válido para la prueba.',
            ];
        }

        $endpoint = $this->endpoint($apiUrl, '/v1/bills/validate');
        $payload = $this->buildTestInvoicePayload($settings, $context, $numberingRangeId, $municipalityId);
        $response = $this->httpPostJson($endpoint, $payload, $accessToken, 20);
        $status = (int) ($response['status'] ?? 0);
        $error = trim((string) ($response['error'] ?? ''));
        $json = is_array($response['json'] ?? null) ? $response['json'] : [];

        if ($error !== '') {
            return [
                'ok' => false,
                'message' => 'Error de red al emitir factura de prueba en Factus: ' . $error,
            ];
        }

        if ($status >= 200 && $status < 300) {
            $payloadData = is_array($json['data'] ?? null) ? $json['data'] : [];
            $invoice = is_array($payloadData['bill'] ?? null) ? $payloadData['bill'] : $payloadData;

            $invoiceNumber = trim((string) ($invoice['number'] ?? $invoice['number_full'] ?? ''));
            $invoiceCufe = trim((string) ($invoice['cufe'] ?? ''));

            $message = 'Factura de prueba emitida correctamente en Factus.';
            if ($invoiceNumber !== '') {
                $message .= ' Número: ' . $invoiceNumber . '.';
            }
            if ($invoiceCufe !== '') {
                $message .= ' CUFE: ' . $invoiceCufe . '.';
            }

            return [
                'ok' => true,
                'message' => $message,
                'data' => $invoice,
            ];
        }

        $apiMessage = $this->extractApiMessage($json);
        if ($apiMessage === '') {
            $apiMessage = $this->extractBodyMessage((string) ($response['body'] ?? ''));
        }
        if ($apiMessage === '') {
            $apiMessage = 'Factus respondió HTTP ' . $status . '.';
        }

        return [
            'ok' => false,
            'message' => 'No se pudo emitir la factura de prueba: ' . $apiMessage,
        ];
    }

    public function issueSaleInvoice(array $settings, array $saleContext): array
    {
        $auth = $this->authenticate($settings);
        if (!(bool) ($auth['ok'] ?? false)) {
            return [
                'ok' => false,
                'message' => (string) ($auth['message'] ?? 'No fue posible autenticar con Factus.'),
            ];
        }

        $apiUrl = (string) ($auth['api_url'] ?? '');
        $accessToken = (string) ($auth['token'] ?? '');
        if ($apiUrl === '' || $accessToken === '') {
            return [
                'ok' => false,
                'message' => 'No se recibió token de acceso desde Factus.',
            ];
        }

        $municipalityId = (int) ($settings['municipality_id'] ?? 0);
        if ($municipalityId <= 0) {
            return [
                'ok' => false,
                'message' => 'Debes configurar un Municipality ID de Factus válido para emitir factura electrónica.',
            ];
        }

        $configuredNumberingRangeId = (int) ($settings['numbering_range_id'] ?? 0);
        $resolvedNumbering = $this->resolveActiveNumberingRangeId($apiUrl, $accessToken, $configuredNumberingRangeId);
        if (!(bool) ($resolvedNumbering['ok'] ?? false)) {
            return [
                'ok' => false,
                'message' => (string) ($resolvedNumbering['message'] ?? 'No se pudo resolver el rango de numeración en Factus.'),
            ];
        }
        $numberingRangeId = (int) ($resolvedNumbering['id'] ?? 0);

        if ($numberingRangeId <= 0) {
            return [
                'ok' => false,
                'message' => 'No se pudo determinar un Numbering Range ID válido para emitir la factura electrónica.',
            ];
        }

        $payload = $this->buildSaleInvoicePayload($saleContext, $numberingRangeId, $municipalityId);
        $endpoint = $this->endpoint($apiUrl, '/v1/bills/validate');
        $response = $this->httpPostJson($endpoint, $payload, $accessToken, 25);
        $status = (int) ($response['status'] ?? 0);
        $error = trim((string) ($response['error'] ?? ''));
        $json = is_array($response['json'] ?? null) ? $response['json'] : [];

        if ($error !== '') {
            return [
                'ok' => false,
                'message' => 'Error de red al emitir factura electrónica en Factus: ' . $error,
            ];
        }

        if ($status >= 200 && $status < 300) {
            $payloadData = is_array($json['data'] ?? null) ? $json['data'] : [];
            $invoice = is_array($payloadData['bill'] ?? null) ? $payloadData['bill'] : $payloadData;

            $invoiceNumber = trim((string) ($invoice['number'] ?? $invoice['number_full'] ?? ''));
            $invoiceCufe = trim((string) ($invoice['cufe'] ?? ''));

            $message = 'Factura electrónica emitida correctamente en Factus.';
            if ($invoiceNumber !== '') {
                $message .= ' Número: ' . $invoiceNumber . '.';
            }
            if ($invoiceCufe !== '') {
                $message .= ' CUFE: ' . $invoiceCufe . '.';
            }

            return [
                'ok' => true,
                'message' => $message,
                'data' => $invoice,
            ];
        }

        $apiMessage = $this->extractApiMessage($json);
        if ($apiMessage === '') {
            $apiMessage = $this->extractBodyMessage((string) ($response['body'] ?? ''));
        }
        if ($apiMessage === '') {
            $apiMessage = 'Factus respondió HTTP ' . $status . '.';
        }

        return [
            'ok' => false,
            'message' => 'No se pudo emitir la factura electrónica: ' . $apiMessage,
        ];
    }

    public function fetchNumberingRanges(array $settings, string $document = '21', bool $onlyActive = true): array
    {
        $auth = $this->authenticate($settings);
        if (!(bool) ($auth['ok'] ?? false)) {
            return [
                'ok' => false,
                'message' => (string) ($auth['message'] ?? 'No fue posible autenticar con Factus.'),
            ];
        }

        $apiUrl = (string) ($auth['api_url'] ?? '');
        $accessToken = (string) ($auth['token'] ?? '');
        if ($apiUrl === '' || $accessToken === '') {
            return [
                'ok' => false,
                'message' => 'No se recibió token de acceso desde Factus.',
            ];
        }

        $params = [];
        $document = trim($document);
        if ($document !== '') {
            $params['filter[document]'] = $document;
        }
        if ($onlyActive) {
            $params['filter[is_active]'] = '1';
        }

        $endpoint = $this->endpoint($apiUrl, '/v1/numbering-ranges');
        if ($params !== []) {
            $endpoint .= '?' . http_build_query($params);
        }

        $response = $this->httpGetJsonAuth($endpoint, $accessToken, 12);
        $status = (int) ($response['status'] ?? 0);
        $error = trim((string) ($response['error'] ?? ''));
        $json = is_array($response['json'] ?? null) ? $response['json'] : [];

        if ($error !== '') {
            return [
                'ok' => false,
                'message' => 'Error consultando rangos de numeración en Factus: ' . $error,
            ];
        }

        if ($status < 200 || $status >= 300) {
            $apiMessage = $this->extractApiMessage($json);
            return [
                'ok' => false,
                'message' => $apiMessage !== ''
                    ? ('Factus respondió al consultar rangos: ' . $apiMessage)
                    : ('Factus respondió HTTP ' . $status . ' al consultar rangos.'),
            ];
        }

        $ranges = $this->parseNumberingRanges($json);
        if ($ranges === []) {
            return [
                'ok' => true,
                'message' => 'No se encontraron rangos de numeración con esos filtros.',
                'data' => [],
            ];
        }

        $mapped = [];
        foreach ($ranges as $row) {
            if (!is_array($row)) {
                continue;
            }
            $id = (int) ($row['id'] ?? 0);
            if ($id <= 0) {
                continue;
            }

            $prefix = $this->firstNonEmptyString($row, ['prefix', 'prefix_code', 'prefix_number']);
            $documentName = $this->firstNonEmptyString($row, ['document', 'document_name']);
            $rangeFrom = $this->firstNonEmptyString($row, ['from', 'from_number', 'range_from']);
            $rangeTo = $this->firstNonEmptyString($row, ['to', 'to_number', 'range_to']);
            $isActive = (int) ($row['is_active'] ?? 0) === 1;

            $labelParts = ['#' . $id];
            if ($prefix !== '') {
                $labelParts[] = 'Prefijo ' . $prefix;
            }
            if ($rangeFrom !== '' || $rangeTo !== '') {
                $labelParts[] = 'Rango ' . ($rangeFrom !== '' ? $rangeFrom : '?') . '-' . ($rangeTo !== '' ? $rangeTo : '?');
            }
            if ($documentName !== '') {
                $labelParts[] = $documentName;
            }
            if ($isActive) {
                $labelParts[] = 'Activa';
            }

            $mapped[] = [
                'id' => $id,
                'prefix' => $prefix,
                'document' => $documentName,
                'from' => $rangeFrom,
                'to' => $rangeTo,
                'is_active' => $isActive,
                'label' => implode(' · ', $labelParts),
            ];
        }

        usort($mapped, static function (array $a, array $b): int {
            $aActive = (bool) ($a['is_active'] ?? false);
            $bActive = (bool) ($b['is_active'] ?? false);
            if ($aActive !== $bActive) {
                return $aActive ? -1 : 1;
            }
            return (int) ($a['id'] ?? 0) <=> (int) ($b['id'] ?? 0);
        });

        return [
            'ok' => true,
            'message' => 'Rangos de numeración cargados correctamente.',
            'data' => $mapped,
        ];
    }

    public function fetchMunicipalities(array $settings, string $query = '', int $limit = 60): array
    {
        $auth = $this->authenticate($settings);
        if (!(bool) ($auth['ok'] ?? false)) {
            return [
                'ok' => false,
                'message' => (string) ($auth['message'] ?? 'No fue posible autenticar con Factus.'),
            ];
        }

        $apiUrl = (string) ($auth['api_url'] ?? '');
        $accessToken = (string) ($auth['token'] ?? '');
        if ($apiUrl === '' || $accessToken === '') {
            return [
                'ok' => false,
                'message' => 'No se recibió token de acceso desde Factus.',
            ];
        }

        $query = trim($query);
        $limit = max(1, min(200, $limit));
        $endpointBase = $this->endpoint($apiUrl, '/v1/municipalities');

        $endpoints = [];
        if ($query !== '') {
            $endpoints[] = $endpointBase . '?' . http_build_query(['name' => $query]);
            $endpoints[] = $endpointBase . '?' . http_build_query(['filter[name]' => $query]);
        }
        $endpoints[] = $endpointBase;

        $rawRows = [];
        $lastStatus = 0;
        $lastError = '';
        $lastJson = [];

        foreach ($endpoints as $endpoint) {
            $response = $this->httpGetJsonAuth($endpoint, $accessToken, 12);
            $status = (int) ($response['status'] ?? 0);
            $error = trim((string) ($response['error'] ?? ''));
            $json = is_array($response['json'] ?? null) ? $response['json'] : [];

            $lastStatus = $status;
            $lastError = $error;
            $lastJson = $json;

            if ($error !== '') {
                continue;
            }

            if ($status >= 200 && $status < 300) {
                $rows = $this->parseMunicipalities($json);
                if ($rows !== []) {
                    $rawRows = $rows;
                    break;
                }

                if ($query === '' || str_contains($endpoint, 'name=') === false) {
                    $rawRows = $rows;
                    break;
                }
            }
        }

        if ($lastError !== '' && $rawRows === []) {
            return [
                'ok' => false,
                'message' => 'Error consultando municipios en Factus: ' . $lastError,
            ];
        }

        if ($rawRows === [] && ($lastStatus < 200 || $lastStatus >= 300)) {
            $apiMessage = $this->extractApiMessage($lastJson);
            return [
                'ok' => false,
                'message' => $apiMessage !== ''
                    ? ('Factus respondió al consultar municipios: ' . $apiMessage)
                    : ('Factus respondió HTTP ' . $lastStatus . ' al consultar municipios.'),
            ];
        }

        $queryNormalized = $this->normalizeText($query);
        $mapped = [];
        foreach ($rawRows as $row) {
            if (!is_array($row)) {
                continue;
            }

            $id = (int) ($row['id'] ?? 0);
            if ($id <= 0) {
                continue;
            }

            $name = $this->firstNonEmptyString($row, ['name', 'municipality', 'municipality_name', 'nombre']);
            if ($name === '') {
                continue;
            }

            $department = $this->firstNonEmptyString($row, ['department', 'department_name', 'state', 'departamento']);
            $code = $this->firstNonEmptyString($row, ['code', 'dane_code', 'codigo']);

            if ($queryNormalized !== '') {
                $haystack = $this->normalizeText($name . ' ' . $department . ' ' . $code . ' ' . $id);
                if (!str_contains($haystack, $queryNormalized)) {
                    continue;
                }
            }

            $label = $name;
            if ($department !== '') {
                $label .= ' (' . $department . ')';
            }
            $label .= ' · ID ' . $id;

            $mapped[] = [
                'id' => $id,
                'name' => $name,
                'department' => $department,
                'code' => $code,
                'label' => $label,
            ];
        }

        usort($mapped, static function (array $a, array $b): int {
            return strcasecmp((string) ($a['name'] ?? ''), (string) ($b['name'] ?? ''));
        });

        if (count($mapped) > $limit) {
            $mapped = array_slice($mapped, 0, $limit);
        }

        return [
            'ok' => true,
            'message' => $mapped === []
                ? 'No se encontraron municipios con ese criterio.'
                : 'Municipios cargados correctamente.',
            'data' => $mapped,
        ];
    }

    private function authenticate(array $settings): array
    {
        $apiUrl = $this->normalizeApiBaseUrl((string) ($settings['api_url'] ?? ''));
        $clientId = trim((string) ($settings['client_id'] ?? ''));
        $clientSecret = trim((string) ($settings['client_secret'] ?? ''));
        $username = trim((string) ($settings['username'] ?? ''));
        $password = trim((string) ($settings['password'] ?? ''));

        if ($apiUrl === '' || $clientId === '' || $clientSecret === '' || $username === '' || $password === '') {
            return [
                'ok' => false,
                'message' => 'Faltan credenciales obligatorias de Factus.',
            ];
        }

        if (!function_exists('curl_init')) {
            return [
                'ok' => false,
                'message' => 'cURL no está disponible en PHP. No se puede conectar con Factus.',
            ];
        }

        $payload = [
            'grant_type' => 'password',
            'client_id' => $clientId,
            'client_secret' => $clientSecret,
            'username' => $username,
            'password' => $password,
        ];

        $errors = [];
        foreach ($this->authEndpoints($apiUrl) as $endpoint) {
            $response = $this->httpPostForm($endpoint, $payload, 12);
            $status = (int) ($response['status'] ?? 0);
            $error = trim((string) ($response['error'] ?? ''));
            $json = is_array($response['json'] ?? null) ? $response['json'] : [];
            $accessToken = trim((string) ($json['access_token'] ?? ''));

            if ($error !== '') {
                $errors[] = $error;
                continue;
            }

            if ($status >= 200 && $status < 300 && $accessToken !== '') {
                return [
                    'ok' => true,
                    'token' => $accessToken,
                    'api_url' => $apiUrl,
                ];
            }

            if ($status === 400 || $status === 401 || $status === 403 || $status === 422) {
                $apiMessage = $this->extractApiMessage($json);
                if ($apiMessage === '') {
                    $apiMessage = 'Factus rechazó credenciales (HTTP ' . $status . ').';
                }

                return [
                    'ok' => false,
                    'message' => 'Factus respondió: ' . $apiMessage,
                ];
            }

            if ($status === 404) {
                $errors[] = 'Endpoint no encontrado (' . $endpoint . ').';
                continue;
            }

            if ($status >= 500) {
                $errors[] = 'Factus respondió HTTP ' . $status . '.';
                continue;
            }
        }

        return [
            'ok' => false,
            'message' => $errors !== []
                ? 'No se pudo autenticar con Factus: ' . implode(' ', array_unique($errors))
                : 'No fue posible autenticar con Factus en este momento.',
        ];
    }

    private function resolveActiveNumberingRangeId(string $apiUrl, string $accessToken, int $requestedId = 0): array
    {
        $query = http_build_query([
            'filter[document]' => '21',
            'filter[is_active]' => '1',
        ]);
        $endpoint = $this->endpoint($apiUrl, '/v1/numbering-ranges') . '?' . $query;
        $response = $this->httpGetJsonAuth($endpoint, $accessToken, 12);
        $status = (int) ($response['status'] ?? 0);
        $error = trim((string) ($response['error'] ?? ''));
        $json = is_array($response['json'] ?? null) ? $response['json'] : [];

        if ($error !== '') {
            return [
                'ok' => false,
                'message' => 'Error consultando rangos de numeración en Factus: ' . $error,
            ];
        }

        if ($status < 200 || $status >= 300) {
            $apiMessage = $this->extractApiMessage($json);
            return [
                'ok' => false,
                'message' => $apiMessage !== ''
                    ? ('Factus respondió al consultar rangos: ' . $apiMessage)
                    : ('Factus respondió HTTP ' . $status . ' al consultar rangos.'),
            ];
        }

        $ranges = $this->parseNumberingRanges($json);
        if ($ranges === []) {
            return [
                'ok' => false,
                'message' => 'No se encontraron rangos activos para documento 21. Configura Numbering Range ID en Factus.',
            ];
        }

        $activeIds = [];
        foreach ($ranges as $rangeRow) {
            if (!is_array($rangeRow)) {
                continue;
            }
            $rangeId = (int) ($rangeRow['id'] ?? 0);
            if ($rangeId > 0) {
                $activeIds[] = $rangeId;
            }
        }
        $activeIds = array_values(array_unique($activeIds));

        if ($activeIds === []) {
            return [
                'ok' => false,
                'message' => 'Factus no devolvió IDs válidos de rangos activos.',
            ];
        }

        if ($requestedId > 0) {
            if (in_array($requestedId, $activeIds, true)) {
                return [
                    'ok' => true,
                    'id' => $requestedId,
                ];
            }

            if (count($activeIds) === 1) {
                return [
                    'ok' => true,
                    'id' => (int) $activeIds[0],
                ];
            }

            $idsPreview = implode(', ', array_slice(array_map('strval', $activeIds), 0, 10));
            return [
                'ok' => false,
                'message' => 'El Numbering Range ID configurado (' . $requestedId . ') no está activo en Factus. IDs activos: ' . $idsPreview . '.',
            ];
        }

        if (count($activeIds) > 1) {
            return [
                'ok' => false,
                'message' => 'Se encontraron múltiples rangos activos en Factus. Define el Numbering Range ID en configuración.',
            ];
        }

        $id = (int) $activeIds[0];
        if ($id <= 0) {
            return [
                'ok' => false,
                'message' => 'Factus no devolvió un ID de rango de numeración válido.',
            ];
        }

        return [
            'ok' => true,
            'id' => $id,
        ];
    }

    private function parseNumberingRanges(array $json): array
    {
        $data = $json['data'] ?? null;
        if (is_array($data) && isset($data['data']) && is_array($data['data'])) {
            return $data['data'];
        }

        if (is_array($data) && $this->isListArray($data)) {
            return $data;
        }

        return [];
    }

    private function parseMunicipalities(array $json): array
    {
        $data = $json['data'] ?? null;
        if (is_array($data) && isset($data['data']) && is_array($data['data'])) {
            return $data['data'];
        }

        if (is_array($data) && $this->isListArray($data)) {
            return $data;
        }

        if (isset($json['municipalities']) && is_array($json['municipalities'])) {
            return $json['municipalities'];
        }

        return [];
    }

    private function isListArray(array $value): bool
    {
        $index = 0;
        foreach (array_keys($value) as $key) {
            if ($key !== $index) {
                return false;
            }
            $index++;
        }

        return true;
    }

    private function buildTestInvoicePayload(array $settings, array $context, int $numberingRangeId, int $municipalityId): array
    {
        $companyName = trim((string) ($context['company_name'] ?? 'Veltrix POS'));
        $companyName = $companyName !== '' ? $companyName : 'Veltrix POS';

        $companyEmail = trim((string) ($context['company_email'] ?? ''));
        if ($companyEmail === '' || !filter_var($companyEmail, FILTER_VALIDATE_EMAIL)) {
            $companyEmail = 'no-reply@example.com';
        }

        $companyPhone = $this->digitsOnly((string) ($context['company_phone'] ?? ''));
        if ($companyPhone === '') {
            $companyPhone = '3000000000';
        }

        $companyAddress = trim((string) ($context['company_address'] ?? 'Dirección de prueba'));
        if ($companyAddress === '') {
            $companyAddress = 'Dirección de prueba';
        }

        $customerIdentification = $this->digitsOnly((string) ($context['customer_identification'] ?? ''));
        if ($customerIdentification === '') {
            $customerIdentification = '123456789';
        }

        $itemPrice = (float) ($context['item_price'] ?? 10000);
        if ($itemPrice <= 0) {
            $itemPrice = 10000;
        }
        $itemPrice = round($itemPrice, 2);

        $taxRate = (float) ($context['tax_rate'] ?? 19);
        if ($taxRate < 0) {
            $taxRate = 0;
        }
        $taxRate = round($taxRate, 2);

        $referenceCode = 'VELTRIX-TEST-' . date('Ymd-His');

        return [
            'document' => '01',
            'numbering_range_id' => $numberingRangeId,
            'reference_code' => $referenceCode,
            'observation' => 'Factura de prueba enviada desde Veltrix POS.',
            'payment_form' => '1',
            'payment_method_code' => '10',
            'operation_type' => 10,
            'send_email' => false,
            'establishment' => [
                'name' => $companyName,
                'address' => $companyAddress,
                'phone_number' => $companyPhone,
                'email' => $companyEmail,
                'municipality_id' => (string) $municipalityId,
            ],
            'customer' => [
                'identification' => $customerIdentification,
                'company' => '',
                'trade_name' => '',
                'names' => 'Cliente de prueba',
                'address' => $companyAddress,
                'email' => $companyEmail,
                'phone' => $companyPhone,
                'legal_organization_id' => '2',
                'tribute_id' => '21',
                'identification_document_id' => '3',
                'municipality_id' => (string) $municipalityId,
            ],
            'items' => [
                [
                    'code_reference' => 'VELTRIX-FE-TEST',
                    'name' => 'Producto de prueba Veltrix POS',
                    'quantity' => 1,
                    'discount_rate' => 0,
                    'price' => $itemPrice,
                    'tax_rate' => number_format($taxRate, 2, '.', ''),
                    'unit_measure_id' => 70,
                    'standard_code_id' => 1,
                    'is_excluded' => $taxRate > 0 ? 0 : 1,
                    'tribute_id' => 1,
                    'withholding_taxes' => [],
                ],
            ],
        ];
    }

    private function buildSaleInvoicePayload(array $context, int $numberingRangeId, int $municipalityId): array
    {
        $company = is_array($context['company'] ?? null) ? $context['company'] : [];
        $customer = is_array($context['customer'] ?? null) ? $context['customer'] : [];
        $items = is_array($context['items'] ?? null) ? $context['items'] : [];

        $companyName = trim((string) ($company['name'] ?? 'Veltrix POS'));
        if ($companyName === '') {
            $companyName = 'Veltrix POS';
        }

        $companyEmail = trim((string) ($company['email'] ?? ''));
        if ($companyEmail === '' || !filter_var($companyEmail, FILTER_VALIDATE_EMAIL)) {
            $companyEmail = 'no-reply@example.com';
        }

        $companyPhone = $this->digitsOnly((string) ($company['phone'] ?? ''));
        if ($companyPhone === '') {
            $companyPhone = '3000000000';
        }

        $companyAddress = trim((string) ($company['address'] ?? 'Dirección sin definir'));
        if ($companyAddress === '') {
            $companyAddress = 'Dirección sin definir';
        }

        $customerIdentification = $this->digitsOnly((string) ($customer['identification'] ?? ''));
        if ($customerIdentification === '') {
            $customerIdentification = '222222222222';
        }

        $customerName = trim((string) ($customer['name'] ?? 'Consumidor final'));
        if ($customerName === '') {
            $customerName = 'Consumidor final';
        }

        $customerEmail = trim((string) ($customer['email'] ?? ''));
        if ($customerEmail === '' || !filter_var($customerEmail, FILTER_VALIDATE_EMAIL)) {
            $customerEmail = $companyEmail;
        }

        $customerPhone = $this->digitsOnly((string) ($customer['phone'] ?? ''));
        if ($customerPhone === '') {
            $customerPhone = $companyPhone;
        }

        $customerAddress = trim((string) ($customer['address'] ?? ''));
        if ($customerAddress === '') {
            $customerAddress = $companyAddress;
        }

        $paymentForm = trim((string) ($context['payment_form'] ?? '1'));
        if ($paymentForm === '') {
            $paymentForm = '1';
        }

        $paymentMethodCode = trim((string) ($context['payment_method_code'] ?? '10'));
        if ($paymentMethodCode === '') {
            $paymentMethodCode = '10';
        }

        $operationType = (int) ($context['operation_type'] ?? 10);
        if ($operationType <= 0) {
            $operationType = 10;
        }

        $referenceCode = trim((string) ($context['reference_code'] ?? ''));
        if ($referenceCode === '') {
            $referenceCode = 'VELTRIX-POS-' . date('Ymd-His');
        }

        $observation = trim((string) ($context['observation'] ?? 'Venta registrada desde Veltrix POS.'));
        if ($observation === '') {
            $observation = 'Venta registrada desde Veltrix POS.';
        }

        $preparedItems = [];
        foreach (array_values($items) as $index => $item) {
            if (!is_array($item)) {
                continue;
            }

            $quantity = (float) ($item['quantity'] ?? 0);
            $price = (float) ($item['price'] ?? 0);
            if ($quantity <= 0 || $price <= 0) {
                continue;
            }

            $name = trim((string) ($item['name'] ?? ''));
            if ($name === '') {
                $name = 'Producto #' . ($index + 1);
            }

            $codeReference = trim((string) ($item['code_reference'] ?? ''));
            if ($codeReference === '') {
                $codeReference = 'ITEM-' . ($index + 1);
            }

            $discountRate = (float) ($item['discount_rate'] ?? 0);
            $discountRate = max(0.0, min(100.0, $discountRate));

            $taxRate = (float) ($item['tax_rate'] ?? 0);
            $taxRate = max(0.0, min(100.0, $taxRate));

            $unitMeasureId = (int) ($item['unit_measure_id'] ?? 70);
            if ($unitMeasureId <= 0) {
                $unitMeasureId = 70;
            }

            $standardCodeId = (int) ($item['standard_code_id'] ?? 1);
            if ($standardCodeId <= 0) {
                $standardCodeId = 1;
            }

            $tributeId = (int) ($item['tribute_id'] ?? 1);
            if ($tributeId <= 0) {
                $tributeId = 1;
            }

            $preparedItem = [
                'code_reference' => substr($codeReference, 0, 120),
                'name' => substr($name, 0, 255),
                'quantity' => round($quantity, 2),
                'price' => round($price, 2),
                'discount_rate' => round($discountRate, 2),
                'tax_rate' => number_format($taxRate, 2, '.', ''),
                'unit_measure_id' => $unitMeasureId,
                'standard_code_id' => $standardCodeId,
                'is_excluded' => $taxRate > 0 ? 0 : 1,
                'tribute_id' => $tributeId,
                'withholding_taxes' => [],
            ];

            $preparedItems[] = $preparedItem;
        }

        if ($preparedItems === []) {
            $preparedItems[] = [
                'code_reference' => 'VELTRIX-POS-ITEM',
                'name' => 'Producto POS',
                'quantity' => 1,
                'price' => 1000,
                'discount_rate' => 0,
                'tax_rate' => number_format(0, 2, '.', ''),
                'unit_measure_id' => 70,
                'standard_code_id' => 1,
                'is_excluded' => 1,
                'tribute_id' => 1,
                'withholding_taxes' => [],
            ];
        }

        return [
            'document' => '01',
            'numbering_range_id' => $numberingRangeId,
            'reference_code' => $referenceCode,
            'observation' => $observation,
            'payment_form' => $paymentForm,
            'payment_method_code' => $paymentMethodCode,
            'operation_type' => $operationType,
            'send_email' => false,
            'establishment' => [
                'name' => $companyName,
                'address' => $companyAddress,
                'phone_number' => $companyPhone,
                'email' => $companyEmail,
                'municipality_id' => (string) $municipalityId,
            ],
            'customer' => [
                'identification' => $customerIdentification,
                'company' => '',
                'trade_name' => '',
                'names' => $customerName,
                'address' => $customerAddress,
                'email' => $customerEmail,
                'phone' => $customerPhone,
                'legal_organization_id' => '2',
                'tribute_id' => '21',
                'identification_document_id' => '3',
                'municipality_id' => (string) $municipalityId,
            ],
            'items' => $preparedItems,
        ];
    }

    private function extractApiMessage(array $json): string
    {
        $primaryMessage = trim((string) ($json['message'] ?? $json['error_description'] ?? $json['error'] ?? ''));
        $detailMessages = $this->extractDetailedApiMessages($json);

        if ($primaryMessage !== '' && $detailMessages === []) {
            return $primaryMessage;
        }

        if ($primaryMessage === '' && $detailMessages !== []) {
            return implode(' | ', array_slice($detailMessages, 0, 4));
        }

        if ($primaryMessage !== '' && $detailMessages !== []) {
            $detailText = implode(' | ', array_slice($detailMessages, 0, 3));
            if ($detailText === '') {
                return $primaryMessage;
            }

            if ($this->isGenericValidationMessage($primaryMessage)) {
                return $primaryMessage . ': ' . $detailText;
            }

            return $primaryMessage . ' (' . $detailText . ')';
        }

        return '';
    }

    private function extractDetailedApiMessages(array $json): array
    {
        $candidateNodes = [];

        if (isset($json['errors']) && is_array($json['errors'])) {
            $candidateNodes[] = $json['errors'];
        }
        if (isset($json['details']) && is_array($json['details'])) {
            $candidateNodes[] = $json['details'];
        }
        if (isset($json['validation_errors']) && is_array($json['validation_errors'])) {
            $candidateNodes[] = $json['validation_errors'];
        }

        $data = $json['data'] ?? null;
        if (is_array($data)) {
            if (isset($data['errors']) && is_array($data['errors'])) {
                $candidateNodes[] = $data['errors'];
            }
            if (isset($data['details']) && is_array($data['details'])) {
                $candidateNodes[] = $data['details'];
            }
            if (isset($data['validation_errors']) && is_array($data['validation_errors'])) {
                $candidateNodes[] = $data['validation_errors'];
            }
        }

        $messages = [];
        foreach ($candidateNodes as $node) {
            $messages = array_merge($messages, $this->flattenApiMessages($node));
        }

        $normalizedUnique = [];
        $unique = [];
        foreach ($messages as $message) {
            $message = trim((string) $message);
            if ($message === '') {
                continue;
            }
            $fingerprint = $this->normalizeText($message);
            if ($fingerprint === '' || isset($normalizedUnique[$fingerprint])) {
                continue;
            }
            $normalizedUnique[$fingerprint] = true;
            $unique[] = $message;
        }

        return $unique;
    }

    private function flattenApiMessages($node, string $path = ''): array
    {
        if (!is_array($node)) {
            $text = trim((string) $node);
            if ($text === '') {
                return [];
            }

            if ($path === '') {
                return [$text];
            }

            return [$path . ': ' . $text];
        }

        $messages = [];
        $list = $this->isListArray($node);
        foreach ($node as $key => $value) {
            $nextPath = $path;
            if (!$list && is_string($key) && $key !== '') {
                $nextPath = $path === '' ? $key : ($path . '.' . $key);
            }
            $messages = array_merge($messages, $this->flattenApiMessages($value, $nextPath));
        }

        return $messages;
    }

    private function isGenericValidationMessage(string $message): bool
    {
        $normalized = $this->normalizeText($message);
        if ($normalized === '') {
            return false;
        }

        return str_contains($normalized, 'error de validacion')
            || str_contains($normalized, 'validacion')
            || str_contains($normalized, 'validation error')
            || str_contains($normalized, 'validation');
    }

    private function extractBodyMessage(string $body): string
    {
        $body = trim($body);
        if ($body === '') {
            return '';
        }

        $decoded = json_decode($body, true);
        if (is_array($decoded)) {
            return $this->extractApiMessage($decoded);
        }

        $plain = trim(strip_tags($body));
        if ($plain === '') {
            return '';
        }

        if (mb_strlen($plain, 'UTF-8') > 220) {
            return mb_substr($plain, 0, 220, 'UTF-8') . '...';
        }

        return $plain;
    }

    private function authEndpoints(string $apiUrl): array
    {
        $trimmed = rtrim($apiUrl, '/');
        if (preg_match('#/oauth/token$#i', $trimmed) === 1) {
            return [$trimmed];
        }

        return array_values(array_unique([
            $this->endpoint($trimmed, '/oauth/token'),
            $this->endpoint($trimmed, '/api/oauth/token'),
            $this->endpoint($trimmed, '/v1/oauth/token'),
        ]));
    }

    private function normalizeApiBaseUrl(string $apiUrl): string
    {
        $apiUrl = rtrim(trim($apiUrl), '/');
        if ($apiUrl === '') {
            return '';
        }

        if (preg_match('#/oauth/token$#i', $apiUrl) === 1) {
            return preg_replace('#/oauth/token$#i', '', $apiUrl) ?: '';
        }

        if (preg_match('#/v1$#i', $apiUrl) === 1) {
            return substr($apiUrl, 0, -3);
        }

        return $apiUrl;
    }

    private function endpoint(string $apiBase, string $path): string
    {
        $apiBase = rtrim($apiBase, '/');
        $path = '/' . ltrim($path, '/');

        if (str_ends_with(strtolower($apiBase), '/v1') && str_starts_with(strtolower($path), '/v1/')) {
            $path = substr($path, 3);
        }

        return $apiBase . $path;
    }

    private function firstNonEmptyString(array $payload, array $keys): string
    {
        foreach ($keys as $key) {
            $value = trim((string) ($payload[$key] ?? ''));
            if ($value !== '') {
                return $value;
            }
        }

        return '';
    }

    private function normalizeText(string $value): string
    {
        $value = trim(mb_strtolower($value, 'UTF-8'));
        if ($value === '') {
            return '';
        }

        $normalized = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $value);
        if (!is_string($normalized) || $normalized === '') {
            $normalized = $value;
        }

        $normalized = preg_replace('/\s+/', ' ', trim($normalized)) ?? '';
        return mb_strtolower($normalized, 'UTF-8');
    }

    private function digitsOnly(string $value): string
    {
        return preg_replace('/\D+/', '', $value) ?? '';
    }

    private function httpPostForm(string $url, array $data, int $timeoutSeconds = 10): array
    {
        $ch = curl_init($url);
        if ($ch === false) {
            return [
                'status' => 0,
                'error' => 'No se pudo inicializar cURL.',
                'body' => null,
                'json' => null,
            ];
        }

        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_FOLLOWLOCATION => true,
            CURLOPT_MAXREDIRS => 2,
            CURLOPT_CONNECTTIMEOUT => max(1, $timeoutSeconds),
            CURLOPT_TIMEOUT => max(1, $timeoutSeconds),
            CURLOPT_POST => true,
            CURLOPT_POSTFIELDS => http_build_query($data),
            CURLOPT_HTTPHEADER => [
                'Accept: application/json',
                'Content-Type: application/x-www-form-urlencoded',
                'User-Agent: VeltrixPOS/1.0',
            ],
        ]);

        $body = curl_exec($ch);
        $status = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $error = curl_error($ch);
        curl_close($ch);

        $json = null;
        if (is_string($body) && $body !== '') {
            $decoded = json_decode($body, true);
            if (is_array($decoded)) {
                $json = $decoded;
            }
        }

        return [
            'status' => $status,
            'error' => trim($error),
            'body' => is_string($body) ? $body : null,
            'json' => $json,
        ];
    }

    private function httpPostJson(string $url, array $data, string $accessToken, int $timeoutSeconds = 15): array
    {
        $payload = json_encode($data, JSON_UNESCAPED_UNICODE);
        if (!is_string($payload)) {
            return [
                'status' => 0,
                'error' => 'No se pudo serializar el payload JSON de Factus.',
                'body' => null,
                'json' => null,
            ];
        }

        $ch = curl_init($url);
        if ($ch === false) {
            return [
                'status' => 0,
                'error' => 'No se pudo inicializar cURL.',
                'body' => null,
                'json' => null,
            ];
        }

        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_FOLLOWLOCATION => true,
            CURLOPT_MAXREDIRS => 2,
            CURLOPT_CONNECTTIMEOUT => max(1, $timeoutSeconds),
            CURLOPT_TIMEOUT => max(1, $timeoutSeconds),
            CURLOPT_POST => true,
            CURLOPT_POSTFIELDS => $payload,
            CURLOPT_HTTPHEADER => [
                'Accept: application/json',
                'Content-Type: application/json',
                'Authorization: Bearer ' . $accessToken,
                'User-Agent: VeltrixPOS/1.0',
            ],
        ]);

        $body = curl_exec($ch);
        $status = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $error = curl_error($ch);
        curl_close($ch);

        $json = null;
        if (is_string($body) && $body !== '') {
            $decoded = json_decode($body, true);
            if (is_array($decoded)) {
                $json = $decoded;
            }
        }

        return [
            'status' => $status,
            'error' => trim($error),
            'body' => is_string($body) ? $body : null,
            'json' => $json,
        ];
    }

    private function httpGetJsonAuth(string $url, string $accessToken, int $timeoutSeconds = 10): array
    {
        $ch = curl_init($url);
        if ($ch === false) {
            return [
                'status' => 0,
                'error' => 'No se pudo inicializar cURL.',
                'body' => null,
                'json' => null,
            ];
        }

        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_FOLLOWLOCATION => true,
            CURLOPT_MAXREDIRS => 2,
            CURLOPT_CONNECTTIMEOUT => max(1, $timeoutSeconds),
            CURLOPT_TIMEOUT => max(1, $timeoutSeconds),
            CURLOPT_HTTPGET => true,
            CURLOPT_HTTPHEADER => [
                'Accept: application/json',
                'Content-Type: application/json',
                'Authorization: Bearer ' . $accessToken,
                'User-Agent: VeltrixPOS/1.0',
            ],
        ]);

        $body = curl_exec($ch);
        $status = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $error = curl_error($ch);
        curl_close($ch);

        $json = null;
        if (is_string($body) && $body !== '') {
            $decoded = json_decode($body, true);
            if (is_array($decoded)) {
                $json = $decoded;
            }
        }

        return [
            'status' => $status,
            'error' => trim($error),
            'body' => is_string($body) ? $body : null,
            'json' => $json,
        ];
    }
}
