<?php
if (!defined('_PS_VERSION_')) { exit; }

class Whatsboost extends Module
{
    public function __construct()
    {
        $this->name = 'whatsboost';
        $this->tab = 'front_office_features';
        $this->version = '1.0.0';
        $this->author = 'ALMCSECURITY S.L.U.';
        $this->need_instance = 0;
        $this->bootstrap = true;

        parent::__construct();

        $this->displayName = $this->l('WhatsBoost');
        $this->description = $this->l('WhatsBoost envía por WhatsApp avisos automáticos de PrestaShop (pedido recibido, en preparación, enviado, etc.). Personaliza plantillas con datos del pedido y mantén a tus clientes informados en tiempo real.');
        $this->ps_versions_compliancy = ['min' => '1.7.0.0', 'max' => _PS_VERSION_];
    }

    public function install()
    {
        $ok = parent::install()
            && Configuration::updateValue('WHATSBOOST_TITLE', 'Hola desde WhatsBoost')
            && Configuration::updateValue('WHATSBOOST_LICENSE_KEY', '')
            && Configuration::updateValue('WHATSBOOST_SECRET', '')
            && Configuration::updateValue('WHATSBOOST_VERIFY_ENDPOINT', 'https://whatsboost.net/api/get/subscription')
            && Configuration::updateValue('WHATSBOOST_ENABLED', 1)
            && Configuration::updateValue('WHATSBOOST_VERIFIED', 0)
            && Configuration::updateValue('WHATSBOOST_DEBUG', 0)
            && $this->registerHook('displayHome')
            && $this->registerHook('header')
            && $this->registerHook('actionAdminControllerSetMedia')
            && $this->registerHook('displayBackOfficeTop')
            // Disparos relacionados con cambio de estado de pedido
            && $this->registerHook('actionOrderStatusPostUpdate')
            && $this->registerHook('actionOrderHistoryAddAfter')
            && $this->registerHook('actionOrderStatusUpdate')
            && $this->registerHook('actionValidateOrder');
        if ($ok) {
            // Try to create back-office tabs, but don't fail install/reset if tabs cannot be created
            try { $this->installTab(); } catch (\Throwable $e) { /* noop */ }
            try { $this->installAjaxTab(); } catch (\Throwable $e) { /* noop */ }
        }
        return (bool)$ok;
    }

    public function uninstall()
    {
        Configuration::deleteByName('WHATSBOOST_TITLE');
        Configuration::deleteByName('WHATSBOOST_LICENSE_KEY');
        Configuration::deleteByName('WHATSBOOST_SECRET');
        Configuration::deleteByName('WHATSBOOST_VERIFY_ENDPOINT');
        Configuration::deleteByName('WHATSBOOST_ENABLED');
        Configuration::deleteByName('WHATSBOOST_VERIFIED');
        Configuration::deleteByName('WHATSBOOST_UNIQUE');
        Configuration::deleteByName('WHATSBOOST_NEEDS_UNIQUE');
        Configuration::deleteByName('WHATSBOOST_DEBUG');
        // Best-effort tab cleanup; never fail uninstall on tab errors
        try { $this->uninstallTab(); } catch (\Throwable $e) { /* noop */ }
        try { $this->uninstallAjaxTab(); } catch (\Throwable $e) { /* noop */ }
        return (bool)parent::uninstall();
    }

    /**
     * Safe reset to avoid back-office error "Could not perform action reset for module undefined".
     * Uses parent::reset() when available; otherwise chains uninstall/install with best-effort tabs.
     */
    public function reset()
    {
        // Prefer the core implementation if present
        if (method_exists('Module', 'reset')) {
            try {
                return (bool)parent::reset();
            } catch (\Throwable $e) {
                // Fallback to manual reset
            }
        }
        try {
            $un = (bool)$this->uninstall();
        } catch (\Throwable $e) {
            $un = false;
        }
        if (!$un) { return false; }
        try {
            return (bool)$this->install();
        } catch (\Throwable $e) {
            return false;
        }
    }

