<?php

declare(strict_types=1);

namespace Plugin\tnt_stabila_shop\src\Services;

use JTL\Cache\JTLCacheInterface;
use JTL\Mapper\PageTypeToPageNiceName;
use JTL\Shop;
use Plugin\tnt_stabila_shop\src\Helper\Formatter;
use Plugin\tnt_stabila_shop\src\Helper\MathHelper;
use Plugin\tnt_stabila_shop\src\Helper\PerformanceBooster;
use Plugin\tnt_stabila_shop\src\Helper\ServerHealth;
use Plugin\tnt_stabila_shop\src\Repositories\DatabaseRepository;
use Plugin\tnt_stabila_shop\src\Repositories\SessionRepository;

class PerformanceService
{
    use Formatter;

    protected const ALLOWED_PAGE_TYPES = [
        \PAGE_STARTSEITE,
        \PAGE_VERSAND,
        \PAGE_EIGENE,
        \PAGE_MEINKONTO,
        \PAGE_WARENKORB,
        \PAGE_PLUGIN,
        \PAGE_BESTELLVORGANG,
        \PAGE_BESTELLABSCHLUSS,
        \PAGE_ARTIKEL,
        \PAGE_ARTIKELLISTE,
        \PAGE_BESTELLSTATUS,
        \PAGE_WUNSCHLISTE,
        \PAGE_VERGLEICHSLISTE,
        \PAGE_GRATISGESCHENK,
        \PAGE_AUSWAHLASSISTENT,
        \PAGE_HERSTELLER,
        \PAGE_LIVESUCHE,
        \PAGE_UNBEKANNT,
    ];

    public function __construct(
        protected DatabaseRepository $database,
        protected SessionRepository $session,
        protected JTLCacheInterface|null $cache = null
    ) {
        $this->cache = $cache ?? Shop::Container()->getCache();
    }

    /**
     * Returns the data for a metric item based on the type.
     *
     * @param string $type The type of metric item ('orders', 'customers').
     * @return object{icon: object{name: string, viewBox: string}, title: string, total: string,
     *     hint: object{isCritical: bool, text: string}}|null
     */
    public function getMetricItemData(string $type): object|null
    {
        return match ($type) {
            'cpu'    => $this->getBasicCPUMetrics(),
            'memory' => $this->getBasicMemoryMetrics(),
            'disk'   => $this->getBasicDiskMetrics(),
            default => null,
        };
    }

    /**
     * @return object{icon: object{name: string, viewBox: string}, title: string, total: string,
     *     hint: object{isCritical: bool, text: string}}
     */
    public function getBasicCPUMetrics(): object
    {
        $data       = ServerHealth::getDataCPU();
        $isCritical = $data->actualLoad > 80;

        return (object)[
            'icon'  => (object)['name' => 'cpu', 'viewBox' => '0 0 24 24'],
            'title' => \__('CPU load') . ' (' .  \__('%s threads', $data->threads) . ')',
            'total' => self::numberFormat($data->actualLoad, 2, '%'),
            'hint'  => (object)[
                'isCritical' => $isCritical,
                'text' => $isCritical ? \__('Critical load') : \__('Good'),
            ],
        ];
    }

    /**
     * @return object{icon: object{name: string, viewBox: string}, title: string, total: string,
     *     hint: object{isCritical: bool, text: string}}
     */
    public function getBasicMemoryMetrics(): object
    {
        $data       = ServerHealth::getDataMemory();
        $isCritical = $data->usage > 80;

        return (object)[
            'icon'         => (object)['name' => 'ram', 'viewBox' => '0 0 512 512'],
            'title'        => \__('RAM usage'),
            'total'        => self::numberFormat($data->usage, 2, '%'),
            'hint'  => (object)[
                'isCritical' => $isCritical,
                'text' => $isCritical ? \__('Low on memory') : \__('Good'),
            ],
        ];
    }

    /**
     * @return object{icon: object{name: string, viewBox: string}, title: string, total: string,
     *     hint: object{isCritical: bool, text: string}}
     */
    public function getBasicDiskMetrics(): object
    {
        $data       = ServerHealth::getDataDisk();
        $isCritical = $data->usage > 80;

        return (object)[
            'icon'         => (object)['name' => 'disk', 'viewBox' => '0 0 512 512'],
            'title'        => \__('Disk usage'),
            'total'        => self::numberFormat($data->usage, 2, '%'),
            'hint'  => (object)[
                'isCritical' => $isCritical,
                'text' => $isCritical ? \__('Nearly full') : \__('Good'),
            ],
        ];
    }

    /**
     * @return object{lastPerformance: float, weeklyPerformances: list<object{returnValue: int|float}>,
     *     changeType: string, changeAmount: string}
     */
    public function getPerformanceLastWeek(): object
    {
        $performanceLastXDays = $this->database->getPerformanceLastXDays(7);
        // Limited by the last 2 years
        $weeklyPerformance = $this->database->getWeeklyPerformanceTotals();
        $performance       = MathHelper::calcPerformance($performanceLastXDays, $weeklyPerformance);
        $changeType        = '+';
        if ($performanceLastXDays > 0 && $performanceLastXDays < $performance->average) {
            $changeType = '-';
        }

        return (object)[
            'lastPerformance'    => $performanceLastXDays,
            'weeklyPerformances' => $weeklyPerformance,
            'changeType'         => $changeType,
            'changeAmount'       => self::numberFormat($performance->performance, 2, '%'),
        ];
    }

    /**
     * Get average performance grouped by page type for the last X days.
     *
     * @param int $days The number of days to look back.
     * @return object{performance: float, pageType: string}[]
     */
    public function getPageSpeedLastXDays(int $days): array
    {
        $pageSpeeds = $this->database->getPerformanceWithPageTypeLastXDays($days);
        $pageNames  = new PageTypeToPageNiceName();
        $pageSpeeds = \array_filter(
            $pageSpeeds,
            static fn (object $pageSpeed) => \in_array(
                (int)$pageSpeed->pageType,
                self::ALLOWED_PAGE_TYPES,
                true
            )
        );

        return \array_map(
            static fn (object $pageSpeed): object => (object)[
                'performance' => (float)$pageSpeed->performance,
                'pageType'    => $pageNames->mapPageTypeToPageNiceName((int)$pageSpeed->pageType),
            ],
            $pageSpeeds
        );
    }

    public function boostPerformance(): void
    {
        $settingsService = new SettingsService(
            $this->database,
            $this->session,
            $this->cache,
        );
        if ($settingsService->getPerformanceBoosterEnabled() === false) {
            return;
        }
        $performanceBooster = PerformanceBooster::getSQLiteCache();
        if (!$performanceBooster instanceof \SQLite3) {
            return;
        }

        $performanceBooster->exec(
            'PRAGMA cache_size = -20000;
            PRAGMA mmap_size = ' . PerformanceBooster::MMAP_SIZE . ';'
        );
    }
}
