<?php

declare(strict_types=1);

namespace SmsNotifier\Facade;

use GuzzleHttp\Client;
use SmsNotifier\Data\NotificationData;
use SmsNotifier\Facade\ClientCallBellAPI;
use SmsNotifier\Factory\MessageTextFactory;
use SmsNotifier\Service\Logger;
use SmsNotifier\Service\SmsNumberProvider;
use Ubnt\UcrmPluginSdk\Service\UcrmApi;
use Ubnt\UcrmPluginSdk\Service\PluginConfigManager;
use \DateTime;

abstract class AbstractMessageNotifierFacade
{
    protected $logger;
    protected $messageTextFactory;
    protected $clientPhoneNumber;
    protected $ucrmApi;

    const SUBJECT_OF_INSTALLER_CHANGE = ["se ha cancelado una tarea que tenías asignada con el folio ", "se te ha desasignado❌ la tarea con el folio "];
    const ADDITIONAL_CHANGE_DATA = ["Ya no es necesario realizar la visita técnica.", "En tu lugar asistirá el técnico 👷🏻‍♂️➡️ "];

    public function __construct(Logger $logger, MessageTextFactory $messageTextFactory, SmsNumberProvider $clientPhoneNumber)
    {
        $this->logger = $logger;
        $this->messageTextFactory = $messageTextFactory;
        $this->clientPhoneNumber = $clientPhoneNumber;

        $config = PluginConfigManager::create()->loadConfig();
        $ipServer = $config['ipserver'] ?? 'localhost';
        $apiUrl = "https://$ipServer/crm/api/v1.0/";

        $client = new Client([
            'base_uri' => $apiUrl,
            'verify' => false,
        ]);

        $this->ucrmApi = new UcrmApi($client, $config['apitoken'] ?? '');
    }

    public function verifyPaymentActionToDo(NotificationData $notificationData): void
    {
        $arrayPhones = $this->clientPhoneNumber->getUcrmClientNumbers($notificationData, null);
        foreach ($arrayPhones as $type => $phones) {
            $type = trim(strtolower($type));
            if (!is_array($phones)) continue;
            foreach ($phones as $phone) {
                switch ($type) {
                    case 'whatsapp':
                        $this->notifyAndUpdate($notificationData, $phone);
                        break;
                    case 'whatsnotifica':
                        $this->notify($notificationData, $phone);
                        break;
                    case 'whatsactualiza':
                        $this->onlyUpdate($notificationData, $phone);
                        break;
                }
            }
        }
    }

    public function verifyClientActionToDo(NotificationData $notificationData): void
    {
        $arrayPhones = $this->clientPhoneNumber->getUcrmClientNumbers($notificationData, null);
        foreach ($arrayPhones as $type => $phones) {
            $type = trim(strtolower($type));
            if (!is_array($phones)) continue;
            foreach ($phones as $phone) {
                if ($type === 'whatsapp' || $type === 'whatsactualiza') $this->onlyUpdate($notificationData, $phone);
            }
        }
    }

    public function verifyServiceActionToDo(NotificationData $notificationData): void
    {
        $arrayPhones = $this->clientPhoneNumber->getUcrmClientNumbers($notificationData, null);
        foreach ($arrayPhones as $type => $phones) {
            $type = trim(strtolower($type));
            if (!is_array($phones)) continue;
            foreach ($phones as $phone) {
                if ($type === 'whatsapp' || $type === 'whatsactualiza') $this->onlyUpdateService($notificationData, $phone);
            }
        }
    }