    public function getContent()
    {
        $output = '';
        $verifyResult = null;
        // Asegurar que el nuevo hook esté registrado incluso en instalaciones previas
        try { if (method_exists($this, 'registerHook')) { $this->registerHook('actionValidateOrder'); } } catch (\Throwable $e) { /* noop */ }

        // Botón secundario: probar clave sin guardar
        if (Tools::isSubmit('submitWhatsboostTest')) {
            $secret = Tools::getValue('WHATSBOOST_SECRET', Configuration::get('WHATSBOOST_SECRET'));
            $endpoint = Tools::getValue('WHATSBOOST_VERIFY_ENDPOINT', Configuration::get('WHATSBOOST_VERIFY_ENDPOINT'));
            $verifyResult = $this->verifySecret((string)$secret, (string)$endpoint);
            if ($verifyResult['ok']) {
                $msg = isset($verifyResult['message']) ? $verifyResult['message'] : $this->l('Clave verificada correctamente.');
                $output .= $this->displayConfirmation($msg);
            } else {
                $output .= $this->displayError($verifyResult['message']);
            }
        }


        // Guardar cambios (con validaciones)
        if (Tools::isSubmit('submitWhatsboost')) {
            $title = (string)Tools::getValue('WHATSBOOST_TITLE');
            $secret = trim((string)Tools::getValue('WHATSBOOST_SECRET'));
            $endpoint = (string)Tools::getValue('WHATSBOOST_VERIFY_ENDPOINT', Configuration::get('WHATSBOOST_VERIFY_ENDPOINT'));
            $enabledRequested = (int)Tools::getValue('WHATSBOOST_ENABLED') ? 1 : 0;

            if ($title !== '') {
                Configuration::updateValue('WHATSBOOST_TITLE', $title);
            }

            if ($secret === '') {
                $output .= $this->displayError($this->l('El campo Secret es obligatorio.'));
            } else {
                Configuration::updateValue('WHATSBOOST_SECRET', $secret);
                Configuration::updateValue('WHATSBOOST_VERIFY_ENDPOINT', $endpoint ?: 'https://whatsboost.net/api/get/subscription');

                $verifiedOk = (int)Configuration::get('WHATSBOOST_VERIFIED');
                if ($enabledRequested === 1) {
                    $verifyResult = $this->verifySecret($secret, $endpoint ?: 'https://whatsboost.net/api/get/subscription');
                    $verifiedOk = $verifyResult['ok'] ? 1 : 0;
                }

                if ($enabledRequested && !$verifiedOk) {
                    Configuration::updateValue('WHATSBOOST_ENABLED', 0);
                    Configuration::updateValue('WHATSBOOST_VERIFIED', 0);
                    $msg = isset($verifyResult['message']) ? $verifyResult['message'] : $this->l('No se pudo activar: verificación fallida.');
                    $output .= $this->displayError($msg);
                } else {
                    Configuration::updateValue('WHATSBOOST_ENABLED', (int)$enabledRequested);
                    Configuration::updateValue('WHATSBOOST_VERIFIED', (int)$verifiedOk);
                    $output .= $this->displayConfirmation($this->l('Configuración guardada'));
                }
            }
        }

        // Guardar estados (pestaña Estados y pedidos)
        if (Tools::getIsset('wbv') && Tools::getValue('wbv') === 'states' && Tools::isSubmit('submitWhatsboostStates')) {
            $raw = Tools::getValue('states', []);
            $clean = [];
            if (is_array($raw)) {
                foreach ($raw as $sid => $row) {
                    $id = (int)$sid;
                    if ($id <= 0) { continue; }
                    $enabled = isset($row['enabled']) ? 1 : 0;
                    $text = isset($row['text']) ? (string)$row['text'] : '';
                    $clean[$id] = [
                        'enabled' => $enabled,
                        'text' => Tools::substr($text, 0, 2000),
                    ];
                }
            }
            Configuration::updateValue('WHATSBOOST_STATES_CFG', json_encode($clean));
            $output .= $this->displayConfirmation($this->l('Estados guardados correctamente.'));
        }

        // HelperForm base (se mantiene para otros ajustes)
        $helper = new HelperForm();
        $helper->show_toolbar = false;
        $helper->module = $this;
        $helper->default_form_language = (int)Configuration::get('PS_LANG_DEFAULT');
        $helper->identifier = $this->identifier;
        $helper->submit_action = 'submitWhatsboost';
        $helper->currentIndex = $this->context->link->getAdminLink('AdminModules', false)
            .'&configure='.$this->name.'&tab_module='.$this->tab.'&module_name='.$this->name;
        $helper->token = Tools::getAdminTokenLite('AdminModules');
        $helper->fields_value['WHATSBOOST_TITLE'] = Configuration::get('WHATSBOOST_TITLE');
        $helper->fields_value['WHATSBOOST_SECRET'] = Configuration::get('WHATSBOOST_SECRET');
        $helper->fields_value['WHATSBOOST_VERIFY_ENDPOINT'] = Configuration::get('WHATSBOOST_VERIFY_ENDPOINT');
        $helper->fields_value['WHATSBOOST_ENABLED'] = (int)Configuration::get('WHATSBOOST_ENABLED');

        $form = [
            'form' => [
                'legend' => [
                    'title' => $this->l('Ajustes de WhatsBoost'),
                    'icon'  => 'icon-cogs',
                ],
                'input' => [
                    [
                        'type' => 'text',
                        'label' => $this->l('Título a mostrar'),
                        'name' => 'WHATSBOOST_TITLE',
                        'required' => true,
                    ],
                ],
                'submit' => [
                    'title' => $this->l('Guardar'),
                    'class' => 'btn btn-default pull-right'
                ]
            ]
        ];
        $helper_form = $helper->generateForm([$form]);

        // Vista
        $view = Tools::getValue('wbv', 'settings');

        // Datos para la pestaña Estados y pedidos
        $order_states = [];
        if ($view === 'states') {
            try {
                $id_lang = (int)$this->context->language->id;
                if (class_exists('OrderState')) {
                    $order_states = (array)OrderState::getOrderStates($id_lang);
                }
            } catch (Exception $e) {
                $order_states = [];
            }
        }
        $states_cfg = [];
        $cfgJson = (string)Configuration::get('WHATSBOOST_STATES_CFG');
        if ($cfgJson) {
            $tmp = json_decode($cfgJson, true);
            if (is_array($tmp)) { $states_cfg = $tmp; }
        }

        $this->context->smarty->assign([
            'view' => $view,
            'helper_form' => $helper_form,
            'admin_base' => $this->context->link->getAdminLink('AdminModules', true)
                .'&configure='.$this->name.'&tab_module='.$this->tab.'&module_name='.$this->name,
            'ajax_url' => $this->context->link->getAdminLink('AdminWhatsboostAjax', true),
            'front_accounts_url' => $this->context->link->getModuleLink($this->name, 'accounts'),
            'module_logo' => $this->_path.'views/img/favicon.png',
            'module_dir' => $this->_path,
            'settings' => [
                'license_key' => (string) (Configuration::get('WHATSBOOST_LICENSE_KEY') ?: ''),
                'secret'      => (string) (Configuration::get('WHATSBOOST_SECRET') ?: ''),
                'endpoint'    => (string) (Configuration::get('WHATSBOOST_VERIFY_ENDPOINT') ?: 'https://whatsboost.net/api/get/subscription'),
                'enabled'     => (int) Configuration::get('WHATSBOOST_ENABLED'),
                'verified'    => (int) Configuration::get('WHATSBOOST_VERIFIED'),
                'unique'      => (string) (Configuration::get('WHATSBOOST_UNIQUE') ?: ''),
                'needs_unique'=> (int) Configuration::get('WHATSBOOST_NEEDS_UNIQUE'),
                'debug'       => (int) Configuration::get('WHATSBOOST_DEBUG'),
            ],
            'verify_result' => $verifyResult,
            'order_states' => $order_states,
            'states_cfg' => $states_cfg,
            // Aviso persistente: hay clave guardada pero no verificada
            'invalid_secret' => ((string)Configuration::get('WHATSBOOST_SECRET') !== ''
                                  && (int)Configuration::get('WHATSBOOST_VERIFIED') !== 1),
            'invalid_secret_msg' => $this->l('La clave guardada es inválida. Sustituye tu clave (Secret) por una válida para activar WhatsBoost.'),
        ]);

        return $output.$this->fetch('module:'.$this->name.'/views/templates/admin/layout.tpl');
    }

