<?php

declare(strict_types=1);

namespace ReallySimplePlugins\RSS\Core\Features\Pro\Vulnerability\Services\Policies;

use ReallySimplePlugins\RSS\Core\Features\Vulnerability\Services\Policies\ConfigurableSeverityPolicy;
use ReallySimplePlugins\RSS\Core\Services\EmailService;

/**
 * Decide which mitigation measure should be applied based on vulnerability severity.
 *
 * This policy converts a severity label (low/medium/high/critical) into a numeric
 * score and compares it with configured thresholds.
 *
 * Configuration (WordPress options):
 * - rsssl_quarantine: minimum severity required to return "quarantine"
 * - rsssl_force_update: minimum severity required to return "force_update"
 *
 * A threshold value can be one of: "low", "medium", "high", "critical", "*".
 * You can also use the shortcuts: l/m/h/c.
 */
final class VulnerabilitySeverityPolicy
{
    public const OPTION_QUARANTINE_FROM = 'rsssl_quarantine';
    public const OPTION_FORCE_UPDATE_FROM = 'rsssl_force_update';

    public const MEASURE_QUARANTINE = 'quarantine';
    public const MEASURE_FORCE_UPDATE = 'force_update';

    private EmailService $emailService;

    public function __construct(EmailService $emailService)
    {
        $this->emailService = $emailService;
    }

    /**
     * Normalize a raw severity string.
     *
     * Returns one of: low|medium|high|critical or null when unknown.
     */
    public function normalizeSeverity(string $severity): ?string
    {
        $severity = strtolower(trim($severity));
        return array_key_exists($severity, ConfigurableSeverityPolicy::SEVERITY_SCORES) ? $severity : null;
    }

    /**
     * Normalize a raw threshold value from configuration.
     *
     * Supported inputs:
     * - "*" or "" (empty) => "*" (disabled)
     * - "l"/"m"/"h"/"c" => "low"/"medium"/"high"/"critical"
     * - "low"/"medium"/"high"/"critical" => unchanged
     * - anything else => "*"
     */
    public function normalizeThreshold(string $raw): string
    {
        $raw = strtolower(trim($raw));

        switch ($raw) {
            case '*':
            case '':
                return '*';

            case 'l':
                return 'low';

            case 'm':
                return 'medium';

            case 'h':
                return 'high';

            case 'c':
                return 'critical';

            default:
                return isset(ConfigurableSeverityPolicy::SEVERITY_SCORES[$raw]) ? $raw : '*';
        }
    }

    /**
     * Determine the strongest applicable mitigation measure for a given risk.
     *
     * The quarantine threshold has priority over force_update.
     *
     * @param string $risk A normalized severity label (low/medium/high/critical).
     * @return string|null One of self::MEASURE_* or null when no measure applies.
     */
    public function getHighestMeasure(string $risk): ?string
    {
        $severityScore = ConfigurableSeverityPolicy::SEVERITY_SCORES[$risk] ?? 0;

        $quarantineFrom = $this->getNormalizedSeverityThreshold(self::OPTION_QUARANTINE_FROM);
        $forceUpdateFrom = $this->getNormalizedSeverityThreshold(self::OPTION_FORCE_UPDATE_FROM);

        if ($quarantineFrom !== '*' && $severityScore >= (ConfigurableSeverityPolicy::SEVERITY_SCORES[$quarantineFrom] ?? 0)) {
            return self::MEASURE_QUARANTINE;
        }

        if ($forceUpdateFrom !== '*' && $severityScore >= (ConfigurableSeverityPolicy::SEVERITY_SCORES[$forceUpdateFrom] ?? 0)) {
            return self::MEASURE_FORCE_UPDATE;
        }

        return null;
    }

    /**
     * Check whether any mitigation threshold is enabled.
     *
     * Returns false when email is not verified, because threshold reads are
     * normalized to disabled in that state.
     */
    public function hasEnabledMeasures(): bool
    {
        return $this->getNormalizedSeverityThreshold(self::OPTION_FORCE_UPDATE_FROM) !== '*'
            || $this->getNormalizedSeverityThreshold(self::OPTION_QUARANTINE_FROM) !== '*';
    }

    /**
     * Read and normalize a configured severity threshold.
     *
     * Measures stay inactive until email verification is complete, even if a
     * threshold option was manipulated outside the settings UI.
     *
     * @uses normalizeThreshold()
     */
    private function getNormalizedSeverityThreshold(string $optionKey): string
    {
        if (!$this->emailService->isEmailVerified()) {
            return $this->normalizeThreshold('*');
        }

        return $this->normalizeThreshold((string) get_option($optionKey, '*'));
    }
}
