<?php

declare(strict_types=1);

namespace App\Services;

use App\Core\Database;
use App\Models\GlobalSetting;

class NotificationChannelService
{
    private const KEY_EMAIL_FROM_NAME = 'notify_email_from_name';
    private const KEY_EMAIL_FROM_ADDRESS = 'notify_email_from_address';
    private const KEY_EMAIL_REPLY_TO = 'notify_email_reply_to';
    private const KEY_SMTP_HOST = 'notify_smtp_host';
    private const KEY_SMTP_PORT = 'notify_smtp_port';
    private const KEY_SMTP_USERNAME = 'notify_smtp_username';
    private const KEY_SMTP_PASSWORD = 'notify_smtp_password';
    private const KEY_SMTP_SECURE = 'notify_smtp_secure';
    private const KEY_SMTP_FROM_EMAIL = 'notify_smtp_from_email';
    private const KEY_SMTP_FROM_NAME = 'notify_smtp_from_name';

    private const KEY_WHATSAPP_API_URL = 'notify_whatsapp_api_url';
    private const KEY_WHATSAPP_PHONE_ID = 'notify_whatsapp_phone_number_id';
    private const KEY_WHATSAPP_TOKEN = 'notify_whatsapp_access_token';
    private const KEY_WHATSAPP_COUNTRY_CODE = 'notify_whatsapp_default_country_code';
    private const KEY_WHATSAPP_PROVIDER = 'notify_whatsapp_provider';
    private const KEY_MANYCHAT_API_URL = 'notify_manychat_api_url';
    private const KEY_MANYCHAT_TOKEN = 'notify_manychat_access_token';
    private const KEY_MANYCHAT_MESSAGE_TAG = 'notify_manychat_message_tag';
    private const KEY_MANYCHAT_CONTACT_FIELD_ID = 'notify_manychat_contact_field_id';
    private const KEY_WHATSAPP_TEMPLATE_ENABLED = 'notify_whatsapp_template_enabled';
    private const KEY_WHATSAPP_TEMPLATE_LANGUAGE = 'notify_whatsapp_template_language';
    private const KEY_WHATSAPP_TEMPLATE_NAME_10D = 'notify_whatsapp_template_name_10d';
    private const KEY_WHATSAPP_TEMPLATE_NAME_3D = 'notify_whatsapp_template_name_3d';

    private const KEY_CHANNEL_EMAIL = 'notify_channel_email';
    private const KEY_CHANNEL_WHATSAPP = 'notify_channel_whatsapp';

    private const KEY_RETRY_MAX_ATTEMPTS = 'notify_retry_max_attempts';
    private const KEY_RETRY_BACKOFF_MINUTES = 'notify_retry_backoff_minutes';

    private static ?array $settingsCache = null;
    private static ?array $templateNoticeMapCache = null;
    private static ?array $templateCatalogCache = null;

    public function sendEmail(string $to, string $subject, string $textMessage, ?string $htmlMessage = null): array
    {
        $to = strtolower(trim($to));
        if ($to === '' || !filter_var($to, FILTER_VALIDATE_EMAIL)) {
            return [
                'ok' => false,
                'error' => 'Correo de destino inválido.',
                'response' => null,
            ];
        }

        $smtpFromAddress = trim($this->runtimeValue(self::KEY_SMTP_FROM_EMAIL, (string) config('notifications.smtp.from_email', '')));
        $smtpFromName = trim($this->runtimeValue(self::KEY_SMTP_FROM_NAME, (string) config('notifications.smtp.from_name', '')));
        $fromAddress = $smtpFromAddress !== ''
            ? $smtpFromAddress
            : trim($this->runtimeValue(self::KEY_EMAIL_FROM_ADDRESS, (string) config('notifications.email.from_address', 'no-reply@veltrix.local')));
        $fromName = $smtpFromName !== ''
            ? $smtpFromName
            : trim($this->runtimeValue(self::KEY_EMAIL_FROM_NAME, (string) config('notifications.email.from_name', 'Veltrix POS')));
        $replyTo = trim($this->runtimeValue(self::KEY_EMAIL_REPLY_TO, (string) config('notifications.email.reply_to', '')));

        if ($fromAddress === '' || !filter_var($fromAddress, FILTER_VALIDATE_EMAIL)) {
            return [
                'ok' => false,
                'error' => 'Configuración de remitente email inválida.',
                'response' => null,
            ];
        }

        $encodedFromName = str_replace(["\r", "\n"], '', $fromName !== '' ? $fromName : 'Veltrix POS');
        $safeSubject = trim($subject) !== '' ? trim($subject) : 'Notificación Veltrix POS';
        $isHtml = $htmlMessage !== null;
        $body = $isHtml ? (string) $htmlMessage : $textMessage;

        $smtpHost = trim($this->runtimeValue(self::KEY_SMTP_HOST, (string) config('notifications.smtp.host', '')));
        $smtpPort = (int) $this->runtimeValue(self::KEY_SMTP_PORT, (string) config('notifications.smtp.port', 587));
        $smtpUsername = trim($this->runtimeValue(self::KEY_SMTP_USERNAME, (string) config('notifications.smtp.username', '')));
        $smtpPassword = (string) $this->runtimeValue(self::KEY_SMTP_PASSWORD, (string) config('notifications.smtp.password', ''));
        $smtpSecure = strtolower(trim($this->runtimeValue(self::KEY_SMTP_SECURE, (string) config('notifications.smtp.secure', 'tls'))));

        if ($smtpHost !== '' && $smtpPort > 0) {
            return $this->sendEmailViaSmtp([
                'host' => $smtpHost,
                'port' => $smtpPort,
                'username' => $smtpUsername,
                'password' => $smtpPassword,
                'secure' => $smtpSecure,
                'from_address' => $fromAddress,
                'from_name' => $encodedFromName,
                'reply_to' => $replyTo,
            ], $to, $safeSubject, $body, $isHtml);
        }

        $headers = [
            'MIME-Version: 1.0',
            $isHtml
                ? 'Content-Type: text/html; charset=UTF-8'
                : 'Content-Type: text/plain; charset=UTF-8',
            'From: ' . $encodedFromName . ' <' . $fromAddress . '>',
        ];

        if ($replyTo !== '' && filter_var($replyTo, FILTER_VALIDATE_EMAIL)) {
            $headers[] = 'Reply-To: ' . $replyTo;
        }

        $sent = @mail($to, $safeSubject, $body, implode("\r\n", $headers));

        return [
            'ok' => $sent,
            'error' => $sent ? null : 'mail() retornó false al enviar email.',
            'response' => null,
        ];
    }

