<?php

/**
 * Copyright 2024-2025 Simply.IN Sp. z o.o.
 *
 * NOTICE OF LICENSE
 *
 * Licensed under the EUPL-1.2 or later.
 * You may not use this work except in compliance with the Licence.
 *
 * Copy of the Licence is available at:
 * https://joinup.ec.europa.eu/software/page/eupl
 * It is bundled with this package in the file LICENSE.txt
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the Licence is distributed on an as is basis,
 * without warranties or conditions of any kind, either express or implied.
 * Check the Licence for the specific language governing permissions
 * and limitations under the License.
 *
 * @author   Simply.IN Sp. z o.o.
 * @copyright 2024-2025 Simply.IN Sp. z o.o.
 * @license   https://joinup.ec.europa.eu/software/page/eupl
 */

use PrestaShop\PrestaShop\Core\Payment\PaymentOption;

// The module targets PHP 8.1+ (readonly properties, enums, first-class syntax). Bail out
// quietly instead of fataling mid-request on an older runtime.
if (version_compare(PHP_VERSION, '8.1.0', '<')) {
    if (defined('_PS_VERSION_') && class_exists('PrestaShopLogger')) {
        PrestaShopLogger::addLog('Simply.IN requires PHP 8.1+ (running ' . PHP_VERSION . '); module not loaded.', 3);
    }

    return;
}

$simplyinAutoload = dirname(__FILE__) . '/vendor/autoload.php';
if (file_exists($simplyinAutoload)) {
    require_once $simplyinAutoload;
}

require_once dirname(__FILE__) . '/models/SimplyInInstallSqlTrait.php';
require_once dirname(__FILE__) . '/models/SimplyInPoint.php';
require_once dirname(__FILE__) . '/models/SimplyInOrder.php';
require_once dirname(__FILE__) . '/models/SimplyInOrderSync.php';
require_once dirname(__FILE__) . '/models/SimplyInEmailCache.php';
require_once dirname(__FILE__) . '/models/SimplyInPayments.php';

if (!defined('_PS_VERSION_')) {
    exit;
}
class Simplyin extends PaymentModule
{
    /** @var \SimplyIn\Session\SimplyInSessionInterface|null */
    public $simplyin_session;

    /** @var \SimplyIn\Api\SimplyInApiClient|null */
    public $apiClient;

    public $backend_url = 'https://prod.backend.simply.in/api/';

    public string $merchantApiKey = '';

    /** @deprecated since 2.5 — use {@see Simplyin::$merchantApiKey}. Kept as public BC alias, mirrored in {@see Simplyin::loadObjects()}. */
    public string $merchand_api_key = '';

    public $version_ps;

    /** @var \SimplyIn\Front\OpcDetector|null */
    public $opcDetector;

    /** @var \SimplyIn\Front\AssetRegistrar|null */
    public $assetRegistrar;

    /** @var \SimplyIn\Front\JsHeaderDataBuilder|null */
    public $jsHeaderDataBuilder;

    /** @var \SimplyIn\Security\TokenService|null */
    public $tokenService;

    /** @var \SimplyIn\Payment\PaymentStatusChecker|null */
    public $paymentStatusChecker;

    /** @var \SimplyIn\Payment\PaymentOptionsBuilder|null */
    public $paymentOptionsBuilder;

    /** @var \SimplyIn\Payment\OrderConfirmationRenderer|null */
    public $orderConfirmationRenderer;

    /** @var \SimplyIn\Checkout\CheckoutSessionRepository|null */
    public $checkoutSessionRepository;

    /** @var \SimplyIn\Service\OrderSync\OrderCreationHandler|null */
    public $orderCreationHandler;

    /** @var \SimplyIn\Service\MerchantSettingsSync|null */
    public $merchantSettingsSync;

    /** @var \SimplyIn\Service\TrackingNumberSync|null */
    public $trackingNumberSync;

    /** @var \SimplyIn\Carrier\CarrierConfigRegistry|null */
    public $carrierConfigRegistry;

    /** @var \SimplyIn\Session\SessionHelper|null */
    public $sessionHelper;

    /** @var \SimplyIn\BackOffice\AdminOrderDisplay|null */
    public $adminOrderDisplay;

    /** @var \SimplyIn\Payment\OrderStateUpdater|null */
    public $orderStateUpdater;

    /** @var \SimplyIn\Checkout\AccountUpdateHandler|null */
    public $accountUpdateHandler;