    public function verifyJobActionToDo($jsonNotificationData, $reprogramming = null, $changeInstaller = null): void
    {
        $this->logger->info('Iniciando verifyJobActionToDo');
        $clientId = $jsonNotificationData['extraData']['entity']['clientId'];
        $installerId = $jsonNotificationData['extraData']['entity']['assignedUserId'];
        $jobId = $jsonNotificationData['entityId'];

        $dateString = $jsonNotificationData['extraData']['entity']['date'] ?? null;
        $formattedDate = $dateString ? sprintf("*%s*", (new DateTime($dateString))->format('d/m/Y')) : '';

        $config = PluginConfigManager::create()->loadConfig();
        $admin = $this->ucrmApi->get("users/admins/$installerId", []);
        $installerName = trim(($admin['firstName'] ?? '') . ' ' . ($admin['lastName'] ?? ''));

        $installerWhatsApp = '';
        $installers = json_decode($config['installersDataWhatsApp'] ?? '{"instaladores":[]}', true);
        foreach ($installers['instaladores'] as $inst) {
            if ($inst['id'] == $installerId) {
                $installerWhatsApp = $inst['whatsapp'];
                break;
            }
        }

        if (empty($installerWhatsApp)) $this->logger->warning("No se encontró número de WhatsApp para el instalador ID: $installerId");

        $clientCRM = $this->ucrmApi->get("clients/$clientId", []);
        $clientName = trim(($clientCRM['firstName'] ?? '') . ' ' . ($clientCRM['lastName'] ?? ''));
        $passCRM = '';
        foreach ($clientCRM['attributes'] as $attr) {
            if ($attr['key'] === 'passwordAntenaCliente') {
                $passCRM = $attr['value'];
                break;
            }
        }

        $allPhones = $this->clientPhoneNumber->getAllUcrmClientNumbers($clientCRM);
        $phonesStr = implode(', ', array_map(fn($n) => $this->validarNumeroTelefono($n), $allPhones));

        $api = new ClientCallBellAPI($config['apitoken'], $config['ipserver'], $config['tokencallbell']);

        $title = $jsonNotificationData['extraData']['entity']['title'] ?? '';
        $isPending = (stripos($title, '[NOTIFICACION-PENDIENTE]') !== false);
        $isNoWhatsApp = (stripos($title, '[CLIENTE-SIN-WHATSAPP]') !== false);

        $reprogramming = filter_var($reprogramming, FILTER_VALIDATE_BOOLEAN);
        $changeInstaller = filter_var($changeInstaller, FILTER_VALIDATE_BOOLEAN);

        $clientPhones = $this->clientPhoneNumber->getUcrmClientNumbers(null, $clientCRM);
        $hasClientWhatsApp = false;
        foreach ($clientPhones as $type => $phones) {
            $type = trim(strtolower($type));
            if (($type === 'whatsapp' || $type === 'whatsnotifica') && !empty($phones)) {
                $hasClientWhatsApp = true;
                break;
            }
        }

        $shouldNotifyTech = ($isPending || $reprogramming || $changeInstaller);
        $shouldNotifyClient = ($isPending || $isNoWhatsApp || $reprogramming || $changeInstaller);

        // 1. Notificar al Instalador Anterior (Desasignación)
        if ($changeInstaller) {
            $prevId = $jsonNotificationData['extraData']['entityBeforeEdit']['assignedUserId'];
            $prevAdmin = $this->ucrmApi->get("users/admins/$prevId", []);
            $prevWhatsApp = '';
            foreach ($installers['instaladores'] as $inst) {
                if ($inst['id'] == $prevId) {
                    $prevWhatsApp = $inst['whatsapp'];
                    break;
                }
            }
            if ($prevWhatsApp) {
                $api->sendJobNotificationWhatsAppToInstaller($this->validarNumeroTelefono($prevWhatsApp), [
                    "installerName" => "👷🏻‍♂️" . trim(($prevAdmin['firstName'] ?? '') . ' ' . ($prevAdmin['lastName'] ?? '')),
                    "subjectOfChange" => self::SUBJECT_OF_INSTALLER_CHANGE[1],
                    "jobId" => $jobId,
                    "clientFullName" => "[$clientId] $clientName",
                    "additionalChangeData" => self::ADDITIONAL_CHANGE_DATA[1] . ' *' . $installerName . '*',
                ], $reprogramming, $changeInstaller);
                sleep(1);
            }
        }

        // 2. Notificar al Cliente (si aplica)
        $clientNotified = false;
        if ($shouldNotifyClient && $hasClientWhatsApp) {
            foreach ($clientPhones as $type => $phones) {
                $type = trim(strtolower($type));
                if (!is_array($phones) || ($type !== 'whatsapp' && $type !== 'whatsnotifica')) continue;
                foreach ($phones as $phone) {
                    if ($api->sendJobNotificationWhatsAppToClient($this->validarNumeroTelefono($phone), ["clientFullName" => $clientName, "jobId" => $jobId, "date" => $formattedDate, "installerName" => $installerName], $reprogramming, $changeInstaller)) {
                        $clientNotified = true;
                    }
                }
            }
        }

        // 2. Notificar al Técnico (si aplica)
        if ($shouldNotifyTech) {
            $passVault = $this->getVaultCredentialsByClientId($clientId);
            $api->sendJobNotificationWhatsAppToInstaller($this->validarNumeroTelefono($installerWhatsApp), [
                "installerName" => $installerName,
                "clientFullName" => "$clientName [ID:$clientId]",
                "jobId" => $jobId,
                "clientAddress" => $clientCRM['fullAddress'] ?? 'N/A',
                "clientWhatsApp" => !empty($phonesStr) ? $phonesStr : 'Sin WhatsApp',
                "date" => $formattedDate,
                "jobDescription" => $jsonNotificationData['extraData']['entity']['description'] ?? 'S/D',
                "gmapsLocation" => ($clientCRM['addressGpsLat'] && $clientCRM['addressGpsLon']) ? "https://www.google.com/maps?q={$clientCRM['addressGpsLat']},{$clientCRM['addressGpsLon']}" : 'N/A',
                "passwordAntenaCliente" => $this->comparePasswords($passCRM, $passVault)
            ], $reprogramming, false);
        }

        // 3. Gestión del Título / Prefijos
        if ($isPending) {
            if ($clientNotified || $reprogramming || $changeInstaller) {
                $newTitle = str_ireplace('[NOTIFICACION-PENDIENTE]', '', $title);
                $this->ucrmApi->patch("scheduling/jobs/$jobId", ['title' => trim($newTitle)]);
            } else if (!$hasClientWhatsApp) {
                $newTitle = str_ireplace('[NOTIFICACION-PENDIENTE]', '[CLIENTE-SIN-WHATSAPP]', $title);
                $this->ucrmApi->patch("scheduling/jobs/$jobId", ['title' => trim($newTitle)]);
            }
        } else if ($isNoWhatsApp && ($clientNotified || $reprogramming || $changeInstaller)) {
            $newTitle = str_ireplace('[CLIENTE-SIN-WHATSAPP]', '', $title);
            $this->ucrmApi->patch("scheduling/jobs/$jobId", ['title' => trim($newTitle)]);
        }
    }

