<?php

declare(strict_types=1);

namespace ReallySimplePlugins\RSS\Core\Features\Vulnerability\Controllers;

use ReallySimplePlugins\RSS\Core\Features\Vulnerability\Repositories\CoreRepository;
use ReallySimplePlugins\RSS\Core\Features\Vulnerability\Repositories\PluginRepository;
use ReallySimplePlugins\RSS\Core\Features\Vulnerability\Repositories\ThemeRepository;
use ReallySimplePlugins\RSS\Core\Features\Vulnerability\Repositories\VulnerabilityStorageRepository;
use ReallySimplePlugins\RSS\Core\Features\Vulnerability\Services\VulnerabilityEmailService;
use ReallySimplePlugins\RSS\Core\Features\Vulnerability\Support\Helpers\VulnerabilityConfig;
use ReallySimplePlugins\RSS\Core\Features\Vulnerability\Traits\HasFrontendUrl;
use ReallySimplePlugins\RSS\Core\Interfaces\ControllerInterface;
use ReallySimplePlugins\RSS\Core\Interfaces\DoActionInterface;
use ReallySimplePlugins\RSS\Core\Traits\HasViews;

/**
 * Routes vulnerability-related data actions for the admin UI.
 *
 * Notes: Read-only; delegates data access to repositories and delivery to services.
 */
final class VulnerabilityDataController implements ControllerInterface, DoActionInterface
{
    use HasViews;
    use HasFrontendUrl;

    /**
     * Handles the 'rsssl_do_action' action for vulnerability-related data requests.
     * this fetches vulnerability data for plugins and themes. within the admin interface.
     */
    private const SCAN_FILES_ACTION = 'vulnerabilities_scan_files';

    /**
     * Handles the 'rsssl_do_action' action for vulnerability dashboard data requests.
     * this fetches summary data for the vulnerability dashboard within the admin interface.
     */
    private const DASHBOARD_DATA_ACTION = 'vulnerability_dashboard_data';

    /**
     * Handles the 'rsssl_do_action' action for sending a test vulnerability notification.
     * this triggers sending a test email to verify notification settings.
     */
    private const TEST_NOTIFICATION_ACTION = 'vulnerabilities_test_notification';

    private VulnerabilityStorageRepository $vulnerabilityStorageRepository;
    private VulnerabilityConfig $vulnerabilityConfig;
    private PluginRepository $pluginRepository;
    private ThemeRepository $themeRepository;
    private CoreRepository $coreRepository;
    private VulnerabilityEmailService $vulnerabilityEmailService;

    public function __construct(
        VulnerabilityStorageRepository $vulnerabilityStorageRepository,
        VulnerabilityConfig $vulnerabilityConfig,
        PluginRepository $pluginRepository,
        ThemeRepository $themeRepository,
        CoreRepository $coreRepository,
        VulnerabilityEmailService $vulnerabilityEmailService
    )
    {
        $this->vulnerabilityStorageRepository = $vulnerabilityStorageRepository;
        $this->pluginRepository = $pluginRepository;
        $this->themeRepository = $themeRepository;
        $this->vulnerabilityConfig = $vulnerabilityConfig;
        $this->vulnerabilityEmailService = $vulnerabilityEmailService;
        $this->coreRepository = $coreRepository;
    }

    /**
     * Only register the filter for general actions. Specific actions are
     * handled in {@see rssslDoAction()} and {@see DoActionInterface}.
     *
     */
    public function register(): void
    {
        add_filter('rsssl_do_action', [$this, 'rssslDoAction'], 10, 3);
    }

    /**
     * @inheritDoc
     */
    public function rssslDoAction(array $response, string $action, $data): array
    {
        switch ($action) {
            case self::SCAN_FILES_ACTION:
                return $this->getVulnerabilityOverviewData();
            case self::DASHBOARD_DATA_ACTION:
                return $this->getVulnerabilityDashboardData();
            case self::TEST_NOTIFICATION_ACTION:
                return $this->sendTestNotification();
            default:
                return $response;
        }
    }

    /**
     * Build vulnerability overview data for the admin UI.
     *
     * For each installed plugin, theme, and WordPress core component, this collects the highest-severity
     * vulnerability (if any) from storage, enriches it with runtime update availability, and adds
     * derived fields for display (no HTML).
     *
     * @return array{
     *   request_success: bool,
     *   data: list<array<string, mixed>>,
     * }
     */
    private function getVulnerabilityOverviewData(): array
    {
        $highVulnerabilities = [];
        $allPluginsInstalled = $this->pluginRepository->getInstalledComponents();
        $allThemesInstalled = $this->themeRepository->getInstalledComponents();
        $coreInstalled = $this->coreRepository->getInstalledComponents();

        foreach ($allPluginsInstalled as $plugin) {
            $foundPlugin = $this->vulnerabilityStorageRepository
                ->getHighestSeverityForPluginSlug($plugin->getSlug());
            if ($foundPlugin !== null) {
                $foundPluginArray = $foundPlugin->toArray();
                $foundPluginArray['update_available'] = $plugin->hasUpdate();
                $highVulnerabilities[] = $foundPluginArray;
            }
        }

        foreach ($allThemesInstalled as $theme) {
            $foundTheme = $this->vulnerabilityStorageRepository
                ->getHighestSeverityForThemeSlug($theme->getSlug());
            if ($foundTheme !== null) {
                $foundThemeArray = $foundTheme->toArray();
                $foundThemeArray['update_available'] = $theme->hasUpdate();
                $highVulnerabilities[] = $foundThemeArray;
            }
        }

        foreach ($coreInstalled as $core) {
            $foundCore = $this->vulnerabilityStorageRepository
                ->getHighestSeverityForCoreSlug($core->getSlug());
            if ($foundCore !== null) {
                $foundCoreArray = $foundCore->toArray();
                $foundCoreArray['update_available'] = $core->hasUpdate();
                $highVulnerabilities[] = $foundCoreArray;
            }
        }

        $highVulnerabilities = $this->enrichVulnerabilityRows($highVulnerabilities);

        return [
            'request_success' => true,
            'data' => $highVulnerabilities,
        ];
    }