    public function hookActionAdminControllerSetMedia($params)
    {
        // Load admin styles globally so the BO menu icon can be customized
        $this->context->controller->addCSS('modules/'.$this->name.'/views/css/admin.css');
    }

    /**
     * Muestra avisos globales en el back office (arriba) usando el tpl hook/admin-global-warning.tpl
     */
    public function hookDisplayBackOfficeTop($params)
    {
        try {
            $secret = (string)Configuration::get('WHATSBOOST_SECRET');
            $verified = (int)Configuration::get('WHATSBOOST_VERIFIED') === 1;
            $unique = (string)Configuration::get('WHATSBOOST_UNIQUE');
            $needsUnique = (int)Configuration::get('WHATSBOOST_NEEDS_UNIQUE') === 1;

            $show_key = (!$verified) || ($secret === '');
            $show_unique = ($verified && ($needsUnique || $unique === ''));

            $this->context->smarty->assign([
                'wb_key_should_show' => (bool)$show_key,
                'wb_unique_should_show' => (bool)$show_unique,
                'wb_settings_url' => $this->context->link->getAdminLink('AdminModules', true)
                    .'&configure='.$this->name.'&tab_module='.$this->tab.'&module_name='.$this->name,
                'wb_status_url' => $this->context->link->getAdminLink('AdminWhatsboostAjax', true),
            ]);
            return $this->fetch('module:'.$this->name.'/views/templates/hook/admin-global-warning.tpl');
        } catch (\Throwable $e) {
            return '';
        }
    }

    public function hookHeader($params)
    {
        // Estilos front-office mínimos
        $this->context->controller->addCSS('modules/'.$this->name.'/views/css/front.css');
    }

    /**
     * Defensive: Some hosts have strict open_basedir; never render templates from this hook.
     * We don't need to output anything here, only assets are supposed to be queued via addCss/addJs.
     */
    public function hookDisplayBackOfficeHeader($params)
    {
        try {
            // Intentionally no output; assets are handled in hookActionAdminControllerSetMedia
            return '';
        } catch (\Throwable $e) {
            return '';
        }
    }

    public function hookDisplayHome($params)
    {
        $this->context->smarty->assign([
            'whatsboost_title' => (string)Configuration::get('WHATSBOOST_TITLE'),
            'front_link' => $this->context->link->getModuleLink($this->name, 'display'),
        ]);
        return $this->fetch('module:'.$this->name.'/views/templates/hook/displayHome.tpl');
    }

    /**
     * Hook principal (PS 1.7/8): se llama tras actualizar un estado de pedido.
     * $params = [ 'newOrderStatus' => OrderState, 'id_order' => int, 'order' => Order|null ]
     */
    public function hookActionOrderStatusPostUpdate($params)
    {
        try { $this->maybeSendWhatsAppForParams($params); } catch (\Throwable $e) { /* noop */ }
    }

    /**
     * Fallback en algunas versiones/situaciones: al crear historial de pedido.
     * $params = [ 'order_history' => OrderHistory ]
     */
    public function hookActionOrderHistoryAddAfter($params)
    {
        try { $this->maybeSendWhatsAppForParams($params); } catch (\Throwable $e) { /* noop */ }
    }

    // Compatibilidad adicional con versiones antiguas
    public function hookActionOrderStatusUpdate($params)
    {
        try { $this->maybeSendWhatsAppForParams($params); } catch (\Throwable $e) { /* noop */ }
    }

    /**
     * Se dispara cuando se valida/crea el pedido (primer estado)
     * $params = [ 'order' => Order, 'orderStatus' => OrderState, ... ]
     */
    public function hookActionValidateOrder($params)
    {
        try { $this->maybeSendWhatsAppForParams($params); } catch (\Throwable $e) { /* noop */ }
    }