    public function verifyInvoiceActionToDo(NotificationData $notificationData): void
    {
        $arrayPhones = $this->clientPhoneNumber->getUcrmClientNumbers($notificationData, null);
        foreach ($arrayPhones as $type => $phones) {
            $type = trim(strtolower($type));
            if (!is_array($phones)) continue;
            foreach ($phones as $phone) {
                if ($type === 'whatsapp' || $type === 'whatsactualiza') $this->onlyUpdate($notificationData, $phone, false);
            }
        }
    }

    public function notify(NotificationData $notificationData, $phoneToNotify = null): void
    {
        $config = PluginConfigManager::create()->loadConfig();
        $api = new ClientCallBellAPI($config['apitoken'], $config['ipserver'], $config['tokencallbell']);
        $phone = $this->validarNumeroTelefono($phoneToNotify);
        if (!$phone) return;
        if ($config['notificationTypeText'] ?? false) $api->sendTextPaymentNotificationWhatsApp($phone, $notificationData);
        else $api->sendPaymentNotificationWhatsApp($phone, $notificationData);
    }

    public function notifyAndUpdate(NotificationData $notificationData, $phoneToNotifyAndUpdate = null): void
    {
        $config = PluginConfigManager::create()->loadConfig();
        $api = new ClientCallBellAPI($config['apitoken'], $config['ipserver'], $config['tokencallbell']);
        $phone = $this->validarNumeroTelefono($phoneToNotifyAndUpdate);
        if (!$phone) return;
        if ($config['notificationTypeText'] ?? false) {
            if ($api->sendTextPaymentNotificationWhatsApp($phone, $notificationData)) {
                $contact = json_decode($api->getContactWhatsapp($phone), true);
                if (isset($contact['contact']['uuid'])) $api->patchWhatsapp($contact, $notificationData);
            }
        } else {
            if ($api->sendPaymentNotificationWhatsApp($phone, $notificationData)) {
                $contact = json_decode($api->getContactWhatsapp($phone), true);
                if (isset($contact['contact']['uuid'])) $api->patchWhatsapp($contact, $notificationData);
            }
        }
    }