    private function sendEmailViaSmtp(array $smtp, string $to, string $subject, string $body, bool $isHtml): array
    {
        if (!function_exists('stream_socket_client')) {
            return [
                'ok' => false,
                'error' => 'SMTP no disponible: stream_socket_client no existe.',
                'response' => null,
            ];
        }

        $host = trim((string) ($smtp['host'] ?? ''));
        $port = (int) ($smtp['port'] ?? 0);
        $username = trim((string) ($smtp['username'] ?? ''));
        $password = (string) ($smtp['password'] ?? '');
        $secure = strtolower(trim((string) ($smtp['secure'] ?? 'tls')));
        $fromAddress = trim((string) ($smtp['from_address'] ?? ''));
        $fromName = trim((string) ($smtp['from_name'] ?? 'Veltrix POS'));
        $replyTo = trim((string) ($smtp['reply_to'] ?? ''));

        if ($host === '' || $port < 1 || $port > 65535) {
            return [
                'ok' => false,
                'error' => 'Configuración SMTP inválida (host/puerto).',
                'response' => null,
            ];
        }

        if (!in_array($secure, ['tls', 'ssl', 'none'], true)) {
            $secure = 'tls';
        }

        $remoteHost = $secure === 'ssl' ? ('ssl://' . $host) : $host;
        $socket = @stream_socket_client(
            $remoteHost . ':' . $port,
            $errorNo,
            $errorMessage,
            15,
            STREAM_CLIENT_CONNECT
        );

        if (!is_resource($socket)) {
            return [
                'ok' => false,
                'error' => 'No fue posible conectar al servidor SMTP: ' . ((string) $errorMessage ?: 'error #' . (string) $errorNo),
                'response' => null,
            ];
        }

        stream_set_timeout($socket, 25);

        try {
            $this->smtpExpect($socket, [220], 'conexión');
            $this->smtpCommand($socket, 'EHLO veltrix.local', [250], 'EHLO inicial');

            if ($secure === 'tls') {
                $this->smtpCommand($socket, 'STARTTLS', [220], 'STARTTLS');

                $cryptoEnabled = @stream_socket_enable_crypto($socket, true, STREAM_CRYPTO_METHOD_TLS_CLIENT);
                if ($cryptoEnabled !== true) {
                    return [
                        'ok' => false,
                        'error' => 'No fue posible establecer canal TLS con el servidor SMTP.',
                        'response' => null,
                    ];
                }

                $this->smtpCommand($socket, 'EHLO veltrix.local', [250], 'EHLO TLS');
            }

            if ($username !== '' || $password !== '') {
                if ($username === '' || $password === '') {
                    return [
                        'ok' => false,
                        'error' => 'Para autenticación SMTP debes configurar usuario y contraseña.',
                        'response' => null,
                    ];
                }

                $this->smtpCommand($socket, 'AUTH LOGIN', [334], 'AUTH LOGIN');
                $this->smtpCommand($socket, base64_encode($username), [334], 'AUTH usuario');
                $this->smtpCommand($socket, base64_encode($password), [235], 'AUTH clave');
            }

            $this->smtpCommand($socket, 'MAIL FROM:<' . $fromAddress . '>', [250], 'MAIL FROM');
            $this->smtpCommand($socket, 'RCPT TO:<' . $to . '>', [250, 251], 'RCPT TO');
            $this->smtpCommand($socket, 'DATA', [354], 'DATA');

            $headers = [
                'Date: ' . date('r'),
                'MIME-Version: 1.0',
                'Subject: ' . $this->smtpEncodeHeader($subject),
                'From: ' . $fromName . ' <' . $fromAddress . '>',
                'To: <' . $to . '>',
                $isHtml ? 'Content-Type: text/html; charset=UTF-8' : 'Content-Type: text/plain; charset=UTF-8',
                'Content-Transfer-Encoding: 8bit',
            ];

            if ($replyTo !== '' && filter_var($replyTo, FILTER_VALIDATE_EMAIL)) {
                $headers[] = 'Reply-To: ' . $replyTo;
            }

            $messageData = implode("\r\n", $headers)
                . "\r\n\r\n"
                . $this->smtpDotStuff($body)
                . "\r\n.";

            fwrite($socket, $messageData . "\r\n");
            $this->smtpExpect($socket, [250], 'DATA END');

            fwrite($socket, "QUIT\r\n");
        } catch (\Throwable $e) {
            if (is_resource($socket)) {
                fclose($socket);
            }

            return [
                'ok' => false,
                'error' => 'Error SMTP: ' . $e->getMessage(),
                'response' => null,
            ];
        }

        if (is_resource($socket)) {
            fclose($socket);
        }

        return [
            'ok' => true,
            'error' => null,
            'response' => null,
        ];
    }

    /**
     * @param resource $socket
     */
    private function smtpCommand($socket, string $command, array $validCodes, string $label): void
    {
        fwrite($socket, $command . "\r\n");
        $this->smtpExpect($socket, $validCodes, $label);
    }

    /**
     * @param resource $socket
     */
    private function smtpExpect($socket, array $validCodes, string $label): void
    {
        [$code, $response] = $this->smtpRead($socket);

        if (!in_array($code, $validCodes, true)) {
            throw new \RuntimeException($label . ' respondió SMTP ' . $code . ': ' . $response);
        }
    }

    /**
     * @param resource $socket
     */
    private function smtpRead($socket): array
    {
        $lines = [];

        while (($line = fgets($socket, 2048)) !== false) {
            $line = rtrim($line, "\r\n");
            $lines[] = $line;

            if (strlen($line) < 4 || $line[3] !== '-') {
                break;
            }
        }

        if ($lines === []) {
            throw new \RuntimeException('Sin respuesta del servidor SMTP.');
        }

        $last = $lines[count($lines) - 1];
        $code = (int) substr($last, 0, 3);

        return [$code, implode(' | ', $lines)];
    }

    private function smtpDotStuff(string $body): string
    {
        $normalized = preg_replace("/\r\n|\r|\n/", "\r\n", $body) ?: '';
        $lines = explode("\r\n", $normalized);
        foreach ($lines as $index => $line) {
            if (str_starts_with($line, '.')) {
                $lines[$index] = '.' . $line;
            }
        }

        return implode("\r\n", $lines);
    }

    private function smtpEncodeHeader(string $value): string
    {
        $value = trim($value);
        if ($value === '') {
            return 'Notificación Veltrix POS';
        }

        return '=?UTF-8?B?' . base64_encode($value) . '?=';
    }

    public function sendWhatsAppText(string $phone, string $message): array
    {
        $normalizedPhone = $this->normalizePhone($phone);
        if ($normalizedPhone === '') {
            return [
                'ok' => false,
                'error' => 'Número de WhatsApp inválido.',
                'response' => null,
            ];
        }

        $provider = strtolower(trim($this->runtimeValue(
            self::KEY_WHATSAPP_PROVIDER,
            (string) config('notifications.whatsapp.provider', 'meta')
        )));
        if (!in_array($provider, ['meta', 'manychat'], true)) {
            $provider = 'meta';
        }

        if ($provider === 'manychat') {
            return $this->sendWhatsAppViaManyChat($normalizedPhone, $message);
        }

        return $this->sendWhatsAppViaMeta($normalizedPhone, $message);
    }