    /**
     * Lógica compartida: valida ajustes, busca texto por estado, construye mensaje y manda POST.
     */
    private function maybeSendWhatsAppForParams($params)
    {
        $debug = (int)Configuration::get('WHATSBOOST_DEBUG');
        if ($debug >= 1 && method_exists('PrestaShopLogger', 'addLog')) {
            $oidDbg = isset($params['id_order']) ? (int)$params['id_order'] : (isset($params['order']) && $params['order'] instanceof Order ? (int)$params['order']->id : 0);
            PrestaShopLogger::addLog('WB DEBUG hook entered order='.$oidDbg, 1);
        }
        // Comprobaciones rápidas de activación y credenciales
        if (!(int)Configuration::get('WHATSBOOST_ENABLED')) { if($debug>=2 && method_exists('PrestaShopLogger','addLog')){ PrestaShopLogger::addLog('WB DEBUG guard enabled=0',1);} return; }
        if (!(int)Configuration::get('WHATSBOOST_VERIFIED')) { if($debug>=2 && method_exists('PrestaShopLogger','addLog')){ PrestaShopLogger::addLog('WB DEBUG guard verified=0',1);} return; }
        $secret = (string)Configuration::get('WHATSBOOST_SECRET');
        $unique = (string)Configuration::get('WHATSBOOST_UNIQUE');
        if ($secret === '' || $unique === '') { if($debug>=2 && method_exists('PrestaShopLogger','addLog')){ PrestaShopLogger::addLog('WB DEBUG guard missing credentials',1);} return; }
        // WB DEBUG: credenciales (enmascaradas) para diagnóstico cuando debug>=2
        if ($debug >= 2 && method_exists('PrestaShopLogger','addLog')) {
            $mask = function ($s) {
                $s = (string)$s;
                $n = class_exists('Tools') ? Tools::strlen($s) : strlen($s);
                if ($n <= 8) { return str_repeat('*', $n); }
                $left = class_exists('Tools') ? Tools::substr($s, 0, 4) : substr($s, 0, 4);
                $right = class_exists('Tools') ? Tools::substr($s, -4) : substr($s, -4);
                return $left . '...' . $right;
            };
            $secretMasked = $mask($secret);
            $uniqueMasked = $mask($unique);
            PrestaShopLogger::addLog('WB DEBUG creds secret=' . pSQL($secretMasked) . ' unique=' . pSQL($uniqueMasked), 1);
        }

        // Identificar pedido y estado
        $order = null; $stateId = 0;
        if (isset($params['order']) && $params['order'] instanceof Order) {
            $order = $params['order'];
        } elseif (isset($params['id_order'])) {
            $oid = (int)$params['id_order'];
            if ($oid > 0) { $order = new Order($oid); }
        } elseif (isset($params['order_history']) && $params['order_history'] instanceof OrderHistory) {
            $oh = $params['order_history'];
            if ((int)$oh->id_order > 0) { $order = new Order((int)$oh->id_order); }
            if ((int)$oh->id_order_state > 0) { $stateId = (int)$oh->id_order_state; }
        }
        if (!$order || !Validate::isLoadedObject($order)) { if($debug>=2 && method_exists('PrestaShopLogger','addLog')){ PrestaShopLogger::addLog('WB DEBUG guard order not loaded',1);} return; }

        if ($stateId <= 0) {
            if (isset($params['newOrderStatus']) && $params['newOrderStatus'] instanceof OrderState) {
                $stateId = (int)$params['newOrderStatus']->id;
            } else {
                // Último estado del pedido
                $history = $order->getHistory((int)$order->id_lang, false, false, $order->id);
                if (is_array($history) && count($history) > 0) {
                    $last = end($history);
                    if (is_array($last) && isset($last['id_order_state'])) {
                        $stateId = (int)$last['id_order_state'];
                    }
                }
            }
        }
        if ($stateId <= 0) { if($debug>=2 && method_exists('PrestaShopLogger','addLog')){ PrestaShopLogger::addLog('WB DEBUG guard stateId not resolved',1);} return; }
        if ($debug >= 2 && method_exists('PrestaShopLogger','addLog')) { PrestaShopLogger::addLog('WB DEBUG resolved order='.$order->id.' stateId='.$stateId,1); }

        // Guardar simple anti-duplicados por petición (mismo pedido+estado)
        // Previene envíos múltiples cuando varios hooks disparan en la misma solicitud
        static $wb_seen = array();
        $wb_key = (int)$order->id.'-'.(int)$stateId;
        if (isset($wb_seen[$wb_key])) {
            if ($debug >= 1 && method_exists('PrestaShopLogger','addLog')) { PrestaShopLogger::addLog('WB DEBUG guard duplicate in-request key='.$wb_key,1); }
            return;
        }
        $wb_seen[$wb_key] = true;

        // Leer configuración por estado
        $cfgJson = (string)Configuration::get('WHATSBOOST_STATES_CFG');
        if ($cfgJson === '') { if($debug>=2 && method_exists('PrestaShopLogger','addLog')){ PrestaShopLogger::addLog('WB DEBUG guard no WHATSBOOST_STATES_CFG',1);} return; }
        $map = json_decode($cfgJson, true);
        if (!is_array($map)) { if($debug>=2 && method_exists('PrestaShopLogger','addLog')){ PrestaShopLogger::addLog('WB DEBUG guard states cfg not array',1);} return; }
        // Compatibilidad: claves pueden ser numéricas o string
        $row = null;
        if (isset($map[$stateId])) {
            $row = (array)$map[$stateId];
        } elseif (isset($map[(string)$stateId])) {
            $row = (array)$map[(string)$stateId];
        }
        if ($row === null) { if($debug>=2 && method_exists('PrestaShopLogger','addLog')){ PrestaShopLogger::addLog('WB DEBUG guard state not configured id='.$stateId,1);} return; }
        if (empty($row['enabled'])) { if($debug>=2 && method_exists('PrestaShopLogger','addLog')){ PrestaShopLogger::addLog('WB DEBUG guard state disabled id='.$stateId,1);} return; }
        $template = isset($row['text']) ? (string)$row['text'] : '';
        // Log explícito del texto de estado (preview) para diagnóstico
        if ($debug >= 2 && method_exists('PrestaShopLogger','addLog')) {
            $tplPreview = class_exists('Tools') ? Tools::substr($template, 0, 200) : substr($template, 0, 200);
            PrestaShopLogger::addLog('WB DEBUG state cfg id='.$stateId.' enabled=1 template_preview="'.pSQL($tplPreview).'"', 1);
        }
        if ($template === '') { if($debug>=2 && method_exists('PrestaShopLogger','addLog')){ PrestaShopLogger::addLog('WB DEBUG guard empty template id='.$stateId,1);} return; }

        // Teléfono del cliente (priorizar phone sobre phone_mobile) + trazas crudo/normalizado cuando debug>=2
        $idAddr = (int)$order->id_address_delivery ?: (int)$order->id_address_invoice;
        $phone = '';
        $rawPhone = '';
        $rawMobile = '';
        if ($idAddr > 0) {
            $addr = new Address($idAddr);
            if (Validate::isLoadedObject($addr)) {
                $rawPhone  = (string)$addr->phone;
                $rawMobile = (string)$addr->phone_mobile;
                // Elegimos priorizando phone y luego phone_mobile
                $phone = (string)($rawPhone !== '' ? $rawPhone : ($rawMobile !== '' ? $rawMobile : ''));
                if ($debug >= 2 && method_exists('PrestaShopLogger','addLog')) {
                    PrestaShopLogger::addLog(
                        'WB DEBUG phone raw id_addr='.(int)$idAddr.' phone="'.pSQL($rawPhone).'" phone_mobile="'.pSQL($rawMobile).'" chosen="'.pSQL($phone).'"',
                        1
                    );
                }
            }
        }
        $phone = $this->normalizePhone($phone);
        if ($debug >= 2 && method_exists('PrestaShopLogger','addLog')) {
            PrestaShopLogger::addLog('WB DEBUG phone normalized recipient="'.pSQL($phone).'"', 1);
        }
        if ($phone === '') { if($debug>=2 && method_exists('PrestaShopLogger','addLog')){ PrestaShopLogger::addLog('WB DEBUG guard empty phone after normalization',1);} return; }

        // Construir mensaje (con manejo de errores)
        try {
            $text = $this->buildMessageForOrder($order, $template);
        } catch (\Throwable $e) {
            if ($debug >= 1 && method_exists('PrestaShopLogger','addLog')) {
                PrestaShopLogger::addLog('WB ERROR buildMessage exception: '.pSQL($e->getMessage()), 3);
            }
            return; // abortar limpio si falla el build
        }
        if ($text === '') { if($debug>=2 && method_exists('PrestaShopLogger','addLog')){ PrestaShopLogger::addLog('WB DEBUG guard empty text after build',1);} return; }
        if ($debug >= 2 && method_exists('PrestaShopLogger','addLog')) {
            $len = class_exists('Tools') ? Tools::strlen($text) : strlen($text);
            $preview = class_exists('Tools') ? Tools::substr($text, 0, 200) : substr($text, 0, 200);
            PrestaShopLogger::addLog('WB DEBUG payload recipient=' . pSQL($phone) . ' len='.(int)$len.' msg_preview="' . pSQL($preview) . '"', 1);
            PrestaShopLogger::addLog('WB DEBUG sending recipient='.$phone,1);
        }

        // Enviar
        try {
            $this->sendWhatsboostMessage($secret, $unique, $phone, $text);
        } catch (\Throwable $e) {
            if (method_exists('PrestaShopLogger','addLog')) { PrestaShopLogger::addLog('WB ERROR send exception: ' . pSQL($e->getMessage()), 3); }
        }
    }

