<?php
/**
 * 2026 Paysera
 *
 * NOTICE OF LICENSE
 *
 * This source file is subject to the Academic Free License (AFL 3.0)
 * that is bundled with this package in the file LICENSE.txt.
 * It is also available through the world-wide-web at this URL:
 * http://opensource.org/licenses/afl-3.0.php
 * If you did not receive a copy of the license and are unable to
 * obtain it through the world-wide-web, please send an email
 * to license@prestashop.com so we can send you a copy immediately.
 *
 * @author Paysera <plugins@paysera.com>
 * @copyright 2026 Paysera
 * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0)
 * International Registered Trademark & Property of Paysera
 */

declare(strict_types=1);

use Paysera\PrestashopPayments\Checker\ModuleConflictChecker;
use Paysera\PrestashopPayments\Enum\ConfigurationKey;
use Paysera\PrestashopPayments\Exception\HookMethodNotFoundException;
use Paysera\PrestashopPayments\Helper\TabHelper;
use Paysera\PrestashopPayments\Installer\ModuleInstaller;
use Paysera\PrestashopPayments\Logger\PayseraLogger;
use Paysera\PrestashopPayments\Provider\AssetUrlProvider;
use Paysera\PrestashopPayments\Provider\PaymentOptionsProvider;
use Paysera\PrestashopPayments\Provider\SettingsProvider;
use Paysera\PrestashopPayments\Repository\ApiAuthTokenRepository;
use Paysera\PrestashopPayments\Service\AuthorizationService;
use Paysera\PrestashopPayments\Service\CartRestorationService;
use Paysera\PrestashopPayments\Service\CheckoutErrorHandler;
use Paysera\PrestashopPayments\Service\CmsToolsFacade;
use Paysera\PrestashopPayments\Service\TranslationFileRefresher;
use Paysera\PrestashopPayments\Service\TwigEnvironmentFactory;
use Paysera\PrestashopPayments\Service\TwigExtensionRegistry;
use Psr\Log\LoggerInterface;
use Twig\Environment;

if (!defined('_PS_VERSION_')) {
    exit;
}

require_once __DIR__ . '/vendor/autoload.php';

class PayseraPayments extends PaymentModule
{
    public const PLUGIN_NAMESPACE = 'plugin_prestashop_checkout_v3';
    public const TRANSLATION_DOMAIN = 'Modules.Payserapayments.Global';
    public const MODULE_NAME = 'payserapayments';
    public const VERSION = '1.2.0';
    public const MODULE_DIR = _PS_MODULE_DIR_ . self::MODULE_NAME . '/';
    public const MODULE_URI = __PS_BASE_URI__ . 'modules/' . self::MODULE_NAME . '/';
    public const REQUIRED_PHP_EXTENSIONS = ['curl', 'intl', 'json', 'mbstring', 'openssl'];

    public const HOOKS = [
        'actionFrontControllerInitAfter',
        'displayBackOfficeHeader',
        'displayHeader',
        'paymentOptions',
        'displayPaymentReturn',
        'displayPaymentTop',
        'actionObjectLanguageAddAfter',
        'actionUpdateLangAfter',
    ];

    private ?LoggerInterface $logger;

    public function __construct()
    {
        $this->logger = null;
        $this->name = 'payserapayments';
        $this->tab = 'payments_gateways';
        $this->version = '1.2.0';
        $this->author = 'Paysera';
        $this->need_instance = 0;
        $this->ps_versions_compliancy = ['min' => '8.0.0', 'max' => '9.1.99'];
        $this->bootstrap = true;

        parent::__construct();

        $this->displayName = $this->getTranslator()->trans(
            'Paysera Checkout',
            [],
            'Modules.Payserapayments.Global'
        );
        $this->description = $this->getTranslator()->trans(
            'Accept payments through Paysera payment gateway',
            [],
            'Modules.Payserapayments.Global'
        );
        $this->confirmUninstall = $this->getTranslator()->trans(
            'Are you sure you want to uninstall?',
            [],
            'Modules.Payserapayments.Global'
        );
    }