    /** @var \SimplyIn\BackOffice\OrdersGridDecorator|null */
    public $ordersGridDecorator;

    /** @var \SimplyIn\Service\OrderSync\OrderSyncService|null */
    public $orderSyncService;

    /** @var \SimplyIn\Auth\RsaSigner|null */
    public $rsaSigner;

    /** @var \SimplyIn\Auth\RsaRegistrar|null */
    public $rsaRegistrar;

    public function __construct()
    {
        $this->name = 'simplyin';
        $this->tab = 'shipping_logistics';
        $this->version = '8.1.22';
        $this->author = 'Simply.IN';
        $this->need_instance = 1;
        $this->bootstrap = true;
        parent::__construct();
        $this->controllers = ['ajax', 'cron', 'paymentajax', 'validation', 'webhook'];

        $this->displayName = $this->l('Simply.IN');
        $this->description = $this->l('Simply.IN Super easy and fast 1-click shopping  ');
        $this->confirmUninstall = $this->l('Are you sure you want to uninstall Simply.IN?');
        $this->ps_versions_compliancy = ['min' => '8.0.0', 'max' => '9.99.99'];
        // Two buckets only: the module requires PS >= 8.0.0, so the 1.6/1.7 branches were dead —
        // and worse, the old chain labelled every 8.0.1+ shop (8.1, 8.2) as "9".
        $this->version_ps = Tools::version_compare(_PS_VERSION_, '9.0.0', '>=') ? '9' : '8';
        if ($this->apiClient === null) {
            $this->loadObjects();
        }
    }

    public function install()
    {
        // A fresh, clean install can never be in the legacy-conflict state — clear any stale
        // flag so the back-office warning disappears once the merchant reinstalls cleanly.
        Configuration::deleteByName('SIMPLYIN_LEGACY_CONFLICT');

        return parent::install() && (new \SimplyIn\Install\Installer($this))->installAll();
    }

    public function uninstall()
    {
        (new \SimplyIn\Install\Uninstaller($this))->uninstallAll();

        return parent::uninstall();
    }

    public function getContent()
    {
        return (new \SimplyIn\BackOffice\ConfigurationScreen($this))->render();
    }

    public function hookActionOrderStatusPostUpdate($params)
    {
        $this->adminOrderDisplay->onOrderStatusPostUpdate($params);
    }

    public function hookDisplayAdminOrderMain($params)
    {
        return $this->adminOrderDisplay->renderOrderPanel($this, $params, __FILE__);
    }

    public function hookDisplayBackOfficeHeader()
    {
        $this->warnAboutLegacyConflict();
        $this->adminOrderDisplay->registerAdminAssets($this);
        // Best-effort RSA key registration, throttled to avoid repeating the handshake on every request.
        $this->attemptRsaRegistration();
    }

    /**
     * Keeps warning in the back office while a legacy PrestaShop 1.7 Simply.IN install is still
     * present. The flag is set by upgrade-8.1.0.php when an in-place upgrade from that line is
     * refused, and cleared on a clean (re)install. Non-fatal — a persistent warning banner only.
     */
    private function warnAboutLegacyConflict()
    {
        $legacyVersion = (string) Configuration::get('SIMPLYIN_LEGACY_CONFLICT');
        if ($legacyVersion === '') {
            return;
        }
        $this->context->controller->warnings[] = sprintf(
            $this->l('Simply.IN: an older PrestaShop 1.7 version of this module (v%s) was detected and its in-place upgrade was blocked. That version is not compatible with PrestaShop 8/9+. Please UNINSTALL it completely (including its data), then install the dedicated "Simply.IN for PrestaShop 8/9+" module on a clean slate. Checkout will not work correctly until this is resolved.'),
            $legacyVersion
        );
    }

    /**
     * Throttled RSA key-registration attempt; no-op once already registered for the current API key.
     */
    private function attemptRsaRegistration(int $throttleSeconds = 300)
    {
        if ($this->rsaRegistrar === null || $this->merchantApiKey === '') {
            return;
        }
        $last = (int) Configuration::get(\SimplyIn\Auth\RsaRegistrar::LAST_REGISTER_KEY);
        if ($last > 0 && (time() - $last) < $throttleSeconds) {
            return;
        }
        Configuration::updateValue(\SimplyIn\Auth\RsaRegistrar::LAST_REGISTER_KEY, (string) time());
        $this->rsaRegistrar->ensureRegistered();
    }