    private function normalizePhone($raw)
    {
        // Normalización a E.164 con prefijo de país (usa país de tienda; fallback +34)
        $orig = trim((string)$raw);
        if ($orig === '') { return ''; }
        $hasPlus = (strpos($orig, '+') === 0);
        $digits = preg_replace('/\D+/', '', $orig);
        if ($digits === '') { return ''; }
        if ($hasPlus) {
            $e164 = '+' . $digits;
        } elseif (strpos($digits, '00') === 0) {
            $e164 = '+' . substr($digits, 2);
        } else {
            $cc = '34';
            try {
                $shopCountryId = (int)Configuration::get('PS_SHOP_COUNTRY_ID');
                if ($shopCountryId > 0) {
                    $country = new Country($shopCountryId);
                    if (Validate::isLoadedObject($country) && (string)$country->call_prefix !== '') {
                        $cc = (string)$country->call_prefix;
                    }
                }
            } catch (\Throwable $e) { /* fallback a 34 */ }
            $local = ltrim($digits, '0');
            $e164 = '+' . $cc . $local;
        }
        $e164 = preg_replace('/(?!^)\+/', '', $e164);
        $digitsOnly = preg_replace('/\D+/', '', $e164);
        if (strlen($digitsOnly) > 15) {
            $digitsOnly = substr($digitsOnly, 0, 15);
        }
        return '+' . $digitsOnly;
        $raw = trim((string)$raw);
        // Permitir + y dígitos, quitar espacios y otros caracteres
        $raw = preg_replace('~[^0-9\+]~', '', $raw);
        // Quitar ceros de inicio innecesarios (salvo si empieza por +)
        if ($raw !== '' && $raw[0] !== '+') {
            // Mantener 0 inicial si así se usa localmente; no forzamos prefijo país
            $raw = ltrim($raw); // ya sin espacios
        }
        return $raw;
    }

