<?php

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

declare(strict_types=1);

namespace LolaCore\Hub;

use LolaCore\Actions\ActionExecutor;

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

/**
 * Computes the "relevant state slice" fingerprint for fleet abilities (UI-4 §6).
 *
 * Only abilities with a defined state slice return a verifiable fingerprint (UI-4 §6).
 * Non-verifiable abilities skip fingerprint checks; the master approval freezes params (§3.3).
 */
final class HubFingerprint
{
    /**
     * Action IDs whose state slice can be hashed deterministically for anti-stale approval.
     *
     * @var string[]
     */
    private const VERIFIABLE_ACTION_IDS = [
        'update_plugin',
        'bulk_update_plugins',
        'toggle_plugin',
        'edit_content',
        'update_post_meta',
        'update_theme',
        'bulk_update_themes',
    ];

    /**
     * Meta abilities that expose a stable read slice (low drift risk, still useful for reads).
     *
     * @var string[]
     */
    private const VERIFIABLE_META_ABILITIES = [
        'lolacore/get-site-info',
    ];

    public function isVerifiable(string $ability, array $params): bool
    {
        $actionId = $this->resolveActionId($ability, $params);

        if ($actionId !== null) {
            return in_array($actionId, self::VERIFIABLE_ACTION_IDS, true);
        }

        return in_array($ability, self::VERIFIABLE_META_ABILITIES, true);
    }

    /**
     * Returns a sha256 fingerprint or null when the ability is not verifiable.
     */
    public function compute(string $ability, array $params): ?string
    {
        if (!$this->isVerifiable($ability, $params)) {
            return null;
        }

        $slice = $this->buildStateSlice($ability, $params);

        if ($slice === null) {
            return null;
        }

        return self::hashSlice($slice);
    }

    /**
     * @param array<string, mixed> $slice
     */
    public static function hashSlice(array $slice): string
    {
        self::ksortRecursive($slice);

        return hash('sha256', (string) wp_json_encode($slice));
    }

    private function resolveActionId(string $ability, array $params): ?string
    {
        if ($ability === 'lolacore/execute-action') {
            $actionId = (string) ($params['action_id'] ?? '');
            return $actionId !== '' ? ActionExecutor::normalizeActionId($actionId) : null;
        }

        if (!str_starts_with($ability, 'lolacore/')) {
            return null;
        }

        if (in_array($ability, self::VERIFIABLE_META_ABILITIES, true)) {
            return null;
        }

        $suffix = substr($ability, strlen('lolacore/'));

        return ActionExecutor::normalizeActionId(str_replace('-', '_', $suffix));
    }

    /**
     * @return array<string, mixed>|null
     */
    private function buildStateSlice(string $ability, array $params): ?array
    {
        if ($ability === 'lolacore/get-site-info') {
            global $wp_version;

            return [
                'ability'          => $ability,
                'lolacore_version' => defined('LOLACORE_VERSION') ? LOLACORE_VERSION : '',
                'wp_version'       => $wp_version ?? '',
            ];
        }

        $actionId = $this->resolveActionId($ability, $params);
        if ($actionId === null) {
            return null;
        }

        $actionParams = $ability === 'lolacore/execute-action'
            ? (array) ($params['params'] ?? [])
            : $params;

        return match ($actionId) {
            'update_plugin'        => $this->pluginVersionSlice($actionParams),
            'bulk_update_plugins'  => $this->bulkPluginVersionSlice($actionParams),
            'toggle_plugin'        => $this->pluginActiveSlice($actionParams),
            'edit_content'         => $this->postContentSlice($actionParams),
            'update_post_meta'     => $this->postMetaSlice($actionParams),
            'update_theme'         => $this->themeVersionSlice($actionParams),
            'bulk_update_themes'   => $this->bulkThemeVersionSlice($actionParams),
            default                => null,
        };
    }

    /**
     * @param array<string, mixed> $params
     * @return array<string, mixed>|null
     */
    private function pluginVersionSlice(array $params): ?array
    {
        $plugin = (string) ($params['plugin'] ?? $params['slug'] ?? '');
        if ($plugin === '') {
            return null;
        }

        if (!function_exists('get_plugins')) {
            require_once ABSPATH . 'wp-admin/includes/plugin.php';
        }

        $plugins = get_plugins();
        $version = $plugins[$plugin]['Version'] ?? '';

        return [
            'ability' => 'update_plugin',
            'plugin'  => $plugin,
            'version' => (string) $version,
        ];
    }

