<?php

declare(strict_types=1);

namespace Plugin\tnt_stabila_shop\adminmenu\Controller;

use JTL\Shop;
use Plugin\tnt_stabila_shop\src\Enums\PluginSettings;
use Plugin\tnt_stabila_shop\src\Helper\IOArguments;
use Plugin\tnt_stabila_shop\src\Helper\IOHelper;
use Plugin\tnt_stabila_shop\src\Helper\SentryDsnValidator;
use Plugin\tnt_stabila_shop\src\Helper\ServerHealth;
use Plugin\tnt_stabila_shop\src\Helper\ShopRuntime;
use Plugin\tnt_stabila_shop\src\Repositories\DatabaseRepository;
use Plugin\tnt_stabila_shop\src\Repositories\SessionRepository;
use Plugin\tnt_stabila_shop\src\Services\ErrorService;
use Plugin\tnt_stabila_shop\src\Services\PerformanceService;
use Plugin\tnt_stabila_shop\src\Services\SalesService;
use Plugin\tnt_stabila_shop\src\Services\SettingsService;

class AjaxEndpoint extends BaseController
{
    /**
     * @param array<string, mixed> $args
     * @throws \SmartyException
     * @throws \Exception
     */
    public function handleRequest(array $args): void
    {
        if (!isset($args['uri']) || \is_string($args['uri']) === false) {
            $this->die('URI is not set', '', true);
        }

        $functionName = $args['uri'];
        if (\method_exists($this, $functionName) === false) {
            $this->die('Found no endpoint for this URI', '', true);
        }

        // ToDo Check for permissions. Need a mechanism for this. There is already some functionality in AdminIO
        unset($args['uri']);

        // The die() is needed to prevent further logic setting wrong headers. AdminIO works with json, but I need HTML
        die($this->$functionName(new IOArguments(\array_merge($args, $_POST))));
    }

    private function die(
        string $title = 'Something has gone wrong!',
        string $text = '',
        bool $exit = false,
    ): string {
        $response = $this->smarty
            ->assign(tpl_var: 'title', value: $title)
            ->assign(tpl_var: 'text', value: $text)
            ->fetch(self::TAIL_ADMIN_PATH . 'smarty/static/helper/error.tpl');

        if ($exit) {
            die($response);
        }

        return $response;
    }

    public function metricItem(IOArguments $arguments): string
    {
        $type = $arguments->get('type');
        if (\is_string($type) === false) {
            return '';
        }
        $metricData = (new SalesService(
            new DatabaseRepository(Shop::Container()->getDB()),
            new SessionRepository('tnt_stabila_shop'),
            Shop::Container()->getCache()
        ))->getMetricItemData($type);

        if ($metricData === null) {
            return '';
        }
        foreach ((array)$metricData as $offset => $value) {
            $this->smarty->assign(tpl_var: $offset, value: $value);
        }
        try {
            $result = $this->smarty->fetch(self::TAIL_ADMIN_PATH
                . 'smarty/reloadable/components/metric-item.tpl');
        } catch (\Exception $e) {
            return $this->die(
                title: 'Smarty Error',
                text: 'An error occurred while rendering the metric item: ' . $e->getMessage(),
                exit: true
            );
        }

        return $result;
    }

    public function simpleCard(IOArguments $arguments): string
    {
        $type = $arguments->get('type');
        if (\is_string($type) === false) {
            return '';
        }
        $this->smarty->assign(tpl_var: 'details', value: match ($type) {
            'cpu'    => IOHelper::prepareIORequest(['uri' => 'detailedCPU']),
            'memory' => IOHelper::prepareIORequest(['uri' => 'detailedRAM']),
            'disk'   => IOHelper::prepareIORequest(['uri' => 'detailedDisk']),
            default => '',
        });
        $metricData = (new PerformanceService(
            new DatabaseRepository(Shop::Container()->getDB()),
            new SessionRepository('tnt_stabila_shop'),
            Shop::Container()->getCache()
        ))->getMetricItemData($type);

        if ($metricData === null) {
            return '';
        }
        foreach ((array)$metricData as $offset => $value) {
            $this->smarty->assign(tpl_var: $offset, value: $value);
        }
        try {
            $result = $this->smarty->fetch(self::TAIL_ADMIN_PATH
                . 'smarty/reloadable/components/card-simple.tpl');
        } catch (\Exception $e) {
            return $this->die(
                title: 'Smarty Error',
                text: 'An error occurred while rendering the simple card: ' . $e->getMessage(),
                exit: true
            );
        }

        return $result;
    }

