<?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);

namespace Paysera\PrestashopPayments\Diagnostic\Service\Checker;

use Paysera\PrestashopPayments\Checker\ModuleConflictChecker;
use Paysera\PrestashopPayments\Checker\ModuleStatusChecker;
use Paysera\PrestashopPayments\Diagnostic\Config\DiagnosticConfig;
use Paysera\PrestashopPayments\Diagnostic\Dto\CheckResult;
use Paysera\PrestashopPayments\Diagnostic\Dto\DiagnosticSection;
use Paysera\PrestashopPayments\Diagnostic\Service\PlatformInfoService;
use Paysera\PrestashopPayments\Enum\ConfigurationKey;
use Paysera\PrestashopPayments\Installer\DatabaseInstaller;
use Paysera\PrestashopPayments\Provider\ModulePathsProvider;
use Paysera\PrestashopPayments\Provider\ModuleProvider;
use Paysera\PrestashopPayments\Provider\UrlProvider;

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

class PlatformChecker implements CheckerInterface
{
    public const SECTION_KEY = 'platform';
    public const SECTION_LABEL = 'Platform information';

    private const MODULE_TABLES = [
        DatabaseInstaller::TABLE_PAYMENT,
        DatabaseInstaller::TABLE_STATUS_TRANSITION,
    ];

    private const CUSTOM_ORDER_STATUS_KEYS = [
        ConfigurationKey::CUSTOM_OS_AWAITING_PAYMENT,
        ConfigurationKey::CUSTOM_OS_AWAITING_CONFIRMATION,
    ];

    public function __construct(
        private PlatformInfoService $platformInfo,
        private ModuleProvider $moduleProvider,
        private ModuleStatusChecker $moduleStatusChecker,
        private ModuleConflictChecker $moduleConflictChecker,
        private ModulePathsProvider $modulePaths,
        private UrlProvider $urlProvider
    ) {
    }

    public function check(): DiagnosticSection
    {
        return new DiagnosticSection(self::SECTION_KEY, self::SECTION_LABEL, [
            $this->checkPrestaShopVersion(),
            $this->checkServerSoftware(),
            $this->checkDatabaseVersion(),
            $this->checkActiveTheme(),
            $this->checkModuleStatus(),
            $this->checkModuleConflict(),
            $this->checkHooksRegistered(),
            $this->checkCallbackUrlHttps(),
            $this->checkCatalogModeDisabled(),
            $this->checkCustomOrderStatuses(),
            $this->checkDatabaseTables(),
            $this->checkTranslationsDirectoryWritable(),
            $this->checkInstalledModules(),
        ]);
    }

    private function checkPrestaShopVersion(): CheckResult
    {
        $name = 'PrestaShop Version Check';
        $current = $this->platformInfo->getPrestaShopVersion();

        if ($current === null) {
            return CheckResult::fail($name, 'PrestaShop is not detected.');
        }

        $required = DiagnosticConfig::MINIMUM_PRESTASHOP_VERSION;

        return version_compare($current, $required, '>=')
            ? CheckResult::pass($name, sprintf("Your PrestaShop version '%s' meets the minimum requirement of '%s'.", $current, $required))
            : CheckResult::fail($name, sprintf("Your PrestaShop version '%s' is below the minimum required version of '%s'.", $current, $required));
    }

    private function checkServerSoftware(): CheckResult
    {
        $software = $this->platformInfo->getWebServerSoftware();

        return $software === ''
            ? CheckResult::fail('Web Server Check', 'Unable to detect web server software.')
            : CheckResult::pass('Web Server Check', sprintf('Web server: %s', $software));
    }

    private function checkDatabaseVersion(): CheckResult
    {
        $name = 'Database Version Check';

        try {
            $version = $this->platformInfo->getDatabaseVersion();
        } catch (\Throwable $exception) {
            return CheckResult::fail($name, sprintf('Database check failed: %s', $exception->getMessage()));
        }

        return $version === ''
            ? CheckResult::fail($name, 'Unable to detect database version.')
            : CheckResult::pass($name, sprintf('Database version: %s', $version));
    }

    private function checkActiveTheme(): CheckResult
    {
        $name = 'Active Theme Check';
        $themeName = $this->platformInfo->getActiveThemeName();

        return $themeName === null
            ? CheckResult::fail($name, 'No shop context available.')
            : CheckResult::pass($name, sprintf("The active theme is '%s'.", $themeName));
    }

    private function checkModuleStatus(): CheckResult
    {
        $name = 'Module Operational Status';

        try {
            $issue = $this->moduleStatusChecker->getStatusIssue($this->moduleProvider->getModule());
        } catch (\Throwable $exception) {
            return CheckResult::fail($name, sprintf('Unable to resolve module status: %s', $exception->getMessage()));
        }

        return $issue === null
            ? CheckResult::pass($name, 'Module is operational.')
            : CheckResult::fail($name, $this->describeStatusIssue($issue));
    }