    /**
     * Activates RSA request signing immediately for a freshly validated API key, instead of
     * waiting for the periodic back-office/cron check to pick it up.
     */
    public function registerRsaForApiKey($apiKey): \SimplyIn\Api\ConnectionOutcome
    {
        $apiKey = (string) $apiKey;
        if ($apiKey === '') {
            return \SimplyIn\Api\ConnectionOutcome::AUTH_REJECTED;
        }
        if ($this->rsaSigner === null) {
            return \SimplyIn\Api\ConnectionOutcome::TECHNICAL_FAILURE;
        }
        $registrar = new \SimplyIn\Auth\RsaRegistrar(
            $this->rsaSigner,
            rtrim((string) $this->backend_url, '/') . '/',
            $apiKey,
            (string) $this->context->shop->getBaseURL(true),
        );
        // Mark the throttle so this same request cycle does not repeat the handshake right after
        // we have just performed it.
        Configuration::updateValue(\SimplyIn\Auth\RsaRegistrar::LAST_REGISTER_KEY, (string) time());

        if ($registrar->ensureRegistered()) {
            return \SimplyIn\Api\ConnectionOutcome::SUCCESS;
        }

        if ($registrar->wasAlreadyRegistered()) {
            return \SimplyIn\Api\ConnectionOutcome::ALREADY_REGISTERED;
        }

        return $registrar->lastAttemptWasAuthRejected()
            ? \SimplyIn\Api\ConnectionOutcome::AUTH_REJECTED
            : \SimplyIn\Api\ConnectionOutcome::TECHNICAL_FAILURE;
    }

    /**
     * @param array<string, mixed> $params
     */
    public function hookActionOrderGridDefinitionModifier(array $params): void
    {
        $this->ordersGridDecorator->modifyDefinition($params);
    }

    /**
     * @param array<string, mixed> $params
     */
    public function hookActionOrderGridQueryBuilderModifier(array $params): void
    {
        $this->ordersGridDecorator->modifyQuery($params);
    }

    public function checkController()
    {
        return $this->opcDetector->isCheckoutController();
    }

    public function getOPC()
    {
        return $this->opcDetector->detect();
    }

    public function hookDisplayHeader()
    {
        if (!Configuration::get('SIMPLYIN_CONNECTED')) {
            return;
        }
        if (!$this->opcDetector->isCheckoutController()) {
            return;
        }

        Media::addJsDef($this->getJsDefaultHeader());
    }

    public function hookActionFrontControllerSetMedia()
    {
        if (!Configuration::get('SIMPLYIN_CONNECTED')) {
            return;
        }
        if (!$this->opcDetector->isCheckoutController()) {
            return;
        }

        $this->assetRegistrar->registerHeaderAssets(
            $this->context->controller,
            $this->name,
            (string) $this->version,
        );
    }

    public function hookDisplayPaymentTop()
    {
        if (!Configuration::get('SIMPLYIN_CONNECTED')) {
            return '';
        }

        return '<div id="simplyin-phone-prerender" style="display:none">'
            . (new \SimplyIn\Front\NewAccountFormRenderer($this->context, $this))->render((string) $this->getOPC())
            . '</div>';
    }

    public function hookActionModuleRegisterCronJobs()
    {
        return [
            [
                'task' => 'cron',
                'module' => $this->name,
                'freq' => '*/15 * * * *',
            ],
        ];
    }

    public function getAllAvailableShippingMethods()
    {
        return $this->carrierConfigRegistry->getAllAvailableShippingMethods((int) $this->context->language->id);
    }

    public function getAsyncToken()
    {
        return $this->tokenService->getAsyncToken(_COOKIE_IV_);
    }

    public function getAvailableCarriers()
    {
        return $this->carrierConfigRegistry->getAvailableCarriers();
    }

    public function getCarriersKey()
    {
        return $this->carrierConfigRegistry->getCarrierKeys();
    }

    public function getJsDefaultHeader($override = [])
    {
        return $this->jsHeaderDataBuilder->build($this, $override);
    }

    public function checkPaymentAjax()
    {
        return $this->paymentStatusChecker->check($this);
    }

    public function hookActionAdminOrdersTrackingNumberUpdate($params)
    {
        $this->trackingNumberSync->handle($params);
    }

    public function hookActionObjectOrderCarrierUpdateAfter($params)
    {
        if ($this->trackingNumberSync !== null) {
            $this->trackingNumberSync->handleOrderCarrier($params);
        }
    }

