<?php

declare(strict_types=1);

namespace Plugin\tnt_stabila_shop;

use JTL\Backend\AdminIO;
use JTL\Events\Dispatcher;
use JTL\Events\Event;
use JTL\Plugin\Bootstrapper;
use JTL\Shop;
use Plugin\tnt_stabila_shop\adminmenu\Controller\AjaxEndpoint;
use Plugin\tnt_stabila_shop\src\CronJobs\CronJob;
use Plugin\tnt_stabila_shop\src\CronJobs\SentrySpoolCronJob;
use Plugin\tnt_stabila_shop\src\Helper\IOHelper;
use Plugin\tnt_stabila_shop\src\Repositories\DatabaseRepository;
use Plugin\tnt_stabila_shop\src\Repositories\SessionRepository;
use Plugin\tnt_stabila_shop\src\Services\PerformanceService;
use Plugin\tnt_stabila_shop\src\Services\SentryService;

class Bootstrap extends Bootstrapper
{
    /**
     * @throws \Exception
     */
    public function boot(Dispatcher $dispatcher): void
    {
        parent::boot($dispatcher);
        $plugin = $this->getPlugin();
        require_once $plugin->getPaths()->getBasePath() . 'vendor/autoload.php';

        $cache    = Shop::Container()->getCache();
        $database = new DatabaseRepository($this->getDB());
        // Merchant-supplied Sentry DSN (managed in the Dashboard's Quality Booster block). It is
        // mandatory: with no DSN, SentryService stays inactive and no error tracking runs.
        $storedDsn     = $database->getSetting('sentryDsn');
        $sentryDsn     = \is_string($storedDsn) ? $storedDsn : '';
        $sentryService = new SentryService(
            $database,
            new SessionRepository('tnt_stabila_shop'),
            $cache,
            $sentryDsn
        );
        $dispatcher->listen(
            eventNames: 'shop.hook.' . \HOOK_GLOBALINCLUDE_INC,
            listener: fn () => $sentryService->setSentryHandlers()
        );
        $dispatcher->listen(Event::MAP_CRONJOB_TYPE, static function (array &$args) {
            if ($args['type'] === CronJob::CRON_JOB_ID) {
                $args['mapping'] = CronJob::class;
            }
            if ($args['type'] === SentrySpoolCronJob::CRON_JOB_ID) {
                $args['mapping'] = SentrySpoolCronJob::class;
            }
        });
        if (Shop::isFrontend()) {
            $dispatcher->listen(
                eventNames: 'shop.hook.' . \HOOK_LETZTERINCLUDE_INC,
                listener: fn () => $sentryService->collectData()
            );
            $dispatcher->listen(
                eventNames: 'shop.hook.' . \HOOK_BESTELLABSCHLUSS_INC_BESTELLUNGINDB_ENDE,
                listener: $sentryService->saveOrderData(...)
            );
            $performanceService = new PerformanceService(
                new DatabaseRepository($this->getDB()),
                new SessionRepository('tnt_stabila_shop'),
                $cache
            );
            $dispatcher->listen(
                eventNames: 'shop.hook.' . \HOOK_GLOBALINCLUDE_INC,
                listener: fn () => $performanceService->boostPerformance()
            );
        } else {
            // Add notifications to the session
            $sentryService->addNotificationsToSession();
            // Register Smarty function to prepare IO backend requests
            $smarty = Shop::Smarty();
            if (!isset($smarty->registered_plugins['function']['stbIO'])) {
                $smarty->registerPlugin(
                    type: 'function',
                    name: 'stbIO',
                    callback: IOHelper::prepareIORequest(...)
                );
            }
            // Register the Stabila endpoint for AJAX backend requests
            $ioEndpoint = new AjaxEndpoint(
                plugin: $this->getPlugin()
            );
            AdminIO::getInstance()->register(
                name: 'tntStabilaEndpoint',
                function: $ioEndpoint->handleRequest(...)
            );
        }
    }

    /**
     * @param mixed ...$args
     * @throws \JsonException
     * @throws \Exception
     */
    public function prepareIORequest(mixed ...$args): string
    {
        $params = $args[0] ?? [];
        if (\is_array($params) === false) {
            $params = [];
        }

        return Shop::getAdminURL()
            . '/io?token=' . (Shop::getAdminSessionToken() ?? '')
            . '&io=' . \urlencode(\json_encode([
                'name' => 'tntStabilaEndpoint',
                'params' => $params
            ], JSON_THROW_ON_ERROR));
    }

    /**
     * @inheritdoc
     */
    public function installed(): void
    {
        parent::installed();
        $this->getDB()->insert('tcron', CronJob::createCron());
        // The sending-only spool cron is registered idempotently in Migration20260731100000 so
        // that existing installs also pick it up on update (installed() runs on fresh install only).
        // Put widgets to the top of the dashboard
        $this->getDB()->update(
            'tadminwidgets',
            'cClass',
            'StabilaWidgetErrors',
            (object)[
                'eContainer' => 'left',
                'nPos'       => 0
            ],
        );
        $this->getDB()->update(
            'tadminwidgets',
            'cClass',
            'StabilaWidgetPerformance',
            (object)[
                'eContainer' => 'center',
                'nPos'       => 0
            ],
        );
    }

    /**
     * @inheritdoc
     */
    public function uninstalled(bool $deleteData = true): void
    {
        parent::uninstalled($deleteData);
        $this->getDB()->delete('tcron', 'jobType', CronJob::CRON_JOB_ID);
        $this->getDB()->delete('tcron', 'jobType', SentrySpoolCronJob::CRON_JOB_ID);
    }
}