    public function detailedCPU(IOArguments $arguments): string
    {
        $data = ServerHealth::getDataCPU();
        foreach ((array)$data as $offset => $value) {
            $this->smarty->assign(tpl_var: $offset, value: $value);
        }
        try {
            $chart = $this->smarty->fetch(self::TAIL_ADMIN_PATH
                . 'smarty/reloadable/components/cpuDetailsLineChart.tpl');
            $this->smarty->assign(tpl_var: 'modalTitle', value: __('Processor'));
            $this->smarty->assign(tpl_var: 'modalContent', value: $chart);
            $this->smarty->assign(tpl_var: 'chartID', value: 'cpuDetailsLineChart');
            $result = $this->smarty->fetch(self::TAIL_ADMIN_PATH
                . 'smarty/reloadable/components/modals/modal-simple.tpl');
        } catch (\Exception $e) {
            return $this->die(
                title: 'Smarty Error',
                text: 'An error occurred while rendering the detailed CPU template: ' . $e->getMessage(),
                exit: true
            );
        }

        return $result;
    }

    public function detailedRAM(IOArguments $arguments): string
    {
        $data = ServerHealth::getDataMemory();
        foreach ((array)$data as $offset => $value) {
            $this->smarty->assign(tpl_var: $offset, value: $value);
        }
        try {
            $chart = $this->smarty->fetch(self::TAIL_ADMIN_PATH
                . 'smarty/reloadable/components/ramDetailsDonutChart.tpl');
            $this->smarty->assign(tpl_var: 'modalTitle', value: __('Memory'));
            $this->smarty->assign(tpl_var: 'modalContent', value: $chart);
            $this->smarty->assign(tpl_var: 'chartID', value: 'ramDetailsDonutChart');
            $result = $this->smarty->fetch(self::TAIL_ADMIN_PATH
                . 'smarty/reloadable/components/modals/modal-simple.tpl');
        } catch (\Exception $e) {
            return $this->die(
                title: 'Smarty Error',
                text: 'An error occurred while rendering the detailed RAM template: ' . $e->getMessage(),
                exit: true
            );
        }

        return $result;
    }

    public function detailedDisk(IOArguments $arguments): string
    {
        $data = ServerHealth::getDataDisk();
        foreach ((array)$data as $offset => $value) {
            $this->smarty->assign(tpl_var: $offset, value: $value);
        }
        try {
            $chart = $this->smarty->fetch(self::TAIL_ADMIN_PATH
                . 'smarty/reloadable/components/diskDetailsDonutChart.tpl');
            $this->smarty->assign(tpl_var: 'modalTitle', value: __('Storage'));
            $this->smarty->assign(tpl_var: 'modalContent', value: $chart);
            $this->smarty->assign(tpl_var: 'chartID', value: 'diskDetailsDonutChart');
            $result = $this->smarty->fetch(self::TAIL_ADMIN_PATH
                . 'smarty/reloadable/components/modals/modal-simple.tpl');
        } catch (\Exception $e) {
            return $this->die(
                title: 'Smarty Error',
                text: 'An error occurred while rendering the detailed disk template: ' . $e->getMessage(),
                exit: true
            );
        }

        return $result;
    }