    public function install()
    {
        if ($this->rejectIfMissingPhpExtensions()) {
            return false;
        }

        if ($this->rejectIfModuleConflict()) {
            return false;
        }

        if (!parent::install()) {
            return false;
        }

        if (!$this->registerHooks(static::HOOKS)) {
            return false;
        }

        Configuration::updateValue(ConfigurationKey::POST_INSTALL_SETUP_COMPLETED, false);

        return true;
    }

    public function uninstall()
    {
        try {
            $installer = $this->get(ModuleInstaller::class);
            $installer->uninstall();
        } catch (Throwable $exception) {
            $this->_errors[] = 'Module uninstallation failed: ' . $exception->getMessage();
        }

        return parent::uninstall();
    }

    public function enable($force_all = false)
    {
        if ($this->rejectIfModuleConflict()) {
            return false;
        }

        return parent::enable($force_all);
    }

    public function disable($force_all = false)
    {
        try {
            $this->get(ApiAuthTokenRepository::class)->delete();
        } catch (Throwable $exception) {
            $this->_errors[] = 'Module deactivation failed: ' . $exception->getMessage();
        }

        return parent::disable($force_all);
    }

    private function registerHooks(array $hooks): bool
    {
        foreach ($hooks as $hook) {
            $methodName = 'hook' . ucfirst($hook);
            if (!method_exists($this, $methodName)) {
                throw new HookMethodNotFoundException($methodName, static::class);
            }
            parent::registerHook($hook);
        }

        return true;
    }

    public function getContent()
    {
        if (!$this->active) {
            return $this->displayError(
                $this->getTranslator()->trans(
                    'Please enable the module to access configuration.',
                    [],
                    self::TRANSLATION_DOMAIN
                )
            );
        }

        (new CmsToolsFacade())->redirectAdmin(
            $this->context->link->getAdminLink('PaymentSettingsPayseraPaymentsTab')
        );
    }

    public function hookPaymentOptions()
    {
        $this->restorePendingPaymentCart();

        $options = $this->get(PaymentOptionsProvider::class)->getPaymentOptions($this->context, $this);

        if ($options === []) {
            return [];
        }

        $this->registerFrontAssets();

        return $options;
    }

    private function registerFrontAssets(): void
    {
        $this->context->controller->registerStylesheet(
            'payserapayments-payment-front',
            'modules/' . self::MODULE_NAME . '/views/css/payment-front.css',
            ['media' => 'all', 'priority' => 150, 'version' => self::VERSION]
        );
        $this->context->controller->registerJavascript(
            'payserapayments-payment-methods',
            'modules/' . self::MODULE_NAME . '/views/js/payment-methods.js',
            ['position' => 'bottom', 'priority' => 150, 'version' => self::VERSION]
        );
    }

    private function restorePendingPaymentCart(): void
    {
        $this->get(CartRestorationService::class)
            ->restorePendingPaymentCart($this->context);
    }

    public function hookDisplayPaymentReturn()
    {
        if (!$this->active) {
            return;
        }

        return $this->getTranslator()->trans(
            'Your payment has been processed.',
            [],
            'Modules.Payserapayments.Global'
        );
    }

    public function hookDisplayBackOfficeHeader(): void
    {
        try {
            if (!Configuration::get(ConfigurationKey::POST_INSTALL_SETUP_COMPLETED)) {
                $installer = $this->get(ModuleInstaller::class);
                $installer->performPostInstallSetup();
            } else {
                $translationRefresher = $this->get(TranslationFileRefresher::class);
                $translationRefresher->refreshIfNeeded();
            }
        } catch (Throwable $exception) {
            try {
                $this->logger()->error('Back office header hook failed: ' . $exception->getMessage());
            } catch (Throwable) {
            }
        }

        $this->warnIfModuleConflict();

        $this->context->controller->addCss($this->get(AssetUrlProvider::class)->getUrl('views/css/tab.css'));
    }

    private function warnIfModuleConflict(): void
    {
        if (!$this->get(ModuleConflictChecker::class)->hasConflict()) {
            return;
        }

        $this->get(SettingsProvider::class)->setPaymentsEnabled(false);

        $this->context->controller->warnings[] = $this->getTranslator()->trans(
            'Only one version of the Paysera Checkout plugin can be active at a time. Please deactivate the currently enabled version before activating another.',
            [],
            self::TRANSLATION_DOMAIN
        );
    }

