<?php

declare(strict_types=1);

namespace ShieldX\Scanner;

defined('ABSPATH') || exit;

final class ComponentProvenance
{
    public const OPTION_NAME = 'shieldx_component_provenance';
    public const STATUSES = [
        'wordpress_org',
        'xerotact',
        'trusted_package',
        'trusted_installed',
        'unverified',
    ];

    /** @var PluginFileVerifier */
    private $pluginVerifier;

    public function __construct(?PluginFileVerifier $pluginVerifier = null)
    {
        $this->pluginVerifier = $pluginVerifier ?: PluginFileVerifier::fromWordPress();
    }

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

    /**
     * @return array<int,array{type:string,slug:string,name:string,version:string,status:string}>
     */
    public function all(): array
    {
        $entries = [];
        foreach ($this->pluginVerifier->provenanceEntries() as $entry) {
            if (self::isXerotact((string) $entry['slug'], (string) $entry['name'])) {
                $entry['status'] = 'xerotact';
            }
            $entries[] = $entry;
        }

        foreach ($this->installedThemes() as $theme) {
            $slug = (string) $theme['slug'];
            $version = (string) $theme['version'];
            $status = self::isXerotact($slug, (string) $theme['name'])
                ? 'xerotact'
                : $this->themeStatus($slug, $version);
            $entries[] = [
                'type' => 'theme',
                'slug' => $slug,
                'name' => (string) $theme['name'],
                'version' => $version,
                'status' => $status,
            ];
        }

        usort($entries, static function (array $left, array $right): int {
            return strcmp($left['type'] . '|' . $left['name'], $right['type'] . '|' . $right['name']);
        });

        return $entries;
    }

    /**
     * @return array{total:int,verified:int,unverified:int,counts:array<string,int>}
     */
    public function summary(): array
    {
        $counts = array_fill_keys(self::STATUSES, 0);
        foreach ($this->all() as $entry) {
            $status = in_array($entry['status'], self::STATUSES, true) ? $entry['status'] : 'unverified';
            $counts[$status]++;
        }

        $unverified = (int) $counts['unverified'];

        return [
            'total' => array_sum($counts),
            'verified' => array_sum($counts) - $unverified,
            'unverified' => $unverified,
            'counts' => $counts,
        ];
    }

    public function trustInstalledTheme(string $slug): bool
    {
        $theme = $this->themeBySlug($slug);
        if ($theme === null) {
            return false;
        }

        $this->storeThemeTrust($slug, (string) $theme['version'], 'trusted_installed');

        return true;
    }

    public function trustThemePackage(string $slug, string $archive): bool
    {
        $theme = $this->themeBySlug($slug);
        if ($theme === null || !is_file($archive) || !is_readable($archive) || !class_exists('ZipArchive')) {
            return false;
        }
        $bytes = filesize($archive);
        if (!is_int($bytes) || $bytes < 128 || $bytes > 67108864) {
            return false;
        }

        $zip = new \ZipArchive();
        if ($zip->open($archive) !== true) {
            return false;
        }

        $packageSlug = '';
        $packageVersion = '';
        try {
            for ($index = 0; $index < $zip->numFiles; $index++) {
                $name = str_replace('\\', '/', (string) $zip->getNameIndex($index));
                if (preg_match('~^(?P<slug>[^/]+)/style\.css$~i', $name, $matches) !== 1) {
                    continue;
                }
                $contents = $zip->getFromIndex($index, 65536);
                if (!is_string($contents)) {
                    continue;
                }
                $packageSlug = sanitize_key((string) $matches['slug']);
                if (preg_match('/^[ \t\/*#@]*Version:\s*(.+)$/mi', $contents, $versionMatch) === 1) {
                    $packageVersion = trim((string) $versionMatch[1]);
                }
                break;
            }
        } finally {
            $zip->close();
        }

        if ($packageSlug !== sanitize_key($slug) || $packageVersion === '' || $packageVersion !== (string) $theme['version']) {
            return false;
        }

        $this->storeThemeTrust($slug, $packageVersion, 'trusted_package');

        return true;
    }

    private function themeStatus(string $slug, string $version): string
    {
        if ($this->isOfficialTheme($slug)) {
            return 'wordpress_org';
        }

        $registry = $this->registry();
        $key = self::registryKey('theme', $slug, $version);
        $status = isset($registry[$key]) && is_array($registry[$key]) ? (string) ($registry[$key]['status'] ?? '') : '';

        return in_array($status, ['trusted_package', 'trusted_installed'], true) ? $status : 'unverified';
    }

    private function isOfficialTheme(string $slug): bool
    {
        if (!function_exists('get_site_transient')) {
            return false;
        }
        $updates = get_site_transient('update_themes');
        if (!is_object($updates)) {
            return false;
        }

        return (isset($updates->response[$slug]) && is_array($updates->response[$slug]))
            || (isset($updates->no_update[$slug]) && is_array($updates->no_update[$slug]));
    }

    /**
     * @return array<int,array{slug:string,name:string,version:string}>
     */
    private function installedThemes(): array
    {
        if (!function_exists('wp_get_themes')) {
            return [];
        }

        $themes = [];
        foreach ((array) wp_get_themes() as $slug => $theme) {
            if (!is_object($theme) || !method_exists($theme, 'get')) {
                continue;
            }
            $version = trim((string) $theme->get('Version'));
            if ($version === '') {
                continue;
            }
            $themes[] = [
                'slug' => sanitize_key((string) $slug),
                'name' => trim((string) $theme->get('Name')) ?: sanitize_key((string) $slug),
                'version' => $version,
            ];
        }

        return $themes;
    }

    /**
     * @return array{slug:string,name:string,version:string}|null
     */
    private function themeBySlug(string $slug): ?array
    {
        $slug = sanitize_key($slug);
        foreach ($this->installedThemes() as $theme) {
            if ($theme['slug'] === $slug) {
                return $theme;
            }
        }

        return null;
    }

    private function storeThemeTrust(string $slug, string $version, string $status): void
    {
        $registry = $this->registry();
        $registry[self::registryKey('theme', $slug, $version)] = [
            'status' => $status,
            'trusted_at' => gmdate('c'),
        ];
        if (function_exists('update_option')) {
            update_option(self::OPTION_NAME, $registry, false);
        }
    }

    /**
     * @return array<string,array<string,string>>
     */
    private function registry(): array
    {
        $stored = function_exists('get_option') ? get_option(self::OPTION_NAME, []) : [];

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

    private static function registryKey(string $type, string $slug, string $version): string
    {
        return hash('sha256', sanitize_key($type) . '|' . sanitize_key($slug) . '|' . trim($version));
    }

    private static function isXerotact(string $slug, string $name): bool
    {
        $value = strtolower($slug . ' ' . $name);

        return $slug === 'xerotact-shield-x-security'
            || strpos($slug, 'shieldx-') === 0
            || strpos($value, 'shield-x') !== false
            || strpos($value, 'xerotact') !== false;
    }
}