    private function buildMessageForOrder(Order $order, $template)
    {
        $idLang = (int)$order->id_lang ?: (int)Configuration::get('PS_LANG_DEFAULT');

        $cust = new Customer((int)$order->id_customer);
        $firstName = Validate::isLoadedObject($cust) ? (string)$cust->firstname : '';
        $lastName  = Validate::isLoadedObject($cust) ? (string)$cust->lastname  : '';

        $idAddr = (int)$order->id_address_delivery ?: (int)$order->id_address_invoice;
        $addr = $idAddr ? new Address($idAddr) : null;

        // Producto principal (blindado)
        $productName = '';
        $qty = '';
        try {
            $details = $order->getProducts();
            if (is_array($details) && count($details) > 0) {
                $first = $details[0];
                $productName = (string)($first['product_name'] ?? '');
                $qty = isset($first['product_quantity']) ? 'x'.(int)$first['product_quantity'] : '';
            }
        } catch (\Throwable $e) {}

        // Precio total con moneda (blindado)
        $price = (string)$order->total_paid;
        try {
            $currency = new Currency((int)$order->id_currency);
            if (Validate::isLoadedObject($currency)) {
                $price = Tools::displayPrice((float)$order->total_paid, $currency, false);
            }
        } catch (\Throwable $e) {}

        // Envío / plazo (blindado)
        $plazo = '';
        try {
            $carrier = new Carrier((int)$order->id_carrier);
            if (Validate::isLoadedObject($carrier)) { $plazo = (string)$carrier->name; }
        } catch (\Throwable $e) {}

        // Tienda
        $shop = (string)Configuration::get('PS_SHOP_NAME');

        // Link al pedido (blindado: context/link puede ser null en algunos hooks)
        $link = '';
        try {
            if (isset($this->context) && $this->context && isset($this->context->link) && $this->context->link) {
                $link = $this->context->link->getPageLink('order-detail', true, $idLang, [ 'id_order' => (int)$order->id ]);
            }
        } catch (\Throwable $e) { $link = ''; }

        // Número de pedido y tracking (blindado para PS 1.6/1.7/8)
        $orderNumber = (string)$order->reference ?: ('#'.$order->id);
        $tracking = '';
        try {
            // 1) Usar propiedad si existe en esta versión/instancia de Order
            if (property_exists($order, 'shipping_number') && isset($order->shipping_number)) {
                $tracking = (string)$order->shipping_number;
            } else {
                // 2) Fallback: tomar el último tracking de order_carrier
                $idOc = (int)Db::getInstance()->getValue(
                    'SELECT id_order_carrier FROM '._DB_PREFIX_.'order_carrier '
                  . 'WHERE id_order='.(int)$order->id.' ORDER BY id_order_carrier DESC'
                );
                if ($idOc) {
                    $oc = new OrderCarrier($idOc);
                    if (Validate::isLoadedObject($oc) && (string)$oc->tracking_number !== '') {
                        $tracking = (string)$oc->tracking_number;
                    }
                }
            }
        } catch (\Throwable $e) { $tracking = ''; }

        $replacements = [
            '{nombre}' => $firstName,
            '{apellido}' => $lastName,
            '{producto_pedido}' => $productName,
            '{cantidad}' => $qty,
            '{precio}' => $price,
            '{plazo}' => $plazo,
            '{tienda}' => $shop,
            '{link}' => $link,
            '{order_number}' => $orderNumber,
            '{tracking}' => $tracking,
        ];

        $text = (string)$template;
        $text = strtr($text, $replacements);
        $text = preg_replace('~\s{2,}~', ' ', $text);
        return trim($text);
    }