    public function notifyOverDue(NotificationData $notificationData): void
    {
        $config = PluginConfigManager::create()->loadConfig();
        $api = new ClientCallBellAPI($config['apitoken'], $config['ipserver'], $config['tokencallbell']);
        $phone = $this->clientPhoneNumber->getUcrmClientNumber($notificationData);
        if ($phone) $api->sendOverdueNotificationWhatsApp($phone, $notificationData);
    }

    public function onlyUpdate(NotificationData $notificationData, $phoneToUpdate): void
    {
        $this->logger->debug("onlyUpdate: Iniciando actualización para teléfono: $phoneToUpdate");
        $config = PluginConfigManager::create()->loadConfig();
        $api = new ClientCallBellAPI($config['apitoken'], $config['ipserver'], $config['tokencallbell']);
        $phone = $this->validarNumeroTelefono($phoneToUpdate);
        $this->logger->debug("onlyUpdate: Teléfono validado: $phone");
        $contact = json_decode($api->getContactWhatsapp($phone), true);
        $this->logger->debug("onlyUpdate: Contacto obtenido de CallBell: " . json_encode($contact));
        if (isset($contact['contact']['uuid'])) {
            $this->logger->info("onlyUpdate: Ejecutando patchWhatsapp para teléfono: $phone");
            $api->patchWhatsapp($contact, $notificationData);
        } else {
            $this->logger->warning("onlyUpdate: No se encontró contacto en CallBell para teléfono: $phone");
        }
    }

    public function onlyUpdateService(NotificationData $notificationData, $phoneToUpdate): void
    {
        $config = PluginConfigManager::create()->loadConfig();
        $api = new ClientCallBellAPI($config['apitoken'], $config['ipserver'], $config['tokencallbell']);
        $phone = $this->validarNumeroTelefono($phoneToUpdate);
        $contact = json_decode($api->getContactWhatsapp($phone), true);
        if (isset($contact['contact']['uuid'])) $api->patchServiceStatusWhatsApp($contact, $notificationData);
    }