    public function pageSpeedBarChart(IOArguments $arguments): string
    {
        $days       = (int)($arguments->get('days') ?? 7);
        $pageSpeeds = (new PerformanceService(
            new DatabaseRepository(Shop::Container()->getDB()),
            new SessionRepository('tnt_stabila_shop'),
            Shop::Container()->getCache()
        ))->getPageSpeedLastXDays($days);

        $pageSpeedChart = (object)[
            'data'  => '',
            'label' => '',
            'days'  => $days,
        ];
        if ($pageSpeeds !== []) {
            $pageTypes = [];
            $loadTimes = [];
            foreach ($pageSpeeds as $pageSpeed) {
                $pageTypes[] = '"' . $pageSpeed->pageType . '"';
                $loadTimes[] = $pageSpeed->performance;
            }
            $pageSpeedChart = (object)[
                'data'  => \implode(',', $loadTimes),
                'label' => \implode(',', $pageTypes),
                'days'  => $days,
            ];
        }
        try {
            $this->smarty->assign(tpl_var: 'pageSpeedChart', value: $pageSpeedChart);
            $result = $this->smarty->fetch(self::TAIL_ADMIN_PATH
                . 'smarty/reloadable/components/pageSpeedBarChart.tpl');
        } catch (\Exception $e) {
            return $this->die(
                title: 'Smarty Error',
                text: 'An error occurred while rendering the detailed disk template: ' . $e->getMessage(),
                exit: true
            );
        }

        return $result;
    }

    public function cacheChart(IOArguments $arguments): string
    {
        $data = ServerHealth::getDataCache();
        if ($data !== null) {
            foreach ((array)$data as $offset => $value) {
                $this->smarty->assign(tpl_var: $offset, value: $value);
            }
        }
        $this->smarty
            ->assign(tpl_var: 'cacheDeactivated', value: $data === null)
            ->assign(tpl_var: 'minGoodCacheSpeed', value: ServerHealth::MIN_GOOD_CACHE_SPEED);
        try {
            $result = $this->smarty->fetch(self::TAIL_ADMIN_PATH
                . 'smarty/reloadable/components/cacheHealthChart.tpl');
        } catch (\Exception $e) {
            return $this->die(
                title: 'Smarty Error',
                text: 'An error occurred while rendering the cache health template: ' . $e->getMessage(),
                exit: true
            );
        }

        return $result;
    }

    public function toggleSwitchSetting(IOArguments $arguments): void
    {
        $activate    = (int)\is_string($arguments->get('settingValue'));
        $settingName = $arguments->get('settingName');
        if (\is_string($settingName) === false) {
            return;
        }
        $pluginSettingValues = PluginSettings::fromJsonValue($settingName, 'name');
        if (
            \is_object($pluginSettingValues) === false
            || isset($pluginSettingValues->name, $pluginSettingValues->type, $pluginSettingValues->defaultValue) === false
        ) {
            \header('HX-Trigger: {"hxError":"'
                . \__('A setting with the name %s does not exist.', $settingName) . '"}');
            return;
        }
        /** @var object{name: string, type: string, defaultValue: string} $pluginSetting */
        $pluginSetting   = $pluginSettingValues;
        $settingsService = new SettingsService(
            new DatabaseRepository(Shop::Container()->getDB()),
            new SessionRepository('tnt_stabila_shop'),
            Shop::Container()->getCache()
        );
        // A Sentry DSN is mandatory for error reporting: refuse to enable autoReport without one.
        if ($settingName === 'autoReport' && $activate === 1 && $this->sentryDsnConfigured($settingsService) === false) {
            \header('HX-Trigger: {"hxError":"'
                . \__('Enter your Sentry DSN first — automatic error reports need it.') . '"}');
            return;
        }
        if ($settingName === 'performanceBooster') {
            $toggleResult = $settingsService->toggleSqLiteCache($activate);
            if ($toggleResult === false) {
                \header('HX-Trigger: {"hxError":"'
                    . \__('The Performance Booster can not be activated.'
                        . ' There must be something wrong with your SQLite configuration.') . '"}');
                return;
            }
        }
        $settingsService->setSetting($pluginSetting, $activate);

        \header('HX-Trigger: {"hxSwitchToggled":{"target": "#'
            . $settingName . '","value": "' . $activate . '"}}');
    }

