<?php

declare(strict_types=1);

namespace ShieldX\Maintenance;

defined('ABSPATH') || exit;

use ShieldX\Scanner\LocalScanner;

final class AdminChangeShadowScanner
{
    public const LAST_OPTION = 'shieldx_last_admin_shadow_scan';
    public const RECENT_OPTION = 'shieldx_recent_admin_shadow_scans';

    /** @var LocalScanner */
    private $scanner;

    /** @var array<string,array<string,mixed>> */
    private $deferred = [];

    /** @var bool */
    private $shutdownRegistered = false;

    public function __construct(LocalScanner $scanner)
    {
        $this->scanner = $scanner;
    }

    public static function fromWordPress(): self
    {
        return new self(LocalScanner::fromWordPress());
    }

    public function register(): void
    {
        add_action('upgrader_process_complete', [$this, 'afterUpgraderProcessComplete'], 90, 2);
        add_action('deleted_plugin', [$this, 'afterDeletedPlugin'], 20, 2);
        add_action('delete_theme', [$this, 'afterDeleteTheme'], 20, 1);
        add_action('admin_notices', [$this, 'adminNotice']);
    }

    /**
     * @param mixed               $upgrader
     * @param array<string,mixed> $hookExtra
     */
    public function afterUpgraderProcessComplete($upgrader, array $hookExtra): void
    {
        $scopes = $this->scopesForHookExtra($hookExtra);
        if (!$scopes) {
            return;
        }

        $this->runScan('upgrader', $scopes, $this->labelForHookExtra($hookExtra));
    }

    /**
     * @param mixed $pluginFile
     * @param mixed $deleted
     */
    public function afterDeletedPlugin($pluginFile, $deleted): void
    {
        if (empty($deleted)) {
            return;
        }

        $label = sprintf(
            /* translators: %s is a plugin file path. */
            __('Plugin deleted: %s', 'xerotact-shield-x-security'),
            is_scalar($pluginFile) ? (string) $pluginFile : __('unknown plugin', 'xerotact-shield-x-security')
        );
        $this->deferScan('plugin_deleted', ['plugins'], $label);
    }

    /**
     * @param mixed $stylesheet
     */
    public function afterDeleteTheme($stylesheet): void
    {
        $label = sprintf(
            /* translators: %s is a theme stylesheet directory. */
            __('Theme deleted: %s', 'xerotact-shield-x-security'),
            is_scalar($stylesheet) ? (string) $stylesheet : __('unknown theme', 'xerotact-shield-x-security')
        );
        $this->deferScan('theme_deleted', ['themes'], $label);
    }

    public function runDeferredScans(): void
    {
        foreach ($this->deferred as $job) {
            $scopes = isset($job['scopes']) && is_array($job['scopes']) ? array_map('strval', $job['scopes']) : [];
            if (!$scopes) {
                continue;
            }

            $this->runScan(
                (string) ($job['source'] ?? 'admin_change'),
                $scopes,
                (string) ($job['label'] ?? __('Admin file change', 'xerotact-shield-x-security'))
            );
        }
    }

    public function adminNotice(): void
    {
        if (!current_user_can(self::manageCapability())) {
            return;
        }

        $result = self::lastResult();
        if (!$result || (int) ($result['changes'] ?? 0) < 1) {
            return;
        }

        $scanId = (string) ($result['scan_id'] ?? '');
        if ($scanId === '') {
            return;
        }

        $reviewUrl = self::reviewUrl($result);
        ?>
        <div class="notice notice-warning is-dismissible">
            <p>
                <strong><?php echo esc_html__('SHIELD-X shadow scan is ready.', 'xerotact-shield-x-security'); ?></strong>
                <?php
                echo esc_html(sprintf(
                    /* translators: 1: number of files, 2: admin action label. */
                    _n('%1$d changed file was captured after %2$s.', '%1$d changed files were captured after %2$s.', (int) ($result['changes'] ?? 0), 'xerotact-shield-x-security'),
                    (int) ($result['changes'] ?? 0),
                    (string) ($result['label'] ?? __('an admin change', 'xerotact-shield-x-security'))
                ));
                ?>
                <a href="<?php echo esc_url($reviewUrl); ?>"><?php echo esc_html__('Review shadow session', 'xerotact-shield-x-security'); ?></a>
            </p>
        </div>
        <?php
    }

    /**
     * @return array<string,mixed>
     */
    public static function lastResult(): array
    {
        if (!function_exists('get_option')) {
            return [];
        }

        $result = get_option(self::LAST_OPTION, []);

        return is_array($result) ? $result : [];
    }

    /**
     * @param array<string,mixed> $result
     */
    public static function reviewUrl(array $result): string
    {
        $scanId = (string) ($result['scan_id'] ?? '');
        $changes = max(0, (int) ($result['changes'] ?? 0));

        return admin_url('admin.php?page=shield-x-backup&shieldx_shadow_scanned=1&shieldx_shadow_scan=' . rawurlencode($scanId) . '&shieldx_shadow_changes=' . $changes . '#shieldx-file-shadow-copies');
    }