    protected function getVaultCredentialsByClientId($clientId): string
    {
        $config = PluginConfigManager::create()->loadConfig();
        $ipServer = $config['ipserver'] ?? '';
        $crm = new Client(['base_uri' => "https://{$ipServer}/crm/api/v1.0/", 'verify' => false]);

        try {
            // OPT: Lazy Check - Si ya tiene pass válido en CRM, no hace falta procesar nada
            $respClient = $crm->get("clients/$clientId", ['headers' => ['X-Auth-Token' => $config['apitoken']]]);
            $clientData = json_decode($respClient->getBody()->getContents(), true);
            $passCRM = '';
            if (isset($clientData['attributes'])) {
                foreach ($clientData['attributes'] as $attr) {
                    if ($attr['key'] === 'passwordAntenaCliente') {
                        $passCRM = $attr['value'] ?? '';
                        break;
                    }
                }
            }

            // Si el campo no está vacío y no tiene advertencias, usamos el actual para ahorrar recursos
            if (!empty($passCRM) && strpos($passCRM, '⚠️') === false) {
                return $passCRM;
            }

            // 1. Obtener los servicios del cliente
            $respSvc = $crm->get('clients/services?clientId=' . $clientId, [
                'headers' => ['X-Auth-Token' => $config['apitoken']]
            ]);
            $svcs = json_decode($respSvc->getBody()->getContents(), true);

            if (empty($svcs)) {
                $msg = '⚠️ Cliente sin servicios/antenas';
                $this->syncPasswordWithCrm((int)$clientId, $msg);
                return $msg;
            }

            $unms = new Client(['base_uri' => "https://{$ipServer}/nms/api/v2.1/", 'verify' => false]);
            $allServicePasswords = [];
            $isTestEnv = ($ipServer === '172.16.5.134' || $ipServer === 'pruebas.internet.mx' || $ipServer === 'venus.siip.mx');

            $numServices = count($svcs);
            foreach ($svcs as $index => $svc) {
                $label = ($numServices > 1) ? "Servicio " . ($index + 1) . ":" : "";
                $siteId = $svc['unmsClientSiteId'] ?? null;
                $passwordValue = "";

                if (!$siteId) {
                    $passwordValue = "⚠️ Sin sitio";
                } else {
                    if ($isTestEnv) {
                        // Lógica de bypass: intentar recuperar de la cadena existente
                        $foundInCRM = false;
                        if (!empty($passCRM)) {
                            if ($numServices > 1) {
                                if (preg_match('/Servicio ' . ($index + 1) . ':\s*([^⚠️\s]+)/', $passCRM, $matches)) {
                                    $passwordValue = $matches[1];
                                    $foundInCRM = true;
                                }
                            } else {
                                // Caso de un solo servicio: si no tiene advertencias ni etiquetas, la tomamos a secas
                                if (strpos($passCRM, '⚠️') === false && strpos($passCRM, 'Servicio') === false) {
                                    $passwordValue = trim($passCRM);
                                    $foundInCRM = true;
                                }
                            }
                        }

                        if (!$foundInCRM) {
                            $passwordValue = $this->generateStrongPassword(16);
                        }
                    } else {
                        // Lógica de producción
                        try {
                            $respDev = $unms->get("devices?siteId=$siteId", [
                                'headers' => ['X-Auth-Token' => $config['unmsApiToken']]
                            ]);
                            $devs = json_decode($respDev->getBody()->getContents(), true);

                            if (empty($devs)) {
                                $passwordValue = "⚠️ Sin antena";
                            } else {
                                $passVault = null;
                                $firstDeviceId = null;
                                foreach ($devs as $dev) {
                                    $deviceId = $dev['identification']['id'] ?? null;
                                    if (!$deviceId) continue;
                                    if (!$firstDeviceId) $firstDeviceId = $deviceId;
                                    try {
                                        $respVault = $unms->get("vault/$deviceId/credentials", [
                                            'headers' => ['X-Auth-Token' => $config['unmsApiToken']]
                                        ]);
                                        $vault = json_decode($respVault->getBody()->getContents(), true);
                                        if (isset($vault['credentials'][0]['password'])) {
                                            $passVault = $vault['credentials'][0]['password'];
                                            break;
                                        }
                                    } catch (\Exception $e) {
                                        continue;
                                    }
                                }

                                if ($passVault) {
                                    $passwordValue = $passVault;
                                } else if ($firstDeviceId) {
                                    // Regenerar
                                    $newPass = $this->generateStrongPassword(16);
                                    try {
                                        $unms->post("vault/$firstDeviceId/credentials/regenerate", [
                                            'headers' => ['X-Auth-Token' => $config['unmsApiToken']],
                                            'json' => [['username' => 'ubnt', 'password' => $newPass, 'readOnly' => true]]
                                        ]);
                                        $passwordValue = $newPass;
                                    } catch (\Exception $e) {
                                        $passwordValue = $newPass;
                                    }
                                } else {
                                    $passwordValue = "⚠️ Sin antena";
                                }
                            }
                        } catch (\Exception $e) {
                            $passwordValue = "⚠️ Error API";
                        }
                    }
                }
                $allServicePasswords[] = trim("$label $passwordValue");
            }

            $finalValue = implode(' ', $allServicePasswords);

            // Evitar sincronización redundante
            if ($finalValue === $passCRM) {
                return $finalValue;
            }

            $this->syncPasswordWithCrm((int)$clientId, $finalValue);
            return $finalValue;
        } catch (\Exception $e) {
            $this->logger->error("Error en getVaultCredentialsByClientId: " . $e->getMessage());
            return 'Error: ' . $e->getMessage();
        }
    }