    public function hookActionFrontControllerInitAfter(): void
    {
        $controllerName = $this->context->controller->php_self ?? '';

        if (!in_array($controllerName, ['cart', 'order', 'order-opc'], true)) {
            return;
        }

        $cartId = $this->get(CartRestorationService::class)
            ->consumePendingCartId($this->context);

        if ($cartId === null) {
            return;
        }

        $url = $this->context->link->getModuleLink(
            $this->name,
            'failure',
            ['id_cart' => $cartId, 'restore' => 1],
            true
        );

        $this->get(CmsToolsFacade::class)->redirect($url);
    }

    public function hookDisplayHeader(): void
    {
        $controllerName = $this->context->controller->php_self;
        if ($controllerName === 'order' || $controllerName === 'order-opc') {
            $this->registerFrontAssets();
        }
    }

    public function hookDisplayPaymentTop()
    {
        $errorHandler = new CheckoutErrorHandler($this->context->cookie);
        $errorMessage = $errorHandler->getAndClearError();

        return $this->renderTwig('payment_error.html.twig', [
            'error_message' => $errorMessage ?? '',
        ]);
    }

    public function hookActionObjectLanguageAddAfter(): void
    {
        $this->get(TabHelper::class)->refreshTabs();
        $this->get(TranslationFileRefresher::class)->generateTranslationFiles();
    }

    public function hookActionUpdateLangAfter(): void
    {
        $this->get(TabHelper::class)->refreshTabs();
        $this->get(TranslationFileRefresher::class)->generateTranslationFiles();
    }

    public function isAuthorized(): bool
    {
        $service = $this->get(AuthorizationService::class);
        if ($service === null) {
            return false;
        }

        return $service->isAuthorized();
    }

    public function isUsingNewTranslationSystem(): bool
    {
        return true;
    }

    private function createModuleConflictChecker(): ModuleConflictChecker
    {
        try {
            return $this->get(ModuleConflictChecker::class);
        } catch (Throwable) {
            return new ModuleConflictChecker(
                $this->get('prestashop.module.manager'),
                new PayseraLogger()
            );
        }
    }

    private function rejectIfMissingPhpExtensions(): bool
    {
        $missingExtensions = array_filter(
            self::REQUIRED_PHP_EXTENSIONS,
            static fn (string $extension): bool => !extension_loaded($extension)
        );

        if ($missingExtensions === []) {
            return false;
        }

        $this->_errors[] = $this->getTranslator()->trans(
            'The Paysera Checkout plugin requires the following PHP extensions: %extensions%. Please enable them on your server and try again.',
            ['%extensions%' => implode(', ', $missingExtensions)],
            self::TRANSLATION_DOMAIN
        );

        return true;
    }

    private function rejectIfModuleConflict(): bool
    {
        if (!$this->createModuleConflictChecker()->hasConflict()) {
            return false;
        }

        $this->_errors[] = $this->getTranslator()->trans(
            'Only one version of the Paysera Checkout plugin can be active at a time. Please deactivate the currently enabled version before activating another.',
            [],
            self::TRANSLATION_DOMAIN
        );

        return true;
    }

    private function logger(): LoggerInterface
    {
        if ($this->logger === null) {
            $logger = $this->get('paysera.logger');
            if (!$logger instanceof LoggerInterface) {
                throw new RuntimeException('Logger not available');
            }
            $this->logger = $logger;
        }

        return $this->logger;
    }

    private function renderTwig(string $template, array $variables = []): string
    {
        try {
            $twig = $this->get('paysera.twig');
            if ($twig instanceof Environment) {
                return $twig->render($template, $variables);
            }
        } catch (Throwable $exception) {
            $this->logger()->warning('Failed to get Twig from DI container: ' . $exception->getMessage());
        }

        $container = $this->get('service_container');
        $registry = new TwigExtensionRegistry(
            $container,
            $this->logger()
        );
        $factory = new TwigEnvironmentFactory(
            $registry,
            $this->logger(),
            $this->getLocalPath()
        );

        return $factory->create()->render($template, $variables);
    }
}
