<?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\Diagnostic\Config\DiagnosticConfig;
use Paysera\PrestashopPayments\Diagnostic\Dto\CheckResult;
use Paysera\PrestashopPayments\Diagnostic\Dto\DiagnosticSection;
use Paysera\PrestashopPayments\Diagnostic\Service\OpenSslService;
use Paysera\PrestashopPayments\Diagnostic\Service\PhpEnvironment;
use Paysera\PrestashopPayments\Diagnostic\Service\TlsVersionProbeService;
use Psr\Http\Client\ClientExceptionInterface;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\RequestFactoryInterface;

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

class SecurityChecker implements CheckerInterface
{
    public const SECTION_KEY = 'security';
    public const SECTION_LABEL = 'Security information';

    private const MODERN_TLS_VERSIONS = ['TLS 1.2', 'TLS 1.3'];

    public function __construct(
        private OpenSslService $openSslService,
        private PhpEnvironment $phpEnvironment,
        private TlsVersionProbeService $tlsVersionProbeService,
        private ClientInterface $httpClient,
        private RequestFactoryInterface $requestFactory
    ) {
    }

    public function check(): DiagnosticSection
    {
        return new DiagnosticSection(self::SECTION_KEY, self::SECTION_LABEL, [
            $this->checkPublicKeySignature(),
            $this->checkSslVersionCompatibility(),
            $this->checkTlsVersionsSupport(),
        ]);
    }

    private function checkPublicKeySignature(): CheckResult
    {
        $name = 'SS2 Signature Verification Check';

        if (!$this->phpEnvironment->isExtensionLoaded('openssl')) {
            return CheckResult::fail($name, 'The OpenSSL extension is not loaded. Ensure the extension is enabled.');
        }

        try {
            $request = $this->requestFactory->createRequest('GET', DiagnosticConfig::PUBLIC_KEY_URL);
            $response = $this->httpClient->sendRequest($request);
        } catch (ClientExceptionInterface $exception) {
            return CheckResult::fail(
                $name,
                sprintf('Failed to fetch the public key from the specified URL: %s', $exception->getMessage())
            );
        }

        if ($response->getStatusCode() !== 200) {
            return CheckResult::fail($name, 'Failed to fetch the public key from the specified URL.');
        }

        $content = (string) $response->getBody();

        if ($this->isParseableCertificate($content) || $this->openSslService->isValidPublicKey($content)) {
            return CheckResult::pass($name, 'Public key verification check passed!');
        }

        return CheckResult::fail($name, 'The fetched content is not a parseable X.509 certificate or public key.');
    }

    private function isParseableCertificate(string $content): bool
    {
        return $this->openSslService->parseX509($content) !== false;
    }

    private function checkSslVersionCompatibility(): CheckResult
    {
        $name = 'SSL Version Compatibility Check';
        $versionText = $this->openSslService->getVersionText();

        if ($versionText === null) {
            return CheckResult::fail($name, 'OpenSSL extension is not installed or enabled.');
        }

        if (preg_match('/\b(\d+\.\d+\.\d+\w*)\b/', $versionText, $matches) !== 1) {
            return CheckResult::fail($name, sprintf('Unable to determine OpenSSL version from: %s', $versionText));
        }

        $opensslVersion = $matches[1];
        $opensslCompareVersion = preg_replace('/[A-Za-z]+$/', '', $opensslVersion) ?? $opensslVersion;
        $phpMajorMinor = $this->phpEnvironment->getMajorMinor();

        $rule = DiagnosticConfig::PHP_OPENSSL_COMPATIBILITY[$phpMajorMinor] ?? null;

        if ($rule === null) {
            return CheckResult::fail($name, sprintf('PHP %s is not supported', $phpMajorMinor));
        }

        $isCompatible = version_compare($opensslCompareVersion, $rule['gte'], '>=')
            && version_compare($opensslCompareVersion, $rule['lt'], '<');

        if (!$isCompatible) {
            return CheckResult::fail(
                $name,
                sprintf(
                    'PHP %s and OpenSSL %s are NOT compatible. Expected OpenSSL >= %s and < %s for PHP %s.',
                    $phpMajorMinor,
                    $opensslVersion,
                    $rule['gte'],
                    $rule['lt'],
                    $phpMajorMinor
                )
            );
        }

        return CheckResult::pass(
            $name,
            sprintf('PHP %s and OpenSSL %s are compatible.', $this->phpEnvironment->getVersion(), $opensslVersion)
        );
    }

    private function checkTlsVersionsSupport(): CheckResult
    {
        $name = 'TLS Versions Support Check';
        $probeResults = $this->tlsVersionProbeService->probe(
            DiagnosticConfig::PUBLIC_KEY_URL,
            DiagnosticConfig::REQUEST_TIMEOUT_SECONDS
        );

        $lines = [];
        $hasModernTls = false;
        foreach ($probeResults as $versionName => $supported) {
            if ($supported === null) {
                $lines[] = sprintf('%s: Not Supported (cURL is not compiled with the required TLS version)', $versionName);
                continue;
            }

            $lines[] = sprintf('%s: %s', $versionName, $supported ? 'Supported' : 'Not Supported');

            if ($supported && in_array($versionName, self::MODERN_TLS_VERSIONS, true)) {
                $hasModernTls = true;
            }
        }

        if (!$hasModernTls) {
            $lines[] = 'At least TLS 1.2 or TLS 1.3 must be supported to communicate with Paysera.';
        }

        return $hasModernTls
            ? CheckResult::pass($name, implode("\n", $lines))
            : CheckResult::fail($name, implode("\n", $lines));
    }
}