    private function syncPasswordWithCrm(int $clientId, string $passVault): void
    {
        $config = PluginConfigManager::create()->loadConfig();
        $crm = new Client(['base_uri' => "https://{$config['ipserver']}/crm/api/v1.0/", 'verify' => false]);
        try {
            $respClient = $crm->get("clients/$clientId", [
                'headers' => ['X-Auth-Token' => $config['apitoken']]
            ]);
            $clientData = json_decode($respClient->getBody()->getContents(), true);
            $passCRM = '';
            $attributeId = 17;
            if (isset($clientData['attributes'])) {
                foreach ($clientData['attributes'] as $attr) {
                    if ($attr['key'] === 'passwordAntenaCliente') {
                        $passCRM = $attr['value'] ?? '';
                        $attributeId = $attr['customAttributeId'];
                        break;
                    }
                }
            }
            if (empty($passCRM) || $passCRM !== $passVault) {
                $this->logger->info("Sincronizando pass CRM cliente $clientId.");
                $this->patchClientCustomAttribute($clientId, (int)$attributeId, $passVault);
            }
        } catch (\Exception $e) {
            $this->logger->warning("Fallo sincronización pass CRM: " . $e->getMessage());
        }
    }

    protected function generateStrongPassword(int $length = 16): string
    {
        $lower = 'abcdefghijkmnopqrstuvwxyz'; // Eliminamos 'l'
        $upper = 'ABCDEFGHJKLMNPQRSTUVWXYZ'; // Eliminamos 'I', 'O'
        $digits = '23456789'; // Eliminamos '1', '0'
        $symbols = '@#'; // Solo símbolos amigables para impresoras térmicas

        $all = $lower . $upper . $digits . $symbols;
        $pwChars = [];

        // Asegurar que tenga al menos uno de cada tipo si es posible
        $pwChars[] = $lower[random_int(0, strlen($lower) - 1)];
        $pwChars[] = $upper[random_int(0, strlen($upper) - 1)];
        $pwChars[] = $digits[random_int(0, strlen($digits) - 1)];
        $pwChars[] = $symbols[random_int(0, strlen($symbols) - 1)];

        for ($i = count($pwChars); $i < $length; $i++) {
            $pwChars[] = $all[random_int(0, strlen($all) - 1)];
        }

        // Mezclar Fisher-Yates
        $n = count($pwChars);
        for ($i = $n - 1; $i > 0; $i--) {
            $j = random_int(0, $i);
            $tmp = $pwChars[$i];
            $pwChars[$i] = $pwChars[$j];
            $pwChars[$j] = $tmp;
        }

        return implode('', $pwChars);
    }

    protected function patchClientCustomAttribute(int $clientId, int $attributeId, string $value): bool
    {
        $config = PluginConfigManager::create()->loadConfig();
        $crm = new Client(['base_uri' => "https://{$config['ipserver']}/crm/api/v1.0/", 'verify' => false]);
        try {
            $crm->patch("clients/$clientId", [
                'headers' => ['X-Auth-Token' => $config['apitoken']],
                'json' => [
                    "attributes" => [['value' => $value, 'customAttributeId' => $attributeId]]
                ]
            ]);
            return true;
        } catch (\Exception $e) {
            $this->logger->error("Error patching custom attribute for client $clientId: " . $e->getMessage());
            return false;
        }
    }

    protected function comparePasswords(?string $crm, ?string $vault): string
    {
        if ($vault && strpos($vault, 'Error') !== 0) return $vault;
        if ($crm && strpos($crm, 'Error') !== 0) return $crm;
        return '⚠️ Probar pass conocida.';
    }

    protected function validarNumeroTelefono($n): string
    {
        if (!$n) return '';
        $n = preg_replace('/\D/', '', (string)$n);
        if (strlen($n) === 10) {
            return '521' . $n;
        }
        if (strlen($n) === 12 && strpos($n, '52') === 0) {
            return '521' . substr($n, 2);
        }
        return $n;
    }

    abstract protected function sendWhatsApp(NotificationData $notificationData, string $clientSmsNumber): void;
}