    /**
     * @param array<string,mixed> $hookExtra
     * @return array<int,string>
     */
    private function scopesForHookExtra(array $hookExtra): array
    {
        $type = strtolower((string) ($hookExtra['type'] ?? ''));
        $action = strtolower((string) ($hookExtra['action'] ?? ''));
        if (!in_array($action, ['install', 'update', 'upgrade'], true)) {
            return [];
        }

        if ($type === 'plugin') {
            return ['plugins'];
        }

        if ($type === 'theme') {
            return ['themes'];
        }

        if ($type === 'core') {
            return ['root', 'core-admin', 'core-includes'];
        }

        if ($type === 'translation') {
            return ['content-other'];
        }

        return [];
    }

    /**
     * @param array<string,mixed> $hookExtra
     */
    private function labelForHookExtra(array $hookExtra): string
    {
        $type = strtolower((string) ($hookExtra['type'] ?? 'files'));
        $action = strtolower((string) ($hookExtra['action'] ?? 'update'));
        $items = [];

        foreach (['plugin', 'plugins', 'theme', 'themes'] as $key) {
            if (!isset($hookExtra[$key])) {
                continue;
            }

            $value = $hookExtra[$key];
            if (is_array($value)) {
                $items = array_merge($items, array_map('strval', $value));
            } elseif (is_scalar($value)) {
                $items[] = (string) $value;
            }
        }

        $label = trim($action . ' ' . $type);
        if ($items) {
            $label .= ' (' . implode(', ', array_unique($items)) . ')';
        }

        return $label;
    }

    /**
     * @param array<int,string> $scopes
     */
    private function deferScan(string $source, array $scopes, string $label): void
    {
        $scopes = array_values(array_unique(array_filter(array_map('strval', $scopes))));
        if (!$scopes) {
            return;
        }

        $key = $source . '|' . implode(',', $scopes) . '|' . $label;
        $this->deferred[$key] = [
            'source' => $source,
            'scopes' => $scopes,
            'label' => $label,
        ];

        if (!$this->shutdownRegistered) {
            $this->shutdownRegistered = true;
            register_shutdown_function([$this, 'runDeferredScans']);
        }
    }

    /**
     * @param array<int,string> $scopes
     */
    private function runScan(string $source, array $scopes, string $label): void
    {
        if (function_exists('wp_installing') && wp_installing()) {
            return;
        }

        $scopes = $this->scanner->sanitizeScopes($scopes);
        if (!$scopes) {
            return;
        }

        if (function_exists('set_time_limit')) {
            // phpcs:ignore Squiz.PHP.DiscouragedFunctions.Discouraged -- Admin-triggered shadow scans may need more than the default shared-hosting timeout.
            set_time_limit(300);
        }

        try {
            $result = $this->scanner->runQuickScan(LocalScanner::DEFAULT_MAX_FILES, $scopes, 'admin_change');
        } catch (\Throwable $error) {
            $this->safeRecord([
                'scan_id' => '',
                'status' => 'failed',
                'source' => $source,
                'label' => $label,
                'scopes' => $scopes,
                'changes' => 0,
                'error' => $error->getMessage(),
                'completed_at' => gmdate('c'),
            ]);
            return;
        }

        $changedItems = isset($result['shadow_changed_items']) && is_array($result['shadow_changed_items']) ? $result['shadow_changed_items'] : [];
        $this->safeRecord([
            'scan_id' => (string) ($result['scan_id'] ?? ''),
            'status' => (string) ($result['status'] ?? 'completed'),
            'source' => $source,
            'label' => $label,
            'scopes' => $scopes,
            'scope_label' => (string) ($result['scope_label'] ?? ''),
            'changes' => count($changedItems),
            'pending' => (int) ($result['shadow_changes_pending'] ?? count($changedItems)),
            'issues' => (int) ($result['issues_found'] ?? 0),
            'completed_at' => gmdate('c'),
        ]);
    }

    /**
     * @param array<string,mixed> $result
     */
    private function safeRecord(array $result): void
    {
        try {
            $this->record($result);
        } catch (\Throwable $error) {
            \ShieldX\Support\ErrorLog::write('SHIELD-X admin change shadow scan record failed: ' . $error->getMessage());
        }
    }

    /**
     * @param array<string,mixed> $result
     */
    private function record(array $result): void
    {
        update_option(self::LAST_OPTION, $result, false);

        $recent = get_option(self::RECENT_OPTION, []);
        $recent = is_array($recent) ? $recent : [];
        array_unshift($recent, $result);
        $recent = array_slice($recent, 0, 10);
        update_option(self::RECENT_OPTION, $recent, false);
    }

    private static function manageCapability(): string
    {
        return defined('SHIELDX_MANAGE_CAPABILITY') ? (string) SHIELDX_MANAGE_CAPABILITY : 'manage_options';
    }
}
