<?php

/**
 * LolaCore — Hub Site Health
 *
 * @package LolaCore
 * @license GPL-2.0-or-later
 */

declare(strict_types=1);

namespace LolaCore\Hub;

use LolaCore\Actions\ActionLogger;
use LolaCore\Scanner\ScanDataCollector;
use LolaCore\Scanner\SiteScanner;

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Aggregated site health payload for LolaHub fleet queries (sprint C3).
 *
 * Uses cached scan data — never triggers a full scan on each poll.
 */
final class HubSiteHealth
{
    public const CACHE_KEY = 'lolacore_hub_site_health';

    public const CACHE_TTL = 300;

    /**
     * @param array<string, mixed> $input
     * @return array<string, mixed>
     */
    public function get(array $input = []): array
    {
        $forceRefresh = ! empty($input['force_refresh']);
        $recentLimit  = max(1, min(50, (int) ($input['recent_actions_limit'] ?? 10)));

        if (! $forceRefresh) {
            $cached = get_transient(self::CACHE_KEY);
            if (is_array($cached)) {
                return $this->withRecentActionsLimit($cached, $recentLimit);
            }
        }

        $payload = $this->build($recentLimit);
        set_transient(self::CACHE_KEY, $payload, self::CACHE_TTL);

        return $payload;
    }

    /**
     * @return array<string, mixed>
     */
    private function build(int $recentLimit): array
    {
        global $wp_version;

        $collector = new ScanDataCollector();
        $scanner   = new SiteScanner($collector, new \LolaCore\Memory\MemoryManager());
        $scan      = $scanner->getLastScan();

        $scannedAt = null;
        $ageHours  = null;
        if (is_array($scan)) {
            $scannedAt = (string) ($scan['scan_timestamp'] ?? $scan['timestamp'] ?? '');
            if ($scannedAt !== '') {
                $ageHours = round((time() - strtotime($scannedAt)) / 3600, 1);
            }
        }

        $pending = $this->buildPendingUpdates($scan);

        return [
            'wp_version'       => (string) ($scan['wordpress']['wp_version'] ?? $wp_version ?? ''),
            'lolacore_version' => defined('LOLACORE_VERSION') ? (string) LOLACORE_VERSION : '',
            'php_version'      => (string) ($scan['php']['php_version'] ?? phpversion()),
            'addons'           => $this->collectAddons(),
            'pending_updates'  => $pending,
            'maintenance'      => $this->buildMaintenance($scan, $pending),
            'recent_actions'   => $this->fetchRecentActions($recentLimit),
            'scan_summary'     => [
                'text'         => $scanner->getScanSummary(),
                'scanned_at'   => $scannedAt,
                'age_hours'    => $ageHours,
                'site_health'  => $this->deriveSiteHealth($scan),
                'has_scan'     => is_array($scan),
            ],
            'hub_connection'   => (new PairingRegistrar())->getConnectionState(),
            'generated_at'     => gmdate('c'),
        ];
    }

    /**
     * Maintenance and security posture for the client report's "Status &
     * maintenance" section (DESIGN-H4 §3.2 #4 — ManageWP parity).
     *
     * Honesty (R14): every field is derived from the local scan — only what the
     * site can actually read about itself. No uptime, no external monitoring;
     * backups are reported as "detected / not detected" and never assumed.
     *
     * @param array<string, mixed>|null $scan
     * @param array<string, mixed>      $pending
     * @return array<string, mixed>
     */
    private function buildMaintenance(?array $scan, array $pending): array
    {
        $security = is_array($scan['security'] ?? null) ? $scan['security'] : [];
        $backup   = is_array($scan['backup'] ?? null) ? $scan['backup'] : [];
        $php      = (string) ($scan['php']['php_version'] ?? phpversion());

        $pendingCount = (int) (! empty($pending['core']))
            + count(is_array($pending['plugins'] ?? null) ? $pending['plugins'] : [])
            + count(is_array($pending['themes'] ?? null) ? $pending['themes'] : []);

        $solutions = array_values(array_filter(array_map(
            'strval',
            is_array($backup['detected_plugins'] ?? null) ? $backup['detected_plugins'] : []
        )));

        return [
            'health'                => $this->deriveSiteHealth($scan),
            'ssl_active'            => (bool) ($security['ssl_active'] ?? false),
            'debug_mode_off'        => ! ((bool) ($security['debug_mode'] ?? false)),
            'file_editing_disabled' => (bool) ($security['file_editing_disabled'] ?? false),
            'core_up_to_date'       => (bool) ($scan['wordpress']['is_updated'] ?? true),
            'php_version'           => $php,
            'php_supported'         => $this->isPhpSupported($php),
            'pending_updates_count' => $pendingCount,
            'backups'               => [
                'detected'    => (bool) ($backup['has_backup_plugin'] ?? false),
                'solutions'   => $solutions,
                'last_backup' => $this->detectLastBackup(),
            ],
        ];
    }