    private function sendWhatsboostMessage($secret, $unique, $phone, $text)
    {
        $url = 'https://whatsboost.net/api/send/whatsapp';
        // Alinear con tu script funcional: account/recipient/type/message
        $payload = [
            'secret'    => (string)$secret,
            'account'   => (string)$unique,     // id único de la cuenta
            'recipient' => (string)$phone,      // teléfono destino E.164
            'type'      => 'text',
            'message'   => (string)$text,
        ];

        $ch = curl_init();
        curl_setopt_array($ch, [
            CURLOPT_URL => $url,
            CURLOPT_POST => true,
            // Pasamos array para que cURL lo envíe como multipart/form-data (como en tu ejemplo)
            CURLOPT_POSTFIELDS => $payload,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_HEADER => false,
            CURLOPT_CONNECTTIMEOUT => 5,
            CURLOPT_TIMEOUT => 8,
            CURLOPT_FOLLOWLOCATION => true,
            CURLOPT_MAXREDIRS => 2,
            CURLOPT_HTTPHEADER => [
                'Accept: application/json',
                'User-Agent: PrestaShop-WhatsBoost/' . $this->version,
            ],
            CURLOPT_SSL_VERIFYPEER => true,
            CURLOPT_SSL_VERIFYHOST => 2,
        ]);
        $body = curl_exec($ch);
        $errno = curl_errno($ch);
        $error = curl_error($ch);
        $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        // Depuración opcional: log de resultado (sin exponer secret).
        $debug = (int)Configuration::get('WHATSBOOST_DEBUG');
        $snippet = '';
        if (is_string($body) && $body !== '') {
            $snippet = Tools::substr($body, 0, 300);
        }

        // No lanzamos excepción para no interferir con el flujo del pedido
        $accountMasked = Tools::substr((string)$unique, 0, 4).'****';
        // WB DEBUG: siempre que debug>=2 dejamos rastro del resultado bruto de la API (OK o error)
        if ($debug >= 2 && method_exists('PrestaShopLogger', 'addLog')) {
            PrestaShopLogger::addLog(
                'WB DEBUG api response http='.(int)$code.' errno='.(int)$errno.' recipient='.pSQL((string)$phone).' account='.$accountMasked.' body='.$snippet,
                1
            );
        }
        if ($errno || (int)$code >= 400) {
            if (method_exists('PrestaShopLogger', 'addLog')) {
                PrestaShopLogger::addLog(
                    '[WhatsBoost] Error enviando WhatsApp: cURL '.(int)$errno.' HTTP '.(int)$code.' err: '.($error ?: '-').
                    ' recipient: '.(string)$phone.' account: '.$accountMasked.' resp: '.$snippet,
                    3
                );
            }
        } elseif ($debug) {
            if (method_exists('PrestaShopLogger', 'addLog')) {
                PrestaShopLogger::addLog(
                    'WB DEBUG api OK HTTP '.(int)$code.' recipient: '.(string)$phone.' account: '.$accountMasked.' resp: '.$snippet,
                    1
                );
            }
        }
    }

    /**
     * Fetch WhatsApp accounts for the given secret and return a compact result.
     * Expected API: https://whatsboost.net/api/get/wa.accounts?secret=...&limit=10&page=1
     * Returns first account's unique if available plus raw decoded JSON.
     */
    public function fetchAccounts($secret)
    {
        $secret = (string)$secret;
        if ($secret === '') {
            return ['ok' => false, 'unique' => '', 'json' => null, 'http' => null, 'body' => null];
        }

        $url = 'https://whatsboost.net/api/get/wa.accounts?secret=' . urlencode($secret) . '&limit=10&page=1';
        $ch = curl_init();
        curl_setopt_array($ch, [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_HEADER => false,
            CURLOPT_CONNECTTIMEOUT => 5,
            CURLOPT_TIMEOUT => 7,
            CURLOPT_FOLLOWLOCATION => true,
            CURLOPT_MAXREDIRS => 3,
            CURLOPT_HTTPHEADER => [
                'Accept: application/json',
                'User-Agent: PrestaShop-WhatsBoost/' . $this->version,
            ],
            CURLOPT_SSL_VERIFYPEER => true,
            CURLOPT_SSL_VERIFYHOST => 2,
        ]);
        $body = curl_exec($ch);
        $errno = curl_errno($ch);
        $error = curl_error($ch);
        $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        if ($errno) {
            return ['ok' => false, 'unique' => '', 'json' => null, 'http' => null, 'body' => null, 'message' => 'cURL '.$errno.': '.$error];
        }

        if ((int)$code !== 200) {
            return ['ok' => false, 'unique' => '', 'json' => null, 'http' => (int)$code, 'body' => (string)$body];
        }

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

        $unique = '';
        if (is_array($decoded)) {
            if (isset($decoded['data'])) {
                $d = $decoded['data'];
                if (is_array($d)) {
                    // Case: list of accounts
                    foreach ($d as $item) {
                        if (!is_array($item)) { continue; }
                        if (!empty($item['unique'])) { $unique = (string)$item['unique']; break; }
                    }
                } elseif (isset($d['unique'])) {
                    // Case: single account object
                    $unique = (string)$d['unique'];
                }
            }
        }

        return ['ok' => true, 'unique' => $unique, 'json' => $decoded, 'http' => (int)$code, 'body' => (string)$body];
    }

    private function installTab()
    {
        $id_parent = (int)Tab::getIdFromClassName('IMPROVE');
        if (!$id_parent) {
            $id_parent = 0; // top-level fallback
        }

        $tab = new Tab();
        $tab->active = 1;
        $tab->class_name = 'AdminWhatsboost';
        $tab->id_parent = $id_parent;
        $tab->module = $this->name;
        if (property_exists($tab, 'icon')) {
            // Use a unique icon class; CSS will render our custom favicon
            $tab->icon = 'icon-whatsboost';
        }
        $tab->name = [];
        foreach (Language::getLanguages(false) as $lang) {
            $tab->name[(int)$lang['id_lang']] = 'WhatsBoost';
        }
        return (bool)$tab->add();
    }

    private function uninstallTab()
    {
        $id_tab = (int)Tab::getIdFromClassName('AdminWhatsboost');
        if ($id_tab) {
            $tab = new Tab($id_tab);
            return (bool)$tab->delete();
        }
        return true;
    }