    /**
     * Persist the merchant's Sentry DSN (managed in the Dashboard's Quality Booster block).
     * The DSN is mandatory for error reporting; clearing it also disables automatic error reports.
     */
    public function saveSentryDsn(IOArguments $arguments): void
    {
        $raw = $arguments->get('settingValue');
        $dsn = \is_string($raw) ? \trim($raw) : '';
        if ($dsn !== '' && SentryDsnValidator::isValid($dsn) === false) {
            \header('HX-Trigger: {"hxError":"'
                . \__('That does not look like a valid Sentry DSN.') . '"}');
            return;
        }
        $settingsService = new SettingsService(
            new DatabaseRepository(Shop::Container()->getDB()),
            new SessionRepository('tnt_stabila_shop'),
            Shop::Container()->getCache()
        );
        $dsnSetting      = PluginSettings::fromJsonValue('sentryDsn', 'name');
        if (\is_object($dsnSetting) === false) {
            return;
        }
        /** @var object{name: string, type: string, defaultValue: string} $dsnSetting */
        $settingsService->setSetting($dsnSetting, $dsn);
        // Without a DSN, error reporting cannot run — force autoReport off so state stays consistent.
        if ($dsn === '') {
            $autoReportSetting = PluginSettings::fromJsonValue('autoReport', 'name');
            if (\is_object($autoReportSetting)) {
                /** @var object{name: string, type: string, defaultValue: string} $autoReportSetting */
                $settingsService->setSetting($autoReportSetting, 0);
            }
        }
        \header('HX-Trigger: "hxDsnSaved"');
    }

    private function sentryDsnConfigured(SettingsService $settingsService): bool
    {
        $stored = $settingsService->getSetting('sentryDsn');
        return \is_string($stored) && $stored !== '';
    }

    public function getQualityBoosterContent(IOArguments $arguments): string
    {
        if (\is_string($arguments->get('settingValue')) === false) {
            return '';
        }
        $settingService = new SettingsService(
            new DatabaseRepository(Shop::Container()->getDB()),
            new SessionRepository('tnt_stabila_shop'),
            Shop::Container()->getCache()
        );
        $storedDsn      = $settingService->getSetting('sentryDsn');
        $sentryDsn      = \is_string($storedDsn) ? $storedDsn : '';
        $this->smarty
            ->assign(
                tpl_var: 'sentryDsn',
                value: $sentryDsn
            )->assign(
                tpl_var: 'autoReportDisabled',
                value: $sentryDsn === ''
            )->assign(
                tpl_var: 'autoReport',
                value: $settingService->getSetting('autoReport') ?? 0
            )->assign(
                tpl_var: 'mailNotifications',
                value: $settingService->getSetting('mailNotifications') ?? 0
            );
        try {
            $result = $this->smarty->fetch(self::TAIL_ADMIN_PATH
                . 'smarty/reloadable/components/qualityBoosterForm.tpl');
        } catch (\Exception $e) {
            return $this->die(
                title: 'Smarty Error',
                text: 'An error occurred while rendering the quality booster form template: ' . $e->getMessage(),
                exit: true
            );
        }

        return $result;
    }

    public function getErrorTable(IOArguments $arguments): string
    {
        $page         = (int)($arguments->get('page') ?? 1);
        $search       = (string)($arguments->get('search') ?? '');
        $errorService = new ErrorService(
            new DatabaseRepository(Shop::Container()->getDB()),
            new SessionRepository('tnt_stabila_shop'),
            Shop::Container()->getCache()
        );
        $errorsTable  = $errorService->getErrorTable($page, $search);
        if ($errorsTable === null) {
            return $this->smarty->fetch(
                self::TAIL_ADMIN_PATH . 'smarty/reloadable/components/tables/noData.tpl'
            );
        }
        $this->smarty
            ->assign(
                tpl_var: 'table',
                value: $errorsTable
            )->assign(
                tpl_var: 'showPagination',
                value: $search === ''
            );

        return $this->smarty->fetch(self::TAIL_ADMIN_PATH . 'smarty/reloadable/components/tables/table.tpl');
    }