    /**
     * Whether the PHP branch still receives security fixes. 8.1 is the oldest
     * supported branch at ship time; older is honestly flagged as unsupported.
     */
    private function isPhpSupported(string $version): bool
    {
        return version_compare($version, '8.1', '>=');
    }

    /**
     * Best-effort last-backup timestamp, read from UpdraftPlus's own record
     * (the dominant backup plugin). Null when unknown — we never guess that a
     * backup ran (R14). ISO-8601 UTC when known.
     */
    private function detectLastBackup(): ?string
    {
        $last = get_option('updraft_last_backup');
        if (is_array($last) && ! empty($last['backup_time'])) {
            return gmdate('c', (int) $last['backup_time']);
        }

        return null;
    }

    /**
     * @return array<int, array<string, mixed>>
     */
    private function collectAddons(): array
    {
        $addons = [];

        foreach (apply_filters('lolacore_addon_domains', []) as $slug => $data) {
            if (! is_array($data)) {
                continue;
            }

            $addons[] = [
                'slug'           => (string) $slug,
                'label'          => (string) ($data['label'] ?? ucfirst((string) $slug)),
                'version'        => (string) ($data['version'] ?? ''),
                'license_active' => ! empty($data['license_active']),
            ];
        }

        return $addons;
    }

    /**
     * @param array<string, mixed>|null $scan
     * @return array<string, mixed>
     */
    private function buildPendingUpdates(?array $scan): array
    {
        $pending = [
            'core'    => false,
            'plugins' => [],
            'themes'  => [],
        ];

        if (! is_array($scan)) {
            return $pending;
        }

        if (($scan['wordpress']['is_updated'] ?? true) === false) {
            $pending['core'] = true;
        }

        foreach ($scan['plugins'] ?? [] as $plugin) {
            if (! is_array($plugin) || empty($plugin['update_available'])) {
                continue;
            }

            $pending['plugins'][] = [
                'slug'     => (string) ($plugin['slug'] ?? ''),
                'name'     => (string) ($plugin['name'] ?? ''),
                'version'  => (string) ($plugin['version'] ?? ''),
                'new_version' => (string) ($plugin['new_version'] ?? ''),
            ];
        }

        $theme = $scan['theme'] ?? null;
        if (is_array($theme) && ! empty($theme['update_available'])) {
            $pending['themes'][] = [
                'slug'        => (string) ($theme['active_theme_slug'] ?? ''),
                'name'        => (string) ($theme['active_theme_name'] ?? ''),
                'version'     => (string) ($theme['theme_version'] ?? ''),
                'new_version' => (string) ($theme['new_version'] ?? ''),
            ];
        }

        return $pending;
    }

    /**
     * @return array<int, array<string, mixed>>
     */
    private function fetchRecentActions(int $limit): array
    {
        $logger = new ActionLogger();
        $result = $logger->getHistory([], $limit);
        $rows   = [];

        foreach ($result['actions'] ?? [] as $action) {
            if (! is_array($action)) {
                continue;
            }

            $rows[] = [
                'id'          => (int) ($action['id'] ?? 0),
                'action_type' => (string) ($action['action_type'] ?? ''),
                'description' => (string) ($action['description'] ?? ''),
                'status'      => (string) ($action['status'] ?? ''),
                'source'      => (string) ($action['source'] ?? ''),
                'executed_at' => (string) ($action['executed_at'] ?? ''),
            ];
        }

        return $rows;
    }

    /**
     * @param array<string, mixed>|null $scan
     */
    private function deriveSiteHealth(?array $scan): string
    {
        if (! is_array($scan)) {
            return 'unknown';
        }

        $critical = 0;

        if (($scan['security']['ssl_active'] ?? true) === false) {
            ++$critical;
        }

        if (($scan['security']['debug_mode'] ?? false) === true) {
            ++$critical;
        }

        if (($scan['wordpress']['is_updated'] ?? true) === false) {
            ++$critical;
        }

        $pendingPlugins = count(array_filter(
            $scan['plugins'] ?? [],
            static fn($plugin) => is_array($plugin) && ! empty($plugin['update_available'])
        ));

        if ($critical > 0) {
            return 'critical';
        }

        if ($pendingPlugins > 0 || ! empty($scan['theme']['update_available'])) {
            return 'recommended';
        }

        return 'good';
    }

    /**
     * @param array<string, mixed> $payload
     * @return array<string, mixed>
     */
    private function withRecentActionsLimit(array $payload, int $recentLimit): array
    {
        $actions = $payload['recent_actions'] ?? [];
        if (! is_array($actions)) {
            return $payload;
        }

        $payload['recent_actions'] = array_slice($actions, 0, $recentLimit);

        return $payload;
    }
}