    public function sendWhatsAppNotice(string $phone, string $fallbackMessage, array $templateContext = []): array
    {
        $normalizedPhone = $this->normalizePhone($phone);
        if ($normalizedPhone === '') {
            return [
                'ok' => false,
                'error' => 'Número de WhatsApp inválido.',
                'response' => null,
            ];
        }

        $provider = strtolower(trim($this->runtimeValue(
            self::KEY_WHATSAPP_PROVIDER,
            (string) config('notifications.whatsapp.provider', 'meta')
        )));
        if (!in_array($provider, ['meta', 'manychat'], true)) {
            $provider = 'meta';
        }

        if ($provider !== 'meta') {
            return $this->sendWhatsAppText($normalizedPhone, $fallbackMessage);
        }

        $templateEnabled = $this->toBool($this->runtimeValue(
            self::KEY_WHATSAPP_TEMPLATE_ENABLED,
            (string) ((bool) config('notifications.whatsapp.template_enabled', false) ? '1' : '0')
        ));
        if (!$templateEnabled) {
            return $this->sendWhatsAppViaMeta($normalizedPhone, $fallbackMessage);
        }

        $noticeType = strtolower(trim((string) ($templateContext['notice_type'] ?? '')));
        $templateName = strtolower(trim((string) ($templateContext['template_name'] ?? '')));
        $templateLanguage = trim((string) ($templateContext['template_language'] ?? ''));
        if ($templateName === '') {
            $templateConfig = $this->resolveWhatsAppTemplateConfig($noticeType);
            $templateName = strtolower(trim((string) ($templateConfig['name'] ?? '')));
            if ($templateLanguage === '') {
                $templateLanguage = trim((string) ($templateConfig['language'] ?? ''));
            }
        }
        if ($templateLanguage === '') {
            $templateLanguage = $this->runtimeValue(
                self::KEY_WHATSAPP_TEMPLATE_LANGUAGE,
                (string) config('notifications.whatsapp.template_language', 'es_CO')
            );
        }
        $templateLanguage = $this->normalizeWhatsAppTemplateLanguage($templateLanguage);
        $rawBodyParameters = is_array($templateContext['body_parameters'] ?? null)
            ? $templateContext['body_parameters']
            : [];
        $bodyParameters = [];
        foreach ($rawBodyParameters as $parameter) {
            $value = trim((string) $parameter);
            if ($value !== '') {
                $bodyParameters[] = $value;
            }
        }
        $fallbackBodyParameters = $this->normalizeTemplateBodyParameters($templateName, $templateLanguage, $bodyParameters);
        $fallbackMessage = $this->interpolateTemplatePlaceholders($fallbackMessage, $fallbackBodyParameters);

        if ($templateName === '' || $bodyParameters === []) {
            return $this->sendWhatsAppViaMeta($normalizedPhone, $fallbackMessage);
        }

        $templateResult = $this->sendWhatsAppTemplateViaMeta(
            $normalizedPhone,
            $templateName,
            $templateLanguage,
            $bodyParameters,
            trim((string) ($templateContext['payment_link'] ?? ''))
        );
        if ((bool) ($templateResult['ok'] ?? false)) {
            return $templateResult;
        }

        $textFallbackResult = $this->sendWhatsAppViaMeta($normalizedPhone, $fallbackMessage);
        if ((bool) ($textFallbackResult['ok'] ?? false)) {
            $response = is_array($textFallbackResult['response'] ?? null)
                ? $textFallbackResult['response']
                : ['body' => $textFallbackResult['response'] ?? null];
            $response['template_error'] = trim((string) ($templateResult['error'] ?? ''));
            $response['template_name'] = $templateName;
            $response['template_language'] = $templateLanguage;
            $textFallbackResult['response'] = $response;

            return $textFallbackResult;
        }

        $templateError = trim((string) ($templateResult['error'] ?? 'Error desconocido en plantilla.'));
        $textError = trim((string) ($textFallbackResult['error'] ?? 'Error desconocido en fallback de texto.'));

        return [
            'ok' => false,
            'error' => 'Falló envío por plantilla y fallback texto. Plantilla: '
                . $templateError
                . ' | Texto: '
                . $textError,
            'response' => [
                'template' => $templateResult['response'] ?? null,
                'text_fallback' => $textFallbackResult['response'] ?? null,
            ],
        ];
    }

    private function normalizeTemplateBodyParameters(string $templateName, string $templateLanguage, array $bodyParameters): array
    {
        $templateName = strtolower(trim($templateName));
        if ($templateName === '') {
            return $bodyParameters;
        }

        $expected = $this->expectedBodyParameterCount($templateName, $templateLanguage);
        if ($expected <= 0) {
            return $bodyParameters;
        }

        if (count($bodyParameters) > $expected) {
            // Keep the latest placeholders to avoid desync when older payloads include an extra leading field.
            return array_values(array_slice($bodyParameters, -$expected));
        }

        return $bodyParameters;
    }

    private function expectedBodyParameterCount(string $templateName, string $templateLanguage): int
    {
        $templateName = strtolower(trim($templateName));
        if ($templateName === '') {
            return 0;
        }

        $templateLanguage = $this->normalizeWhatsAppTemplateLanguage($templateLanguage);
        $maxIndex = 0;

        try {
            $pdo = Database::connection();
            $sql = 'SELECT cuerpo_texto, idioma
                    FROM whatsapp_meta_templates
                    WHERE LOWER(TRIM(nombre)) = :name
                    ORDER BY
                      CASE
                        WHEN LOWER(REPLACE(TRIM(idioma), "-", "_")) = LOWER(REPLACE(:lang, "-", "_")) THEN 0
                        ELSE 1
                      END,
                      id DESC
                    LIMIT 1';
            $stmt = $pdo->prepare($sql);
            $stmt->execute([
                ':name' => $templateName,
                ':lang' => $templateLanguage,
            ]);
            $row = $stmt->fetch();
            $bodyText = trim((string) ($row['cuerpo_texto'] ?? ''));
            if ($bodyText === '') {
                return 0;
            }
            $matches = [];
            $hitCount = preg_match_all('/\{\{(\d+)\}\}/', $bodyText, $matches);
            if ($hitCount > 0 && is_array($matches[1] ?? null)) {
                foreach (($matches[1] ?? []) as $indexRaw) {
                    $index = (int) $indexRaw;
                    if ($index > $maxIndex) {
                        $maxIndex = $index;
                    }
                }
            }
        } catch (\Throwable) {
            return 0;
        }

        return max(0, $maxIndex);
    }

    private function interpolateTemplatePlaceholders(string $message, array $bodyParameters): string
    {
        $message = trim($message);
        if ($message === '' || $bodyParameters === []) {
            return $message;
        }

        return preg_replace_callback('/\{\{(\d+)\}\}/', static function (array $matches) use ($bodyParameters): string {
            $index = max(1, (int) ($matches[1] ?? 0)) - 1;
            $value = trim((string) ($bodyParameters[$index] ?? ''));
            return $value !== '' ? $value : '';
        }, $message) ?? $message;
    }