    public function errorDetail(IOArguments $arguments): string
    {
        $errorID      = (int)($arguments->get('id') ?? 0);
        $errorService = new ErrorService(
            new DatabaseRepository(Shop::Container()->getDB()),
            new SessionRepository('tnt_stabila_shop'),
            Shop::Container()->getCache()
        );
        try {
            $errorDetails = $errorService->getErrorDetails($errorID);
            if ($errorDetails === null || ($errorDetails->varDump ?? '') === '') {
                $content = $this->smarty->fetch(self::TAIL_ADMIN_PATH
                        . 'smarty/static/content/noContent.tpl');
            } else {
                $mailTo       = 'info@timniko.com';
                $mailSubject  = 'Stabila Shop - Solve Issue in ' . ShopRuntime::shopUrl();
                $mailBody     = 'Error ID: ' . $errorDetails->id . ' | Error msg: ' . $errorDetails->message;
                $mailTemplate = sprintf(
                    'mailto:%s?subject=%s&body=%s',
                    $mailTo,
                    rawurlencode($mailSubject),
                    rawurlencode($mailBody)
                );
                $content      = $this->smarty
                    ->assign(tpl_var: 'filePath', value: $errorDetails->filePath ?? '')
                    ->assign(tpl_var: 'functionName', value: $errorDetails->functionName ?? '')
                    ->assign(tpl_var: 'template', value: $errorDetails->template ?? '')
                    ->assign(tpl_var: 'cartCount', value: $errorDetails->cartCount ?? '')
                    ->assign(tpl_var: 'severity', value: $errorDetails->severity ?? '')
                    ->assign(tpl_var: 'cartItems', value: $errorDetails->cartItems ?? '')
                    ->assign(tpl_var: 'deliveryCountry', value: $errorDetails->deliveryCountry ?? '')
                    ->assign(tpl_var: 'invoiceCountry', value: $errorDetails->invoiceCountry ?? '')
                    ->assign(tpl_var: 'message', value: $errorDetails->message ?? '')
                    ->assign(tpl_var: 'taxRates', value: implode(', ', $errorDetails->taxRates ?? []))
                    ->assign(tpl_var: 'shopCurrency', value: $errorDetails->shopCurrency ?? '')
                    ->assign(tpl_var: 'shopLanguage', value: $errorDetails->shopLanguage ?? '')
                    ->assign(tpl_var: 'totalExecTime', value: $errorDetails->totalExecTime ?? '')
                    ->assign(tpl_var: 'varDump', value: $errorDetails->varDump ?? '')
                    ->assign(tpl_var: 'userBrowser', value: $errorDetails->userBrowser ?? '')
                    ->assign(
                        tpl_var: 'createDate',
                        value: $errorDetails->createDate->format('d-m-Y H:i:s')
                    )
                    ->assign(tpl_var: 'shopVersion', value: $errorDetails->shopVersion ?? '')
                    ->assign(
                        tpl_var: 'mailTemplate',
                        value: $mailTemplate
                    )
                    ->fetch(self::TAIL_ADMIN_PATH
                        . 'smarty/static/content/errorDetails.tpl');
            }
            $this->smarty
                ->assign(tpl_var: 'modalTitle', value: \__('Error details'))
                ->assign(tpl_var: 'modalContent', value: $content)
                ->assign(tpl_var: 'modalWidth', value: '1200px');

            $result = $this->smarty->fetch(self::TAIL_ADMIN_PATH
                . 'smarty/reloadable/components/modals/modal-simple.tpl');
        } catch (\Exception $e) {
            return $this->die(
                title: 'Smarty Error',
                text: 'An error occurred while rendering the error detail template: ' . $e->getMessage(),
                exit: true
            );
        }

        return $result;
    }

    public function monthlyTargetChart(IOArguments $arguments): string
    {
        // Resets each 30 days and sets the target to the average of past monthly sales.
        $salesService = new SalesService(
            new DatabaseRepository(Shop::Container()->getDB()),
            new SessionRepository('tnt_stabila_shop'),
            Shop::Container()->getCache()
        );

        $monthlyAverageSales = $salesService->getMonthlyAverageSales();
        $this->smarty
            ->assign(tpl_var: 'monthlySales', value: $monthlyAverageSales);
        try {
            $result = $this->smarty->fetch(self::TAIL_ADMIN_PATH
                . 'smarty/reloadable/components/monthlySalesChart.tpl');
        } catch (\Exception $e) {
            return $this->die(
                title: 'Smarty Error',
                text: 'An error occurred while rendering the monthly sales template: ' . $e->getMessage(),
                exit: true
            );
        }

        return $result;
    }
}