    public function getBackendUrl()
    {
        return $this->backend_url;
    }

    public function simplyInUpdateMerchantSettings($new_api_key = '', $admin = false): \SimplyIn\Api\ConnectionOutcome
    {
        return $this->merchantSettingsSync->sync((string) $new_api_key, (bool) $admin);
    }

    public function hookActionValidateOrder($params)
    {
        $this->orderCreationHandler->handle($params);
    }

    public function checkUserExist($user_email)
    {
        return SimplyInEmailCache::checkUserExist($user_email);
    }

    public function getCronSecureKey()
    {
        return $this->tokenService->getCronSecureKey();
    }

    public function execCron()
    {

        // The cron key derivation changed in 8.1.20; keep accepting the previous one so an
        // already-scheduled crontab entry does not silently stop working after the upgrade.
        $providedKey = (string) Tools::getValue('secure_key');
        if (!hash_equals((string) $this->getCronSecureKey(), $providedKey)
            && !hash_equals($this->tokenService->getLegacyCronSecureKey(), $providedKey)
        ) {
            return false;
        }
        // Zero-touch RSA registration backstop + webhook-dedup housekeeping.
        if ($this->rsaRegistrar !== null) {
            $this->rsaRegistrar->ensureRegistered();
        }
        (new \SimplyIn\Webhook\WebhookDeduplicator())->prune();

        $this->orderSyncService->refreshPending();
        $this->orderSyncService->synchronizePending();

        return true;
    }

    public function hookActionCarrierUpdate($params)
    {
        $carrier = $params['carrier'] ?? null;
        if ($carrier === null) {
            return;
        }
        $this->carrierConfigRegistry->handleCarrierUpdate((int) $carrier->id);
    }

    public function getCartCarrierCost()
    {
        return $this->carrierConfigRegistry->getCartCarrierCost($this->context);
    }

    public function getAjaxToken()
    {
        return $this->tokenService->getAjaxToken();
    }

    public function getCartToken($id_cart = false)
    {
        if ($id_cart) {
            $id_cart = (int) $this->context->cart->id;
        }

        return $this->tokenService->getCartToken($id_cart);
    }

    public function getCustomerEmail()
    {
        return $this->sessionHelper->getCustomerEmail();
    }

    public function hookActionCustomerAccountUpdate($params)
    {
        $this->accountUpdateHandler->handle($params);
    }

    public function getCheckoutSessionData()
    {
        return $this->checkoutSessionRepository->getCheckoutSessionData((int) $this->context->cart->id);
    }

    public function upgradeSessionData($data, $redirect = true)
    {
        $this->checkoutSessionRepository->upgradeSessionData($this->context, $redirect);
    }

    public function hookActionCustomerLogoutBefore($params)
    {
        $customer = $params['customer'] ?? null;
        if ($customer instanceof Customer && !$customer->is_guest) {
            $this->logout();
        }
    }

    public function logout()
    {
        $this->sessionHelper->logout();
    }

    public function getOrderToken($id_cart)
    {
        return $this->tokenService->getOrderToken($id_cart);
    }

    public function getNewAccountPhone()
    {
        return $this->sessionHelper->getNewAccountPhone();
    }

    public function isSimplyInAuth()
    {
        return $this->sessionHelper->isAuthenticated();
    }

    public function hookPaymentOptions($params)
    {
        return $this->paymentOptionsBuilder->build($this, $params['cart']);
    }

    public function hookDisplayOrderConfirmation($params)
    {
        return $this->orderConfirmationRenderer->render($this, $params['order']);
    }

    public function getPaymentCheckToken($ajax_token, $cart_token)
    {
        return $this->tokenService->getPaymentCheckToken((string) $ajax_token, (string) $cart_token);
    }

    public function upgradeModule($version)
    {
        return (new \SimplyIn\Install\UpgradeRunner())->run($this, (string) $version);
    }

    public function updateOrderState($order, $new_state, $callback)
    {
        $this->orderStateUpdater->update($order, (int) $new_state, (string) $callback);
    }

    public function loadObjects()
    {
        (new \SimplyIn\Bootstrap\ModuleBootstrap($this))->bootstrap();
    }
    // Do not remove translations for objects
    // $this->l('blik');
    // $this->l('card');
    // $this->l('visa');
    // $this->l('mastercard');
    // $this->l('amex');
    // $this->l('discover');
    // $this->l('diners');
}