    private function describeStatusIssue(string $issue): string
    {
        return match ($issue) {
            'module_inactive' => 'Module is disabled in PrestaShop. Enable it under Modules > Module Manager.',
            'payments_disabled' => 'Payments are disabled in module settings. Enable them under Paysera Checkout > Payment Settings.',
            'not_authorized' => 'Module is not authorised. Connect your Paysera credentials under Paysera Checkout > Authorisation.',
            default => sprintf('Module is not operational: %s', $issue),
        };
    }

    private function checkModuleConflict(): CheckResult
    {
        $name = 'Conflicting Modules';
        $conflicting = $this->moduleConflictChecker->getActiveConflictingModuleName();

        return $conflicting === null
            ? CheckResult::pass($name, 'No conflicting modules detected.')
            : CheckResult::fail($name, sprintf("Conflicting module '%s' is enabled. Please disable it.", $conflicting));
    }

    private function checkHooksRegistered(): CheckResult
    {
        $name = 'Module Hooks Registered';

        try {
            $module = $this->moduleProvider->getModule();
        } catch (\Throwable $exception) {
            return CheckResult::fail($name, sprintf('Unable to resolve module instance: %s', $exception->getMessage()));
        }

        $missing = [];
        foreach (\PayseraPayments::HOOKS as $hookName) {
            if (!$module->isRegisteredInHook($hookName)) {
                $missing[] = $hookName;
            }
        }

        if ($missing === []) {
            return CheckResult::pass(
                $name,
                sprintf('All %d hooks are registered.', count(\PayseraPayments::HOOKS))
            );
        }

        return CheckResult::fail(
            $name,
            sprintf('Hooks not registered: %s. Reinstall the module to restore them.', implode(', ', $missing))
        );
    }

    private function checkCallbackUrlHttps(): CheckResult
    {
        $name = 'Callback URL Uses HTTPS';
        $url = $this->urlProvider->getCallbackUrl();

        if (strpos($url, 'https://') === 0) {
            return CheckResult::pass($name, sprintf('Callback URL is served over HTTPS: %s', $url));
        }

        return CheckResult::fail(
            $name,
            sprintf("Callback URL is not HTTPS: %s. Paysera requires HTTPS callbacks; enable SSL in PrestaShop ('Shop parameters > General > Enable SSL') or fix the shop base URL.", $url)
        );
    }

    private function checkCatalogModeDisabled(): CheckResult
    {
        $name = 'Catalog Mode';
        $catalogMode = $this->platformInfo->getConfiguration('PS_CATALOG_MODE');

        if ($catalogMode === '' || $catalogMode === '0') {
            return CheckResult::pass($name, 'Catalog mode is disabled, checkout is available.');
        }

        return CheckResult::fail(
            $name,
            "Catalog mode is enabled (PS_CATALOG_MODE). Customers cannot complete purchases — disable it under 'Shop parameters > General'."
        );
    }

    private function checkCustomOrderStatuses(): CheckResult
    {
        $name = 'Custom Order Statuses';
        $missing = [];

        foreach (self::CUSTOM_ORDER_STATUS_KEYS as $key) {
            $value = $this->platformInfo->getConfiguration($key);
            $orderStateId = (int) $value;

            if ($value === '' || $orderStateId <= 0 || !$this->platformInfo->orderStateExists($orderStateId)) {
                $missing[] = $key;
            }
        }

        if ($missing === []) {
            return CheckResult::pass($name, 'All module-installed order statuses are present.');
        }

        return CheckResult::fail(
            $name,
            sprintf('Missing order statuses configured under: %s. Reinstall the module to recreate them.', implode(', ', $missing))
        );
    }

    private function checkDatabaseTables(): CheckResult
    {
        $name = 'Module Database Tables';
        $missing = [];

        foreach (self::MODULE_TABLES as $table) {
            if (!$this->platformInfo->tableExists($table)) {
                $missing[] = $table;
            }
        }

        if ($missing === []) {
            return CheckResult::pass($name, sprintf('All %d module tables exist.', count(self::MODULE_TABLES)));
        }

        return CheckResult::fail(
            $name,
            sprintf('Missing module tables: %s. Reinstall the module to recreate them.', implode(', ', $missing))
        );
    }

    private function checkTranslationsDirectoryWritable(): CheckResult
    {
        $name = 'Translations Directory Writable';
        $path = $this->modulePaths->getTranslationsPath();

        if ($this->platformInfo->isPathWritable($path)) {
            return CheckResult::pass($name, sprintf('Translations directory is writable: %s', $path));
        }

        return CheckResult::fail(
            $name,
            sprintf('Translations directory is not writable: %s. Language files cannot be generated — adjust filesystem permissions.', $path)
        );
    }

    private function checkInstalledModules(): CheckResult
    {
        $name = 'Installed Modules Check';
        $modules = $this->platformInfo->getInstalledModules();

        if ($modules === []) {
            return CheckResult::pass($name, 'No modules are installed.');
        }

        $lines = [];
        foreach ($modules as $module) {
            $lines[] = sprintf(
                '%s (Version: %s, %s)',
                $module['name'],
                $module['version'],
                $module['active'] ? 'active' : 'inactive'
            );
        }

        return CheckResult::pass($name, sprintf("The following modules are installed:\n - %s", implode("\n - ", $lines)));
    }
}