    private function sendWhatsAppViaMeta(string $normalizedPhone, string $message): array
    {
        $apiUrl = rtrim(trim($this->runtimeValue(
            self::KEY_WHATSAPP_API_URL,
            (string) config('notifications.whatsapp.api_url', 'https://graph.facebook.com/v20.0')
        )), '/');
        $phoneNumberId = trim($this->runtimeValue(
            self::KEY_WHATSAPP_PHONE_ID,
            (string) config('notifications.whatsapp.phone_number_id', '')
        ));
        $accessToken = trim($this->runtimeValue(
            self::KEY_WHATSAPP_TOKEN,
            (string) config('notifications.whatsapp.access_token', '')
        ));

        if ($apiUrl === '' || $phoneNumberId === '' || $accessToken === '') {
            return [
                'ok' => false,
                'error' => 'WhatsApp no configurado (api_url / phone_number_id / access_token).',
                'response' => null,
            ];
        }

        if (!function_exists('curl_init')) {
            return [
                'ok' => false,
                'error' => 'cURL no está disponible para envío WhatsApp.',
                'response' => null,
            ];
        }

        $endpoint = $apiUrl . '/' . rawurlencode($phoneNumberId) . '/messages';
        $payload = [
            'messaging_product' => 'whatsapp',
            'recipient_type' => 'individual',
            'to' => $normalizedPhone,
            'type' => 'text',
            'text' => [
                'body' => trim($message),
            ],
        ];

        $ch = curl_init($endpoint);
        if ($ch === false) {
            return [
                'ok' => false,
                'error' => 'No se pudo inicializar cURL para WhatsApp.',
                'response' => null,
            ];
        }

        $jsonPayload = json_encode($payload, JSON_UNESCAPED_UNICODE);
        if ($jsonPayload === false) {
            return [
                'ok' => false,
                'error' => 'No se pudo serializar payload WhatsApp.',
                'response' => null,
            ];
        }

        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
            CURLOPT_POSTFIELDS => $jsonPayload,
            CURLOPT_CONNECTTIMEOUT => 10,
            CURLOPT_TIMEOUT => 20,
            CURLOPT_HTTPHEADER => [
                'Authorization: Bearer ' . $accessToken,
                'Content-Type: application/json',
                'Accept: application/json',
            ],
        ]);

        $raw = curl_exec($ch);
        $status = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $error = trim((string) curl_error($ch));
        curl_close($ch);

        if ($error !== '') {
            return [
                'ok' => false,
                'error' => 'Error cURL WhatsApp: ' . $error,
                'response' => $raw,
            ];
        }

        $decoded = null;
        if (is_string($raw) && trim($raw) !== '') {
            $decodedRaw = json_decode($raw, true);
            if (is_array($decodedRaw)) {
                $decoded = $decodedRaw;
            }
        }

        if ($status >= 200 && $status < 300) {
            return [
                'ok' => true,
                'error' => null,
                'response' => [
                    'status' => $status,
                    'body' => $decoded ?? $raw,
                ],
            ];
        }

        $errorMessage = 'WhatsApp API respondió HTTP ' . $status . '.';
        if (is_array($decoded) && is_array($decoded['error'] ?? null)) {
            $apiMessage = trim((string) ($decoded['error']['message'] ?? ''));
            if ($apiMessage !== '') {
                $errorMessage .= ' ' . $apiMessage;
            }
        }

        return [
            'ok' => false,
            'error' => $errorMessage,
            'response' => [
                'status' => $status,
                'body' => $decoded ?? $raw,
            ],
        ];
    }

    private function sendWhatsAppTemplateViaMeta(
        string $normalizedPhone,
        string $templateName,
        string $languageCode,
        array $bodyParameters,
        ?string $paymentLink = null
    ): array {
        $apiUrl = rtrim(trim($this->runtimeValue(
            self::KEY_WHATSAPP_API_URL,
            (string) config('notifications.whatsapp.api_url', 'https://graph.facebook.com/v20.0')
        )), '/');
        $phoneNumberId = trim($this->runtimeValue(
            self::KEY_WHATSAPP_PHONE_ID,
            (string) config('notifications.whatsapp.phone_number_id', '')
        ));
        $accessToken = trim($this->runtimeValue(
            self::KEY_WHATSAPP_TOKEN,
            (string) config('notifications.whatsapp.access_token', '')
        ));

        if ($apiUrl === '' || $phoneNumberId === '' || $accessToken === '') {
            return [
                'ok' => false,
                'error' => 'WhatsApp no configurado (api_url / phone_number_id / access_token).',
                'response' => null,
            ];
        }

        if (!function_exists('curl_init')) {
            return [
                'ok' => false,
                'error' => 'cURL no está disponible para envío WhatsApp.',
                'response' => null,
            ];
        }

        $endpoint = $apiUrl . '/' . rawurlencode($phoneNumberId) . '/messages';
        $allParameters = [];
        foreach ($bodyParameters as $value) {
            $textValue = trim((string) $value);
            if ($textValue === '') {
                continue;
            }
            $allParameters[] = [
                'type' => 'text',
                'text' => $textValue,
            ];
        }

        $templateEntry = $this->resolveTemplateCatalogEntry($templateName, $languageCode);
        $headerExpected = $this->countTemplatePlaceholders((string) ($templateEntry['encabezado_texto'] ?? ''));
        $bodyExpected = $this->countTemplatePlaceholders((string) ($templateEntry['cuerpo_texto'] ?? ''));

        $allParameterValues = array_map(
            static fn (array $parameter): string => (string) ($parameter['text'] ?? ''),
            $allParameters
        );
        $expectedTotal = max(0, $headerExpected + $bodyExpected);
        if ($expectedTotal > 0 && count($allParameterValues) > $expectedTotal) {
            $allParameterValues = array_values(array_slice($allParameterValues, -$expectedTotal));
        }

        $components = [];
        if ($headerExpected > 0) {
            $headerValues = array_values(array_slice($allParameterValues, 0, $headerExpected));
            if (count($headerValues) < $headerExpected) {
                return [
                    'ok' => false,
                    'error' => 'La plantilla requiere parámetros de encabezado y no se recibieron completos.',
                    'response' => null,
                ];
            }
            $components[] = [
                'type' => 'header',
                'parameters' => array_map(static fn (string $value): array => [
                    'type' => 'text',
                    'text' => $value,
                ], $headerValues),
            ];
        }

        if ($bodyExpected > 0) {
            $offset = $headerExpected > 0 ? $headerExpected : 0;
            $bodyValues = array_values(array_slice($allParameterValues, $offset, $bodyExpected));
            if (count($bodyValues) < $bodyExpected) {
                return [
                    'ok' => false,
                    'error' => 'La plantilla requiere parámetros de cuerpo y no se recibieron completos.',
                    'response' => null,
                ];
            }
            $components[] = [
                'type' => 'body',
                'parameters' => array_map(static fn (string $value): array => [
                    'type' => 'text',
                    'text' => $value,
                ], $bodyValues),
            ];
        } elseif ($allParameters !== []) {
            // Backward compatibility for templates not present in local catalog.
            $components[] = [
                'type' => 'body',
                'parameters' => $allParameters,
            ];
        }

        $templatePayload = [
            'name' => $templateName,
            'language' => [
                'code' => $languageCode,
            ],
        ];
        if ($components !== []) {
            $templatePayload['components'] = $components;
        }

        $payload = [
            'messaging_product' => 'whatsapp',
            'recipient_type' => 'individual',
            'to' => $normalizedPhone,
            'type' => 'template',
            'template' => $templatePayload,
        ];

        $buttonComponent = $this->buildTemplateUrlButtonComponent(
            $templateName,
            $languageCode,
            trim((string) $paymentLink)
        );
        if ($buttonComponent !== null) {
            if (!is_array($payload['template']['components'] ?? null)) {
                $payload['template']['components'] = [];
            }
            $payload['template']['components'][] = $buttonComponent;
        }

        $ch = curl_init($endpoint);
        if ($ch === false) {
            return [
                'ok' => false,
                'error' => 'No se pudo inicializar cURL para WhatsApp.',
                'response' => null,
            ];
        }

        $jsonPayload = json_encode($payload, JSON_UNESCAPED_UNICODE);
        if ($jsonPayload === false) {
            return [
                'ok' => false,
                'error' => 'No se pudo serializar payload de plantilla WhatsApp.',
                'response' => null,
            ];
        }

        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
            CURLOPT_POSTFIELDS => $jsonPayload,
            CURLOPT_CONNECTTIMEOUT => 10,
            CURLOPT_TIMEOUT => 20,
            CURLOPT_HTTPHEADER => [
                'Authorization: Bearer ' . $accessToken,
                'Content-Type: application/json',
                'Accept: application/json',
            ],
        ]);

        $raw = curl_exec($ch);
        $status = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $error = trim((string) curl_error($ch));
        curl_close($ch);

        if ($error !== '') {
            return [
                'ok' => false,
                'error' => 'Error cURL WhatsApp template: ' . $error,
                'response' => $raw,
            ];
        }

        $decoded = null;
        if (is_string($raw) && trim($raw) !== '') {
            $decodedRaw = json_decode($raw, true);
            if (is_array($decodedRaw)) {
                $decoded = $decodedRaw;
            }
        }

        if ($status >= 200 && $status < 300) {
            return [
                'ok' => true,
                'error' => null,
                'response' => [
                    'status' => $status,
                    'body' => $decoded ?? $raw,
                ],
            ];
        }

        $errorMessage = 'WhatsApp template API respondió HTTP ' . $status . '.';
        if (is_array($decoded) && is_array($decoded['error'] ?? null)) {
            $apiMessage = trim((string) ($decoded['error']['message'] ?? ''));
            if ($apiMessage !== '') {
                $errorMessage .= ' ' . $apiMessage;
            }
        }

        return [
            'ok' => false,
            'error' => $errorMessage,
            'response' => [
                'status' => $status,
                'body' => $decoded ?? $raw,
            ],
        ];
    }

    private function buildTemplateUrlButtonComponent(string $templateName, string $templateLanguage, string $paymentLink): ?array
    {
        $template = $this->resolveTemplateCatalogEntry($templateName, $templateLanguage);
        if ($template === null) {
            return null;
        }

        $buttonUrl = trim((string) ($template['boton_url'] ?? ''));
        if ($buttonUrl === '') {
            return null;
        }

        $placeholderIndexes = [];
        $matches = [];
        $hitCount = preg_match_all('/\{\{(\d+)\}\}/', $buttonUrl, $matches);
        if ($hitCount > 0 && is_array($matches[1] ?? null)) {
            foreach (($matches[1] ?? []) as $indexRaw) {
                $placeholderIndexes[] = max(1, (int) $indexRaw);
            }
        }
        $placeholderIndexes = array_values(array_unique($placeholderIndexes));
        sort($placeholderIndexes);
        if ($placeholderIndexes === []) {
            return null;
        }

        $paymentLink = trim($paymentLink);
        if ($paymentLink === '') {
            return null;
        }

        $replacementValues = $this->resolveTemplateUrlButtonValues($buttonUrl, $paymentLink, $placeholderIndexes);
        if ($replacementValues === []) {
            return null;
        }

        $parameters = [];
        foreach ($placeholderIndexes as $index) {
            $value = trim((string) ($replacementValues[$index] ?? ''));
            if ($value === '') {
                return null;
            }
            $parameters[] = [
                'type' => 'text',
                'text' => $value,
            ];
        }
        if ($parameters === []) {
            return null;
        }

        return [
            'type' => 'button',
            'sub_type' => 'url',
            'index' => '0',
            'parameters' => $parameters,
        ];
    }

    private function resolveTemplateUrlButtonValues(string $templateUrl, string $paymentLink, array $placeholderIndexes): array
    {
        $templateUrl = trim($templateUrl);
        $paymentLink = trim($paymentLink);
        if ($templateUrl === '' || $paymentLink === '' || $placeholderIndexes === []) {
            return [];
        }

        $values = [];

        $segments = preg_split('/\{\{\d+\}\}/', $templateUrl) ?: [];
        $pattern = '/^';
        foreach ($segments as $index => $segment) {
            $pattern .= preg_quote($segment, '/');
            if ($index < count($segments) - 1) {
                $pattern .= '(.*?)';
            }
        }
        $pattern .= '$/';

        $captures = [];
        if (preg_match($pattern, $paymentLink, $captures) === 1) {
            for ($i = 1; $i < count($captures); $i++) {
                $placeholderIndex = (int) ($placeholderIndexes[$i - 1] ?? 0);
                if ($placeholderIndex > 0) {
                    $values[$placeholderIndex] = (string) ($captures[$i] ?? '');
                }
            }
            if ($values !== []) {
                return $values;
            }
        }

        parse_str((string) parse_url($paymentLink, PHP_URL_QUERY), $paymentQuery);
        $reference = trim((string) ($paymentQuery['reference'] ?? ''));
        if ($reference !== '' && count($placeholderIndexes) === 1) {
            $values[$placeholderIndexes[0]] = $reference;
            return $values;
        }

        return [];
    }

    private function resolveTemplateCatalogEntry(string $templateName, string $templateLanguage): ?array
    {
        $templateName = strtolower(trim($templateName));
        if ($templateName === '') {
            return null;
        }

        $templateLanguage = $this->normalizeWhatsAppTemplateLanguage($templateLanguage);
        $cacheKey = $templateName . '|' . strtolower($templateLanguage);
        if (array_key_exists($cacheKey, self::$templateCatalogCache ?? [])) {
            $cached = self::$templateCatalogCache[$cacheKey];
            return is_array($cached) ? $cached : null;
        }

        $row = null;
        try {
            $pdo = Database::connection();
            $sql = 'SELECT nombre, idioma, encabezado_texto, cuerpo_texto, boton_url
                    FROM whatsapp_meta_templates
                    WHERE LOWER(TRIM(nombre)) = :name
                    ORDER BY
                      CASE
                        WHEN LOWER(REPLACE(TRIM(idioma), "-", "_")) = LOWER(REPLACE(:lang, "-", "_")) THEN 0
                        ELSE 1
                      END,
                      id DESC
                    LIMIT 1';
            $stmt = $pdo->prepare($sql);
            $stmt->execute([
                ':name' => $templateName,
                ':lang' => $templateLanguage,
            ]);
            $found = $stmt->fetch();
            if (is_array($found)) {
                $row = $found;
            }
        } catch (\Throwable) {
            $row = null;
        }

        if (self::$templateCatalogCache === null) {
            self::$templateCatalogCache = [];
        }
        self::$templateCatalogCache[$cacheKey] = $row;

        return is_array($row) ? $row : null;
    }

    private function countTemplatePlaceholders(string $text): int
    {
        $text = trim($text);
        if ($text === '') {
            return 0;
        }

        $matches = [];
        $hitCount = preg_match_all('/\{\{(\d+)\}\}/', $text, $matches);
        if ($hitCount < 1 || !is_array($matches[1] ?? null)) {
            return 0;
        }

        $maxIndex = 0;
        foreach (($matches[1] ?? []) as $indexRaw) {
            $index = max(1, (int) $indexRaw);
            if ($index > $maxIndex) {
                $maxIndex = $index;
            }
        }

        return $maxIndex;
    }

    private function resolveWhatsAppTemplateConfig(string $noticeType): array
    {
        $noticeType = strtolower(trim($noticeType));
        if ($noticeType === '') {
            return [
                'name' => '',
                'language' => $this->normalizeWhatsAppTemplateLanguage(
                    $this->runtimeValue(
                        self::KEY_WHATSAPP_TEMPLATE_LANGUAGE,
                        (string) config('notifications.whatsapp.template_language', 'es_CO')
                    )
                ),
            ];
        }

        $map = $this->whatsappTemplateNoticeMap();
        if (isset($map[$noticeType]) && is_array($map[$noticeType])) {
            return [
                'name' => strtolower(trim((string) ($map[$noticeType]['name'] ?? ''))),
                'language' => $this->normalizeWhatsAppTemplateLanguage((string) ($map[$noticeType]['language'] ?? 'es_CO')),
            ];
        }

        return [
            'name' => '',
            'language' => $this->normalizeWhatsAppTemplateLanguage(
                $this->runtimeValue(
                    self::KEY_WHATSAPP_TEMPLATE_LANGUAGE,
                    (string) config('notifications.whatsapp.template_language', 'es_CO')
                )
            ),
        ];
    }

    public function whatsappTemplateNoticeMap(): array
    {
        if (self::$templateNoticeMapCache !== null) {
            return self::$templateNoticeMapCache;
        }

        $map = [];
        $defaultLanguage = $this->normalizeWhatsAppTemplateLanguage(
            $this->runtimeValue(
                self::KEY_WHATSAPP_TEMPLATE_LANGUAGE,
                (string) config('notifications.whatsapp.template_language', 'es_CO')
            )
        );

        try {
            $pdo = Database::connection();
            $stmt = $pdo->query(
                'SELECT uso, nombre, idioma
                 FROM whatsapp_meta_templates
                 WHERE TRIM(COALESCE(uso, "")) <> ""
                   AND LOWER(TRIM(uso)) <> "libre"
                 ORDER BY id DESC'
            );
            $rows = $stmt->fetchAll() ?: [];
            foreach ($rows as $row) {
                $noticeType = strtolower(trim((string) ($row['uso'] ?? '')));
                $templateName = strtolower(trim((string) ($row['nombre'] ?? '')));
                if ($noticeType === '' || $templateName === '') {
                    continue;
                }
                if (isset($map[$noticeType])) {
                    continue;
                }
                $map[$noticeType] = [
                    'name' => $templateName,
                    'language' => $this->normalizeWhatsAppTemplateLanguage((string) ($row['idioma'] ?? $defaultLanguage)),
                ];
            }
        } catch (\Throwable) {
        }

        $legacyTemplate10d = strtolower(trim($this->runtimeValue(
            self::KEY_WHATSAPP_TEMPLATE_NAME_10D,
            (string) config('notifications.whatsapp.template_name_10d', '')
        )));
        if ($legacyTemplate10d !== '' && !isset($map['previo_10_dias'])) {
            $map['previo_10_dias'] = [
                'name' => $legacyTemplate10d,
                'language' => $defaultLanguage,
            ];
        }

        $legacyTemplate3d = strtolower(trim($this->runtimeValue(
            self::KEY_WHATSAPP_TEMPLATE_NAME_3D,
            (string) config('notifications.whatsapp.template_name_3d', '')
        )));
        if ($legacyTemplate3d !== '' && !isset($map['previo_3_dias'])) {
            $map['previo_3_dias'] = [
                'name' => $legacyTemplate3d,
                'language' => $defaultLanguage,
            ];
        }

        self::$templateNoticeMapCache = $map;
        return self::$templateNoticeMapCache;
    }

    private function normalizeWhatsAppTemplateLanguage(string $languageCode): string
    {
        $languageCode = trim($languageCode);
        if ($languageCode === '') {
            return 'es_CO';
        }

        if (preg_match('/^[a-z]{2}([-_][A-Za-z]{2})?$/', $languageCode) !== 1) {
            return 'es_CO';
        }

        if (str_contains($languageCode, '-')) {
            [$lang, $country] = array_pad(explode('-', $languageCode, 2), 2, '');
            $country = strtoupper(trim($country));
            $lang = strtolower(trim($lang));

            return $country !== '' ? ($lang . '_' . $country) : $lang;
        }

        if (str_contains($languageCode, '_')) {
            [$lang, $country] = array_pad(explode('_', $languageCode, 2), 2, '');
            $country = strtoupper(trim($country));
            $lang = strtolower(trim($lang));

            return $country !== '' ? ($lang . '_' . $country) : $lang;
        }

        return strtolower($languageCode);
    }

    private function sendWhatsAppViaManyChat(string $normalizedPhone, string $message): array
    {
        if (!function_exists('curl_init')) {
            return [
                'ok' => false,
                'error' => 'cURL no está disponible para envío WhatsApp.',
                'response' => null,
            ];
        }

        $apiBase = rtrim(trim($this->runtimeValue(
            self::KEY_MANYCHAT_API_URL,
            (string) config('notifications.whatsapp.manychat_api_url', 'https://api.manychat.com')
        )), '/');
        $apiToken = trim($this->runtimeValue(
            self::KEY_MANYCHAT_TOKEN,
            (string) config('notifications.whatsapp.manychat_access_token', '')
        ));
        $messageTag = strtoupper(trim($this->runtimeValue(
            self::KEY_MANYCHAT_MESSAGE_TAG,
            (string) config('notifications.whatsapp.manychat_message_tag', 'ACCOUNT_UPDATE')
        )));

        if ($apiBase === '' || $apiToken === '') {
            return [
                'ok' => false,
                'error' => 'ManyChat no configurado (api_url / access_token).',
                'response' => null,
            ];
        }

        [$subscriberId, $resolveError] = $this->manyChatResolveSubscriberId($apiBase, $apiToken, $normalizedPhone);
        if ($subscriberId <= 0) {
            return [
                'ok' => false,
                'error' => $resolveError !== ''
                    ? $resolveError
                    : 'No fue posible resolver el contacto en ManyChat para WhatsApp.',
                'response' => null,
            ];
        }

        $payload = [
            'subscriber_id' => $subscriberId,
            'data' => [
                'version' => 'v2',
                'content' => [
                    'type' => 'whatsapp',
                    'messages' => [
                        [
                            'type' => 'text',
                            'text' => trim($message),
                        ],
                    ],
                    'actions' => [],
                    'quick_replies' => [],
                ],
            ],
        ];
        if ($messageTag !== '') {
            $payload['message_tag'] = $messageTag;
        }

        $result = $this->manyChatRequest(
            $apiBase,
            $apiToken,
            '/fb/sending/sendContent',
            $payload,
            'POST'
        );
        if ((bool) ($result['ok'] ?? false)) {
            return [
                'ok' => true,
                'error' => null,
                'response' => [
                    'provider' => 'manychat',
                    'status' => (int) ($result['status'] ?? 0),
                    'body' => $result['body'] ?? null,
                ],
            ];
        }

        $status = (int) ($result['status'] ?? 0);
        $error = trim((string) ($result['error'] ?? 'Error desconocido ManyChat.'));
        if ($status > 0 && $error === '') {
            $error = 'ManyChat API respondió HTTP ' . $status . '.';
        }

        return [
            'ok' => false,
            'error' => $error !== '' ? $error : 'ManyChat API rechazó el envío.',
            'response' => [
                'provider' => 'manychat',
                'status' => $status,
                'body' => $result['body'] ?? null,
            ],
        ];
    }

    private function manyChatResolveSubscriberId(string $apiBase, string $apiToken, string $phone): array
    {
        $errors = [];
        $phone = ltrim(trim($phone), '+');
        $phoneWithPlus = '+' . $phone;
        $createPayloads = [
            ['whatsapp_phone' => $phoneWithPlus],
            ['whatsapp_phone' => $phone],
        ];
        $alreadyExists = false;
        $validationError = false;

        foreach ($createPayloads as $createPayload) {
            $createResult = $this->manyChatRequest(
                $apiBase,
                $apiToken,
                '/fb/subscriber/createSubscriber',
                $createPayload,
                'POST'
            );

            if ((bool) ($createResult['ok'] ?? false)) {
                $createdId = $this->extractManyChatSubscriberId($createResult['body'] ?? null);
                if ($createdId > 0) {
                    return [$createdId, ''];
                }
                $errors[] = 'ManyChat createSubscriber respondió OK pero sin subscriber_id.';
            } else {
                $createError = trim((string) ($createResult['error'] ?? ''));
                if ($createError !== '') {
                    $errors[] = 'createSubscriber: ' . $createError;
                    $lowerError = strtolower($createError);
                    if (str_contains($lowerError, 'already')) {
                        $alreadyExists = true;
                    }
                    if (str_contains($lowerError, 'validation error')) {
                        $validationError = true;
                    }
                }
            }
        }

        $compactErrors = array_values(array_unique(array_filter(array_map('trim', $errors), static fn (string $v): bool => $v !== '')));
        $detail = $compactErrors !== [] ? implode(' | ', array_slice($compactErrors, 0, 3)) : '';

        if ($alreadyExists) {
            $subscriberId = $this->manyChatFindSubscriberByPhone($apiBase, $apiToken, $phoneWithPlus, $errors);
            if ($subscriberId <= 0) {
                $subscriberId = $this->manyChatFindSubscriberByCustomField($apiBase, $apiToken, $phoneWithPlus, $errors);
            }
            if ($subscriberId > 0) {
                return [$subscriberId, ''];
            }

            $compactErrors = array_values(array_unique(array_filter(array_map('trim', $errors), static fn (string $v): bool => $v !== '')));
            $detail = $compactErrors !== [] ? implode(' | ', array_slice($compactErrors, 0, 3)) : '';
            $message = 'ManyChat indica que el contacto ya existe, pero no entrega subscriber_id en esta respuesta.'
                . ' Para enviar por API necesitas el Contact ID de ManyChat o que ese contacto tenga el campo Phone'
                . ' para poder resolverlo por findBySystemField (tambien puedes configurar un Custom Field ID en Veltrix).';
            if ($detail !== '') {
                $message .= ' Detalle API: ' . $detail;
            }

            return [0, $message];
        }

        $message = 'No fue posible crear/resolver el contacto en ManyChat para WhatsApp.'
            . ' Verifica que el canal WhatsApp esté conectado y que tu plan tenga habilitada la importación de contactos por API.';
        if ($validationError) {
            $message .= ' ManyChat respondió "Validation error", usualmente por plan/permisos o parámetros no habilitados para esa cuenta.';
        }
        if ($detail !== '') {
            $message .= ' Detalle API: ' . $detail;
        }

        return [0, $message];
    }

    private function manyChatFindSubscriberByPhone(string $apiBase, string $apiToken, string $phone, array &$errors = []): int
    {
        $phone = ltrim(trim($phone), '+');
        if ($phone === '') {
            return 0;
        }

        $phoneWithPlus = '+' . $phone;
        $attempts = [
            [
                'phone' => $phoneWithPlus,
            ],
            [
                'phone' => $phone,
            ],
        ];

        foreach ($attempts as $query) {
            $result = $this->manyChatRequest(
                $apiBase,
                $apiToken,
                '/fb/subscriber/findBySystemField',
                $query,
                'GET'
            );

            if (!(bool) ($result['ok'] ?? false)) {
                $findError = trim((string) ($result['error'] ?? ''));
                if ($findError !== '') {
                    $errors[] = 'findBySystemField: ' . $findError;
                }
                continue;
            }

            $subscriberId = $this->extractManyChatSubscriberId($result['body'] ?? null);
            if ($subscriberId > 0) {
                return $subscriberId;
            }
        }

        return 0;
    }

    private function manyChatFindSubscriberByCustomField(string $apiBase, string $apiToken, string $phone, array &$errors = []): int
    {
        $phone = ltrim(trim($phone), '+');
        if ($phone === '') {
            return 0;
        }

        $fieldIds = $this->manyChatResolveCustomFieldIds($apiBase, $apiToken, $errors);
        if ($fieldIds === []) {
            return 0;
        }

        $fieldValues = array_values(array_unique(['+' . $phone, $phone]));

        foreach ($fieldIds as $fieldId) {
            foreach ($fieldValues as $fieldValue) {
                $result = $this->manyChatRequest(
                    $apiBase,
                    $apiToken,
                    '/fb/subscriber/findByCustomField',
                    [
                        'field_id' => $fieldId,
                        'field_value' => $fieldValue,
                    ],
                    'GET'
                );

                if (!(bool) ($result['ok'] ?? false)) {
                    $findError = trim((string) ($result['error'] ?? ''));
                    if ($findError !== '') {
                        $errors[] = 'findByCustomField(' . $fieldId . '): ' . $findError;
                    }
                    continue;
                }

                $subscriberId = $this->extractManyChatSubscriberId($result['body'] ?? null);
                if ($subscriberId > 0) {
                    return $subscriberId;
                }
            }
        }

        $errors[] = 'findByCustomField sin coincidencias en field_id: ' . implode(',', array_slice($fieldIds, 0, 5));

        return 0;
    }

    private function manyChatResolveCustomFieldIds(string $apiBase, string $apiToken, array &$errors = []): array
    {
        $fieldIds = [];
        $configuredRaw = trim($this->runtimeValue(
            self::KEY_MANYCHAT_CONTACT_FIELD_ID,
            (string) config('notifications.whatsapp.manychat_contact_field_id', '')
        ));
        if ($configuredRaw !== '') {
            $segments = array_filter(array_map('trim', explode(',', $configuredRaw)), static fn (string $v): bool => $v !== '');
            foreach ($segments as $segment) {
                if (preg_match('/^\d{1,20}$/', $segment) === 1) {
                    $fieldIds[] = $segment;
                }
            }
        }

        $result = $this->manyChatRequest(
            $apiBase,
            $apiToken,
            '/fb/page/getCustomFields',
            [],
            'GET'
        );
        if (!(bool) ($result['ok'] ?? false)) {
            $error = trim((string) ($result['error'] ?? ''));
            if ($error !== '') {
                $errors[] = 'getCustomFields: ' . $error;
            }

            return array_values(array_unique($fieldIds));
        }

        $customFields = $this->extractManyChatCustomFields($result['body'] ?? null);
        foreach ($customFields as $field) {
            $fieldId = (string) max(
                0,
                (int) ($field['id'] ?? 0),
                (int) ($field['field_id'] ?? 0)
            );
            if ($fieldId === '0') {
                continue;
            }

            $name = $this->manyChatNormalizeSearchLabel((string) ($field['name'] ?? ($field['title'] ?? '')));
            $systemName = $this->manyChatNormalizeSearchLabel((string) ($field['system_name'] ?? ($field['slug'] ?? '')));
            if (
                $this->manyChatLooksLikePhoneField($name)
                || $this->manyChatLooksLikePhoneField($systemName)
            ) {
                $fieldIds[] = $fieldId;
            }
        }

        return array_values(array_unique($fieldIds));
    }

    private function extractManyChatCustomFields(mixed $body): array
    {
        if (!is_array($body)) {
            return [];
        }

        $fields = [];
        $this->collectManyChatCustomFields($body, $fields);

        return $fields;
    }

    private function collectManyChatCustomFields(mixed $value, array &$fields): void
    {
        if (!is_array($value)) {
            return;
        }

        $fieldId = max(0, (int) ($value['id'] ?? 0), (int) ($value['field_id'] ?? 0));
        if (
            $fieldId > 0
            && (
                isset($value['name'])
                || isset($value['title'])
                || isset($value['system_name'])
                || isset($value['slug'])
            )
        ) {
            $fields[] = $value;
        }

        foreach ($value as $item) {
            if (is_array($item)) {
                $this->collectManyChatCustomFields($item, $fields);
            }
        }
    }

    private function manyChatNormalizeSearchLabel(string $value): string
    {
        $value = strtolower(trim($value));
        if ($value === '') {
            return '';
        }

        $value = str_replace(['_', '-'], ' ', $value);
        $value = preg_replace('/\s+/', ' ', $value) ?: $value;

        return trim($value);
    }

    private function manyChatLooksLikePhoneField(string $label): bool
    {
        if ($label === '') {
            return false;
        }

        $keywords = ['whatsapp', 'wa id', 'waid', 'telefono', 'phone', 'celular', 'movil'];
        foreach ($keywords as $keyword) {
            if (str_contains($label, $keyword)) {
                return true;
            }
        }

        return false;
    }

    private function extractManyChatSubscriberId(mixed $body): int
    {
        if (!is_array($body)) {
            return 0;
        }

        $candidates = [];
        $data = is_array($body['data'] ?? null) ? $body['data'] : [];

        if (isset($body['subscriber_id'])) {
            $candidates[] = (int) $body['subscriber_id'];
        }
        if (isset($data['subscriber_id'])) {
            $candidates[] = (int) $data['subscriber_id'];
        }
        if (isset($data['id'])) {
            $candidates[] = (int) $data['id'];
        }

        if (is_array($data['subscriber'] ?? null)) {
            if (isset($data['subscriber']['id'])) {
                $candidates[] = (int) $data['subscriber']['id'];
            }
            if (isset($data['subscriber']['subscriber_id'])) {
                $candidates[] = (int) $data['subscriber']['subscriber_id'];
            }
        }

        foreach (['subscribers', 'contacts', 'items'] as $listKey) {
            $list = $data[$listKey] ?? null;
            if (!is_array($list)) {
                continue;
            }
            foreach ($list as $item) {
                if (!is_array($item)) {
                    continue;
                }
                if (isset($item['subscriber_id'])) {
                    $candidates[] = (int) $item['subscriber_id'];
                }
                if (isset($item['id'])) {
                    $candidates[] = (int) $item['id'];
                }
            }
        }

        foreach ($candidates as $candidate) {
            if ($candidate > 0) {
                return $candidate;
            }
        }

        return 0;
    }

    private function manyChatRequest(
        string $apiBase,
        string $apiToken,
        string $path,
        array $payload,
        string $method
    ): array {
        $method = strtoupper(trim($method));
        if (!in_array($method, ['GET', 'POST'], true)) {
            $method = 'POST';
        }

        $endpoint = rtrim($apiBase, '/') . '/' . ltrim($path, '/');
        $requestBody = null;
        if ($method === 'GET') {
            if ($payload !== []) {
                $endpoint .= '?' . http_build_query($payload);
            }
        } else {
            $requestBody = json_encode($payload, JSON_UNESCAPED_UNICODE);
            if (!is_string($requestBody)) {
                return [
                    'ok' => false,
                    'status' => 0,
                    'error' => 'No se pudo serializar payload ManyChat.',
                    'body' => null,
                ];
            }
        }

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

        $options = [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_CONNECTTIMEOUT => 10,
            CURLOPT_TIMEOUT => 20,
            CURLOPT_HTTPHEADER => [
                'Authorization: Bearer ' . $apiToken,
                'Content-Type: application/json',
                'Accept: application/json',
            ],
        ];
        if ($method === 'POST') {
            $options[CURLOPT_POST] = true;
            $options[CURLOPT_POSTFIELDS] = $requestBody;
        }
        curl_setopt_array($ch, $options);

        $raw = curl_exec($ch);
        $status = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $curlError = trim((string) curl_error($ch));
        curl_close($ch);

        if ($curlError !== '') {
            return [
                'ok' => false,
                'status' => $status,
                'error' => 'Error cURL ManyChat: ' . $curlError,
                'body' => is_string($raw) ? $raw : null,
            ];
        }

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

        $statusText = strtolower(trim((string) (($decoded['status'] ?? ''))));
        $ok = $status >= 200 && $status < 300 && ($decoded === null || $statusText === '' || $statusText === 'success');
        $errorMessage = '';
        if (!$ok) {
            if (is_array($decoded)) {
                $message = trim((string) ($decoded['message'] ?? ''));
                $detail = $this->manyChatExtractDetailText($decoded['details'] ?? null);
                if ($message !== '' && $detail !== '') {
                    $errorMessage = $message . ' (' . $detail . ')';
                } elseif ($message !== '') {
                    $errorMessage = $message;
                } elseif ($detail !== '') {
                    $errorMessage = $detail;
                }
            }

            if ($errorMessage === '') {
                $errorMessage = 'ManyChat API respondió HTTP ' . $status . '.';
            }
        }

        return [
            'ok' => $ok,
            'status' => $status,
            'error' => $errorMessage !== '' ? $errorMessage : null,
            'body' => $decoded ?? (is_string($raw) ? $raw : null),
        ];
    }

    private function manyChatExtractDetailText(mixed $details): string
    {
        $messages = [];
        $this->manyChatCollectErrorDetails($details, $messages);
        $messages = array_values(array_unique(array_filter(array_map('trim', $messages), static fn (string $v): bool => $v !== '')));
        if ($messages === []) {
            return '';
        }

        return implode(' | ', array_slice($messages, 0, 3));
    }

    private function manyChatCollectErrorDetails(mixed $value, array &$messages): void
    {
        if (is_string($value)) {
            $trimmed = trim($value);
            if ($trimmed !== '') {
                $messages[] = $trimmed;
            }

            return;
        }

        if (!is_array($value)) {
            return;
        }

        foreach ($value as $item) {
            if (is_array($item)) {
                $this->manyChatCollectErrorDetails($item, $messages);
                continue;
            }
            if (is_string($item)) {
                $trimmed = trim($item);
                if ($trimmed !== '') {
                    $messages[] = $trimmed;
                }
            }
        }
    }

    public function isChannelEnabled(string $channel): bool
    {
        $channel = strtolower(trim($channel));

        if ($channel === 'email') {
            return $this->toBool(
                $this->runtimeValue(
                    self::KEY_CHANNEL_EMAIL,
                    (string) ((bool) config('notifications.channels.email', true) ? '1' : '0')
                )
            );
        }

        if ($channel === 'whatsapp') {
            return $this->toBool(
                $this->runtimeValue(
                    self::KEY_CHANNEL_WHATSAPP,
                    (string) ((bool) config('notifications.channels.whatsapp', true) ? '1' : '0')
                )
            );
        }

        return false;
    }

    public function maxAttempts(): int
    {
        $fromConfig = max(1, (int) config('notifications.retry.max_attempts', 3));
        $runtime = (int) $this->runtimeValue(self::KEY_RETRY_MAX_ATTEMPTS, (string) $fromConfig);

        return max(1, min(15, $runtime));
    }

    public function retryBackoffMinutes(): array
    {
        $raw = trim($this->runtimeValue(
            self::KEY_RETRY_BACKOFF_MINUTES,
            (string) config('notifications.retry.backoff_minutes', '5,30,180')
        ));

        $parts = array_filter(array_map('trim', explode(',', $raw)), static fn (string $v): bool => $v !== '');
        $minutes = [];

        foreach ($parts as $part) {
            $value = (int) $part;
            if ($value > 0) {
                $minutes[] = $value;
            }
        }

        if ($minutes === []) {
            return [5, 30, 180];
        }

        return array_values(array_unique($minutes));
    }

    public function normalizePhone(string $phone): string
    {
        $phone = preg_replace('/[^0-9+]/', '', trim($phone)) ?: '';
        if ($phone === '') {
            return '';
        }

        if (str_starts_with($phone, '+')) {
            $phone = substr($phone, 1);
        }

        $phone = ltrim($phone, '0');
        if ($phone === '') {
            return '';
        }

        $defaultCountryCode = preg_replace('/[^0-9]/', '', trim($this->runtimeValue(
            self::KEY_WHATSAPP_COUNTRY_CODE,
            (string) config('notifications.whatsapp.default_country_code', '57')
        ))) ?: '57';

        if (strlen($phone) <= 10) {
            $phone = $defaultCountryCode . $phone;
        }

        return $phone;
    }

    private function runtimeValue(string $key, string $fallback): string
    {
        $settings = $this->globalSettings();
        $value = trim((string) ($settings[$key] ?? ''));
        if ($value !== '') {
            return $value;
        }

        return trim($fallback);
    }

    private function globalSettings(): array
    {
        if (self::$settingsCache !== null) {
            return self::$settingsCache;
        }

        $keys = [
            self::KEY_EMAIL_FROM_NAME,
            self::KEY_EMAIL_FROM_ADDRESS,
            self::KEY_EMAIL_REPLY_TO,
            self::KEY_SMTP_HOST,
            self::KEY_SMTP_PORT,
            self::KEY_SMTP_USERNAME,
            self::KEY_SMTP_PASSWORD,
            self::KEY_SMTP_SECURE,
            self::KEY_SMTP_FROM_EMAIL,
            self::KEY_SMTP_FROM_NAME,
            self::KEY_WHATSAPP_API_URL,
            self::KEY_WHATSAPP_PHONE_ID,
            self::KEY_WHATSAPP_TOKEN,
            self::KEY_WHATSAPP_COUNTRY_CODE,
            self::KEY_WHATSAPP_PROVIDER,
            self::KEY_MANYCHAT_API_URL,
            self::KEY_MANYCHAT_TOKEN,
            self::KEY_MANYCHAT_MESSAGE_TAG,
            self::KEY_MANYCHAT_CONTACT_FIELD_ID,
            self::KEY_WHATSAPP_TEMPLATE_ENABLED,
            self::KEY_WHATSAPP_TEMPLATE_LANGUAGE,
            self::KEY_WHATSAPP_TEMPLATE_NAME_10D,
            self::KEY_WHATSAPP_TEMPLATE_NAME_3D,
            self::KEY_CHANNEL_EMAIL,
            self::KEY_CHANNEL_WHATSAPP,
            self::KEY_RETRY_MAX_ATTEMPTS,
            self::KEY_RETRY_BACKOFF_MINUTES,
        ];

        try {
            self::$settingsCache = (new GlobalSetting())->getMany($keys);
        } catch (\Throwable) {
            self::$settingsCache = [];
        }

        return self::$settingsCache;
    }

    private function toBool(string $value): bool
    {
        $normalized = strtolower(trim($value));

        return in_array($normalized, ['1', 'true', 'yes', 'si', 'on'], true);
    }
}