    /**
     * Build dashboard metrics for the admin UI.
     *
     * Metrics include:
     * - updatableComponents: number of installed plugins/themes with an update available.
     * - vulnerablePlugins: number of stored components that currently have vulnerabilities.
     * - highestSeverity: highest severity across all stored components (or "none").
     * - severityScore: numeric rank for the highest severity.
     *
     * @return array{
     *   request_success: bool,
     *   data: array{
     *     updatableComponents: int,
     *     vulnerablePlugins: int,
     *     highestSeverity: string,
     *     severityScore: int
     *   }
     * }
     */
    private function getVulnerabilityDashboardData(): array
    {
        $updatable = $this->getUpdatableComponentsCount();

        $highestSeverity = $this->vulnerabilityStorageRepository->getHighestSeverity();
        if (empty($highestSeverity)) {
            $highestSeverity = 'none';
        }

        $data = [
            'updatableComponents' => $updatable,
            'vulnerablePlugins' => count($this->vulnerabilityStorageRepository->getAllVulnerableComponentsRaw()),
            'highestSeverity' => $highestSeverity ?? '',
            'severityScore' => $this->vulnerabilityStorageRepository->getSeverityScore($highestSeverity),
        ];

        return [
            'request_success' => true,
            'data' => $data,
        ];
    }

    /**
     * Prepare for sending a test vulnerability notification email.
     *
     * Sets a unique option to track the test email and clears existing admin notices.
     */
    private function prepareTestNotification(): void
    {
        $randomString = md5((string)time());
        update_option('test_vulnerability_tester', $randomString, false);
        delete_option('rsssl_admin_notices');
    }

    /**
     * Send a test vulnerability notification email.
     *
     * @return array<string, mixed> Response payload for the admin UI.
     */
    private function sendTestNotification(): array
    {
        $this->prepareTestNotification();
        try {
            $emailAddress = $this->vulnerabilityEmailService->getNotificationsEmail();
            $this->vulnerabilityEmailService->setEmail($emailAddress);
            return $this->vulnerabilityEmailService->sendTestEmail();
        } catch (\Throwable $e) {
            return [
                'request_success' => false,
                'message' => __('Unable to send test notification at this time.', 'rsp'),
                'error' => $e->getMessage(),
            ];
        }
    }

    /**
     * Enrich vulnerability rows with derived fields for display (no HTML).
     *
     * Validates required keys before accessing them to avoid runtime notices
     * and to keep the data contract explicit.
     *
     * @param list<array<string, mixed>> $rows
     *
     * @return list<array<string, mixed>>
     */
    private function enrichVulnerabilityRows(array $rows): array
    {
        foreach ($rows as $index => $row) {
            if (!isset(
                $row['type'],
                $row['slug'],
                $row['vulnerability'],
                $row['vulnerability']['lookup'],
                $row['vulnerability']['published_at']
            )) {
                // Skip rows that do not match the expected structure
                continue;
            }

            $publishedAt = $row['vulnerability']['published_at'];
            $timestamp = strtotime((string)$publishedAt);
            if ($timestamp !== false) {
                $row['published_date_human'] = date('F d, Y', $timestamp);
            }

            $row['details_url'] = $this->getFrontendUrl(
                (string)$row['type'],
                (string)$row['slug'],
                (string)$row['vulnerability']['lookup']
            );

            $rows[$index] = $row;
        }

        return $rows;
    }

    /**
     * Count installed components (plugins and themes) that have an update available.
     *
     * Centralizes update availability logic to avoid duplicated loops
     * and keep dashboard calculations consistent.
     */
    private function getUpdatableComponentsCount(): int
    {
        $count = 0;

        $plugins = $this->pluginRepository->getInstalledComponents();
        foreach ($plugins as $plugin) {
            if ($plugin->hasUpdate()) {
                $count++;
            }
        }

        $themes = $this->themeRepository->getInstalledComponents();
        foreach ($themes as $theme) {
            if ($theme->hasUpdate()) {
                $count++;
            }
        }

        return $count;
    }
}
