<?php

declare(strict_types=1);

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

use ReallySimplePlugins\RSS\Core\Features\Vulnerability\Dtos\InstalledComponentDto;
use ReallySimplePlugins\RSS\Core\Features\Vulnerability\Interfaces\InstalledComponentRepositoryInterface;

/**
 * Read-only repository for WordPress core.
 *
 * Purpose:
 * - Exposes the installed WordPress core as an InstalledComponentDto.
 * - Provides the installed version and (when available) the latest version from
 *   WordPress core update information.
 *
 * This repository does not perform remote calls and does not persist anything.
 * It only translates WordPress runtime state into a predictable DTO format.
 */
final class CoreRepository implements InstalledComponentRepositoryInterface
{
    /**
     * Return WordPress core as a list of InstalledComponentDto objects.
     *
     * The returned list contains a single DTO representing the installed WordPress core and includes:
     * - type: "core"
     * - slug: "wordpress"
     * - name: "WordPress"
     * - installed version
     * - active flag: always true (core is always active)
     * - latest available version (when WordPress update information is available)
     *
     * @return list<InstalledComponentDto>
     */
    public function getInstalledComponents(): array
    {
        $core = $this->getNormalizedCore();

        return [
            new InstalledComponentDto(
                'core',
                $core['slug'],
                $core['name'],
                $core['version'],
                '', // No "plugin file" equivalent for core.
                true,
                $core['latestVersion']
            ),
        ];
    }

    /**
     * Build a normalized representation of the installed WordPress core.
     *
     * WordPress core update information is stored in a site transient. When it
     * is present, we extract the latest available version from it.
     *
     * @return array{
     *   slug: string,
     *   name: string,
     *   version: string,
     *   latestVersion: ?string
     * }
     */
    private function getNormalizedCore(): array
    {
        $installedVersion = $this->getInstalledCoreVersion();
        $latestVersion = $this->getLatestCoreVersionFromUpdates();

        return [
            'slug' => 'wordpress',
            'name' => 'WordPress',
            'version' => $installedVersion,
            'latestVersion' => $latestVersion,
        ];
    }

    /**
     * Get the installed WordPress version.
     *
     * @return string Non-empty version string when available, otherwise empty string.
     */
    private function getInstalledCoreVersion(): string
    {
        if (function_exists('get_bloginfo')) {
            $version = get_bloginfo('version');
            return is_string($version) ? $version : '';
        }

        // Fallback for unusual execution contexts.
        /** @var string|null $wp_version */
        global $wp_version;

        return is_string($wp_version) ? $wp_version : '';
    }

    /**
     * Determine the latest available WordPress core version from update information.
     *
     * WordPress stores available core updates in a site transient. The transient
     * contains an object with an `updates` list of update items.
     *
     * @return string|null Latest available version, or null when not available.
     */
    private function getLatestCoreVersionFromUpdates(): ?string
    {
        if (!function_exists('get_core_updates')) {
            require_once ABSPATH . 'wp-admin/includes/update.php';
        }

        $updates = get_core_updates();

        if (!is_array($updates)) {
            return null;
        }

        $latest = null;

        foreach ($updates as $update) {
            if (!is_object($update) || empty($update->current) || !is_string($update->current)) {
                continue;
            }

            $candidate = $update->current;

            if ($latest === null || version_compare($candidate, $latest, '>')) {
                $latest = $candidate;
            }
        }

        return $latest;
    }
}