    /**
     * @param array<string, mixed> $params
     * @return array<string, mixed>|null
     */
    private function bulkPluginVersionSlice(array $params): ?array
    {
        $plugins = $params['plugins'] ?? [];
        if (!is_array($plugins) || $plugins === []) {
            return null;
        }

        if (!function_exists('get_plugins')) {
            require_once ABSPATH . 'wp-admin/includes/plugin.php';
        }

        $installed = get_plugins();
        $versions  = [];

        foreach ($plugins as $plugin) {
            if (!is_string($plugin) || $plugin === '') {
                continue;
            }
            $versions[$plugin] = (string) ($installed[$plugin]['Version'] ?? '');
        }

        ksort($versions);

        return [
            'ability'  => 'bulk_update_plugins',
            'versions' => $versions,
        ];
    }

    /**
     * @param array<string, mixed> $params
     * @return array<string, mixed>|null
     */
    private function pluginActiveSlice(array $params): ?array
    {
        $plugin = (string) ($params['plugin'] ?? $params['slug'] ?? '');
        if ($plugin === '') {
            return null;
        }

        return [
            'ability' => 'toggle_plugin',
            'plugin'  => $plugin,
            'active'  => is_plugin_active($plugin),
        ];
    }

    /**
     * @param array<string, mixed> $params
     * @return array<string, mixed>|null
     */
    private function postContentSlice(array $params): ?array
    {
        $postId = (int) ($params['post_id'] ?? $params['id'] ?? 0);
        if ($postId <= 0) {
            return null;
        }

        $post = get_post($postId);
        if (!$post instanceof \WP_Post) {
            return null;
        }

        return [
            'ability'       => 'edit_content',
            'post_id'       => $postId,
            'modified_gmt'  => $post->post_modified_gmt,
            'content_hash'  => hash('sha256', (string) $post->post_content),
        ];
    }

    /**
     * @param array<string, mixed> $params
     * @return array<string, mixed>|null
     */
    private function postMetaSlice(array $params): ?array
    {
        $postId = (int) ($params['post_id'] ?? 0);
        $metaKey = (string) ($params['meta_key'] ?? '');
        if ($postId <= 0 || $metaKey === '') {
            return null;
        }

        // phpcs:disable WordPress.DB.SlowDBQuery.slow_db_query_meta_key, WordPress.DB.SlowDBQuery.slow_db_query_meta_value -- Fingerprint payload keys, not a meta_query.
        $slice = [
            'ability'    => 'update_post_meta',
            'post_id'    => $postId,
            'meta_key'   => $metaKey,
            'meta_value' => (string) get_post_meta($postId, $metaKey, true),
        ];
        // phpcs:enable WordPress.DB.SlowDBQuery.slow_db_query_meta_key, WordPress.DB.SlowDBQuery.slow_db_query_meta_value

        return $slice;
    }

    /**
     * @param array<string, mixed> $params
     * @return array<string, mixed>|null
     */
    private function themeVersionSlice(array $params): ?array
    {
        $theme = (string) ($params['theme'] ?? $params['slug'] ?? '');
        if ($theme === '') {
            return null;
        }

        $themeObj = wp_get_theme($theme);

        return [
            'ability' => 'update_theme',
            'theme'   => $theme,
            'version' => $themeObj->exists() ? (string) $themeObj->get('Version') : '',
        ];
    }

    /**
     * @param array<string, mixed> $params
     * @return array<string, mixed>|null
     */
    private function bulkThemeVersionSlice(array $params): ?array
    {
        $themes = $params['themes'] ?? [];
        if (!is_array($themes) || $themes === []) {
            return null;
        }

        $versions = [];
        foreach ($themes as $theme) {
            if (!is_string($theme) || $theme === '') {
                continue;
            }
            $themeObj           = wp_get_theme($theme);
            $versions[$theme] = $themeObj->exists() ? (string) $themeObj->get('Version') : '';
        }

        ksort($versions);

        return [
            'ability'  => 'bulk_update_themes',
            'versions' => $versions,
        ];
    }

    /**
     * @param array<string, mixed> $array
     */
    private static function ksortRecursive(array &$array): void
    {
        ksort($array);
        foreach ($array as &$value) {
            if (is_array($value)) {
                self::ksortRecursive($value);
            }
        }
    }
}