    private function installAjaxTab()
    {
        // Create four subtabs under our main tab to access module views directly
        $id_parent = (int)Tab::getIdFromClassName('AdminWhatsboost');
        if (!$id_parent) {
            $id_parent = (int)Tab::getIdFromClassName('IMPROVE');
        }
        $defs = [
            ['class' => 'AdminWhatsboostWelcome',  'name_es' => 'Bienvenida',          'name_en' => 'Welcome'],
            ['class' => 'AdminWhatsboostSettings', 'name_es' => 'Ajustes',             'name_en' => 'Settings'],
            ['class' => 'AdminWhatsboostStates',   'name_es' => 'Estados y pedidos',   'name_en' => 'States and orders'],
            ['class' => 'AdminWhatsboostDocs',     'name_es' => 'Documentos',          'name_en' => 'Documents'],
        ];
        $ok = true;
        foreach ($defs as $def) {
            $tab = new Tab();
            $tab->active = 1;
            $tab->class_name = $def['class'];
            $tab->id_parent = $id_parent ?: 0;
            $tab->module = $this->name;
            $tab->name = [];
            foreach (Language::getLanguages(false) as $lang) {
                $iso = isset($lang['iso_code']) ? Tools::strtolower($lang['iso_code']) : '';
                if ($iso === 'en') {
                    $tab->name[(int)$lang['id_lang']] = (string)$def['name_en'];
                } elseif ($iso === 'es') {
                    $tab->name[(int)$lang['id_lang']] = (string)$def['name_es'];
                } else {
                    // Fallback to English for other languages
                    $tab->name[(int)$lang['id_lang']] = (string)$def['name_en'];
                }
            }
            $ok = $ok && (bool)$tab->add();
        }
        return (bool)$ok;
    }

    private function uninstallAjaxTab()
    {
        // Remove new subtabs and legacy Ajax tab if present
        $classes = [
            'AdminWhatsboostWelcome',
            'AdminWhatsboostSettings',
            'AdminWhatsboostStates',
            'AdminWhatsboostDocs',
            'AdminWhatsboostAjax', // legacy
        ];
        $ok = true;
        foreach ($classes as $cls) {
            $id_tab = (int)Tab::getIdFromClassName($cls);
            if ($id_tab) {
                $tab = new Tab($id_tab);
                $ok = $ok && (bool)$tab->delete();
            }
        }
        return (bool)$ok;
    }

    public function verifySecret($secret, $endpoint)
    {
        $endpoint = $endpoint ?: 'https://whatsboost.net/api/get/subscription';
        $sep = (strpos($endpoint, '?') === false) ? '?' : '&';
        $url = $endpoint . $sep . 'secret=' . urlencode($secret);

        $ch = curl_init();
        $headers = [
            'Accept: application/json',
            'User-Agent: PrestaShop-WhatsBoost/' . $this->version,
        ];
        curl_setopt_array($ch, [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_HEADER => false,
            CURLOPT_CONNECTTIMEOUT => 5,
            CURLOPT_TIMEOUT => 7,
            CURLOPT_FOLLOWLOCATION => true,
            CURLOPT_MAXREDIRS => 3,
            CURLOPT_HTTPHEADER => $headers,
            CURLOPT_SSL_VERIFYPEER => true,
            CURLOPT_SSL_VERIFYHOST => 2,
        ]);

        $body = curl_exec($ch);
        $errno = curl_errno($ch);
        $error = curl_error($ch);
        $info = curl_getinfo($ch);
        curl_close($ch);

        if ($errno) {
            return [
                'ok' => false,
                'message' => sprintf($this->l('Error de red (cURL %d): %s'), $errno, $error),
                'http' => null,
                'body' => null,
                'json' => null,
            ];
        }

        $code = isset($info['http_code']) ? (int)$info['http_code'] : 0;
        if ($code !== 200) {
            $snippet = '';
            if (is_string($body) && $body !== '') {
                $snippet = Tools::substr($body, 0, 300);
            }
            return [
                'ok' => false,
                'message' => 'Error: HTTP Code ' . $code . ', Response: ' . $snippet,
                'http' => $code,
                'body' => $body,
                'json' => null,
            ];
        }

        $json = null;
        if (is_string($body) && $body !== '') {
            $decoded = json_decode($body, true);
            if (is_array($decoded)) {
                $json = $decoded;
                $active = false;
                if (isset($decoded['active'])) { $active = (bool)$decoded['active']; }
                if (isset($decoded['valid'])) { $active = $active || (bool)$decoded['valid']; }
                if (isset($decoded['status'])) {
                    $status = $decoded['status'];
                    if (is_numeric($status)) {
                        $active = $active || ((int)$status === 200);
                    } else {
                        $sv = strtolower((string)$status);
                        $active = $active || ($sv === 'active' || $sv === '200');
                    }
                }

                if ($active) {
                    $snippet = '';
                    if (is_string($body) && $body !== '') { $snippet = Tools::substr($body, 0, 300); }
                    return [
                        'ok' => true,
                        'message' => $this->l('Success: ').$snippet,
                        'http' => 200,
                        'body' => $body,
                        'json' => $json,
                    ];
                }

                return [
                    'ok' => false,
                    'message' => $this->l('Clave inválida, expirada o plan no activo.'),
                    'http' => 200,
                    'body' => $body,
                    'json' => $json,
                ];
            }
        }

        // Fallback: 200 con cuerpo no vacío
        if (is_string($body) && Tools::strlen($body) > 0) {
            $snippet = Tools::substr($body, 0, 300);
            return [
                'ok' => true,
                'message' => $this->l('Success: ').$snippet,
                'http' => 200,
                'body' => $body,
                'json' => null,
            ];
        }

        return [
            'ok' => false,
            'message' => $this->l('Respuesta no JSON o vacía al verificar la clave.'),
            'http' => 200,
            'body' => $body,
            'json' => null,
        ];
    }
}
