<?php
declare(strict_types=1);

namespace Help4BuilderSuite\Modules\Licensing;

use Help4BuilderSuite\Contracts\ModuleContract;
use Help4BuilderSuite\Support\IpTools;
use Help4BuilderSuite\Support\Settings;
use WP_REST_Request;
use WP_REST_Response;

if (! defined('ABSPATH')) {
    exit;
}

final class Module implements ModuleContract
{
    private const LICENSE_OPTION = 'h4bs_license_state';
    private const UPDATE_CACHE_KEY = 'h4bs_update_manifest_cache';
    private const THEME_UPDATE_CACHE_KEY = 'h4bs_theme_update_manifest_cache';
    private const UPDATE_CACHE_TTL = 21600;
    private const UPDATE_MANIFEST_URLS = [
        'https://help4wordpress.com/help4-builder-suite/manifest.json',
        'https://md.help4wordpress.com/help4-builder-suite/manifest.json',
        'https://plugins.help4wordpress.com/help4-builder-suite/manifest.json',
        'https://plugin.help4wordpress.com/help4-builder-suite/manifest.json',
    ];
    private const UPDATE_PAGE_URL = 'https://help4wordpress.com/help4-builder-suite/';
    private const UPDATE_LATEST_ZIP_URLS = [
        'https://help4wordpress.com/help4-builder-suite/latest.zip',
        'https://plugins.help4wordpress.com/help4-builder-suite/latest.zip',
        'https://plugin.help4wordpress.com/help4-builder-suite/latest.zip',
        'https://md.help4wordpress.com/help4-builder-suite/latest.zip',
    ];
    private const THEME_MANIFEST_URLS = [
        'https://help4wordpress.com/help4-blank/manifest.json',
        'https://md.help4wordpress.com/help4-blank/manifest.json',
        'https://plugins.help4wordpress.com/help4-blank/manifest.json',
        'https://plugin.help4wordpress.com/help4-blank/manifest.json',
    ];
    private const THEME_UPDATE_PAGE_URL = 'https://help4wordpress.com/help4-blank/';
    private const THEME_LATEST_ZIP_URLS = [
        'https://help4wordpress.com/help4-blank/help4-blank.zip',
        'https://plugins.help4wordpress.com/help4-blank/help4-blank.zip',
        'https://plugin.help4wordpress.com/help4-blank/help4-blank.zip',
        'https://md.help4wordpress.com/help4-blank/help4-blank.zip',
    ];
    private const PACKAGE_HOSTS = [
        'help4wordpress.com',
        'plugins.help4wordpress.com',
        'md.help4wordpress.com',
        'plugin.help4wordpress.com',
    ];
    private const THEME_SLUG = 'help4-blank';
    private const PRODUCT_ID = 'help4-builder-suite';
    private const REQUEST_TIMEOUT = 20;
    /**
     * @var array<string, bool>
     */
    private array $urlReachabilityCache = [];

    public function key(): string
    {
        return 'licensing';
    }

    public function boot(): void
    {
        add_action('admin_menu', [$this, 'registerAdminPage']);
        add_action('admin_notices', [$this, 'renderLicenseNotice']);
        add_action('rest_api_init', [$this, 'registerRestRoutes']);
        add_filter('site_transient_update_plugins', [$this, 'sanitizePluginUpdateTransient'], 5);
        add_filter('site_transient_update_themes', [$this, 'sanitizeThemeUpdateTransient'], 5);
        add_filter('pre_set_site_transient_update_plugins', [$this, 'injectPluginUpdate']);
        add_filter('pre_set_site_transient_update_themes', [$this, 'injectThemeUpdate']);
        add_filter('plugins_api', [$this, 'pluginsApiInfo'], 20, 3);
        add_filter('update_plugins_help4wordpress.com', [$this, 'injectUpdateUri'], 20, 4);
    }

    public function registerAdminPage(): void
    {
        add_submenu_page(
            'h4bs-settings',
            __('Licensing', 'help4-builder-suite'),
            __('Licensing', 'help4-builder-suite'),
            'manage_options',
            'h4bs-licensing',
            [$this, 'renderAdminPage']
        );
    }

    public function renderAdminPage(): void
    {
        $status = $this->licenseStatus();
        $raw = is_array($status['raw'] ?? null) ? $status['raw'] : [];
        $manifestHints = implode("\n", $this->updateManifestUrls());
        ?>
        <div class="wrap h4bs-admin-wrap">
            <h1><?php esc_html_e('Help4 Updates', 'help4-builder-suite'); ?></h1>
            <p><?php esc_html_e('This build is free and does not require a license key.', 'help4-builder-suite'); ?></p>
            <table class="widefat striped">
                <tbody>
                    <tr>
                        <th><?php esc_html_e('Status', 'help4-builder-suite'); ?></th>
                        <td><?php echo esc_html((string) ($status['status'] ?? 'unknown')); ?></td>
                    </tr>
                    <tr>
                        <th><?php esc_html_e('Message', 'help4-builder-suite'); ?></th>
                        <td><?php echo esc_html((string) ($status['message'] ?? '')); ?></td>
                    </tr>
                    <tr>
                        <th><?php esc_html_e('Update Manifest (hardcoded)', 'help4-builder-suite'); ?></th>
                        <td><pre style="margin:0;white-space:pre-wrap;"><code><?php echo esc_html($manifestHints); ?></code></pre></td>
                    </tr>
                    <tr>
                        <th><?php esc_html_e('Detected Root Domain', 'help4-builder-suite'); ?></th>
                        <td><?php echo esc_html((string) ($raw['detected_root_domain'] ?? ($status['bound_domain'] ?? ''))); ?></td>
                    </tr>
                    <tr>
                        <th><?php esc_html_e('Detected Server IP', 'help4-builder-suite'); ?></th>
                        <td><?php echo esc_html((string) ($raw['detected_server_ip'] ?? ($status['ip'] ?? ''))); ?></td>
                    </tr>
                    <tr>
                        <th><?php esc_html_e('Installed Version', 'help4-builder-suite'); ?></th>
                        <td><?php echo esc_html(H4BS_VERSION); ?></td>
                    </tr>
                    <tr>
                        <th><?php esc_html_e('Latest Version', 'help4-builder-suite'); ?></th>
                        <td><?php echo esc_html((string) ($raw['latest_version'] ?? '')); ?></td>
                    </tr>
                    <tr>
                        <th><?php esc_html_e('Update Package URL', 'help4-builder-suite'); ?></th>
                        <td><code><?php echo esc_html((string) ($raw['package_url'] ?? '')); ?></code></td>
                    </tr>
                </tbody>
            </table>
            <p><?php esc_html_e('Force refresh via API: POST /wp-json/h4bs/v1/license/activate.', 'help4-builder-suite'); ?></p>
        </div>
        <?php
    }

    public function renderLicenseNotice(): void
    {
        return;
    }

    public function registerRestRoutes(): void
    {
        register_rest_route(
            'h4bs/v1',
            '/license/status',
            [
                'methods'             => 'GET',
                'callback'            => [$this, 'statusRoute'],
                'permission_callback' => static fn (): bool => current_user_can('manage_options'),
            ]
        );

        register_rest_route(
            'h4bs/v1',
            '/license/activate',
            [
                'methods'             => 'POST',
                'callback'            => [$this, 'activateRoute'],
                'permission_callback' => static fn (): bool => current_user_can('manage_options'),
            ]
        );
    }

    public function statusRoute(): WP_REST_Response
    {
        return new WP_REST_Response($this->licenseStatus());
    }

    public function activateRoute(WP_REST_Request $request): WP_REST_Response
    {
        delete_transient(self::UPDATE_CACHE_KEY);
        delete_transient(self::THEME_UPDATE_CACHE_KEY);
        $checked = $this->licenseStatus(true);
        return new WP_REST_Response($checked, 200);
    }

    /**
     * @param object|mixed $transient
     * @return object|mixed
     */
    public function injectPluginUpdate($transient)
    {
        if (! is_object($transient)) {
            return $transient;
        }

        $transient = $this->sanitizePluginUpdateTransient($transient);
        $pluginFile = plugin_basename(H4BS_FILE);

        $metadata = $this->updateMetadata();
        $newVersion = sanitize_text_field((string) ($metadata['latest_version'] ?? ''));
        $package = esc_url_raw((string) ($metadata['package_url'] ?? ''));
        $updateAvailable = rest_sanitize_boolean($metadata['update_available'] ?? false);

        if ($newVersion === '' || version_compare(H4BS_VERSION, $newVersion, '>=')) {
            return $transient;
        }
        if ($package === '' || ! $this->isAllowedPluginPackageUrl($package) || ! $updateAvailable) {
            return $transient;
        }

        $payload = (object) [
            'slug'        => dirname($pluginFile),
            'plugin'      => $pluginFile,
            'new_version' => $newVersion,
            'url'         => esc_url_raw((string) ($metadata['changelog_url'] ?? 'https://help4wordpress.com/plugin-help4-builder-suite/')),
            'package'     => $package,
            'tested'      => sanitize_text_field((string) ($metadata['tested_up_to'] ?? '')),
            'requires'    => sanitize_text_field((string) ($metadata['min_wp'] ?? '')),
            'requires_php'=> sanitize_text_field((string) ($metadata['min_php'] ?? '')),
        ];

        if (! isset($transient->response) || ! is_array($transient->response)) {
            $transient->response = [];
        }
        $transient->response[$pluginFile] = $payload;

        return $transient;
    }

    /**
     * @param object|mixed $transient
     * @return object|mixed
     */
    public function injectThemeUpdate($transient)
    {
        if (! is_object($transient)) {
            return $transient;
        }

        $transient = $this->sanitizeThemeUpdateTransient($transient);
        $installedVersion = (string) wp_get_theme(self::THEME_SLUG)->get('Version');

        $metadata = $this->themeMetadata();
        $newVersion = sanitize_text_field((string) ($metadata['latest_version'] ?? ''));
        $package = esc_url_raw((string) ($metadata['package_url'] ?? ''));
        $updateAvailable = rest_sanitize_boolean($metadata['update_available'] ?? false);

        if ($newVersion === '' || version_compare($installedVersion, $newVersion, '>=')) {
            return $transient;
        }
        if ($package === '' || ! $this->isAllowedThemePackageUrl($package) || ! $updateAvailable) {
            return $transient;
        }

        if (! isset($transient->response) || ! is_array($transient->response)) {
            $transient->response = [];
        }

        $transient->response[self::THEME_SLUG] = [
            'theme'        => self::THEME_SLUG,
            'new_version'  => $newVersion,
            'url'          => esc_url_raw((string) ($metadata['changelog_url'] ?? $this->themeUpdatePageUrl())),
            'package'      => $package,
            'requires'     => sanitize_text_field((string) ($metadata['min_wp'] ?? '6.5')),
            'requires_php' => sanitize_text_field((string) ($metadata['min_php'] ?? '8.1')),
        ];

        return $transient;
    }

    /**
     * @param object|mixed $transient
     * @return object|mixed
     */
    public function sanitizePluginUpdateTransient($transient)
    {
        if (is_object($transient)) {
            $this->removeNonNewerResponse($transient, plugin_basename(H4BS_FILE), H4BS_VERSION);
        }

        return $transient;
    }

    /**
     * @param object|mixed $transient
     * @return object|mixed
     */
    public function sanitizeThemeUpdateTransient($transient)
    {
        if (is_object($transient)) {
            $installedVersion = (string) wp_get_theme(self::THEME_SLUG)->get('Version');
            $this->removeNonNewerResponse($transient, self::THEME_SLUG, $installedVersion);
        }

        return $transient;
    }

    private function removeNonNewerResponse(object $transient, string $key, string $installedVersion): void
    {
        if ($installedVersion === '' || ! isset($transient->response) || ! is_array($transient->response)) {
            return;
        }

        $entry = $transient->response[$key] ?? null;
        if (is_object($entry)) {
            $offeredVersion = sanitize_text_field((string) ($entry->new_version ?? $entry->version ?? ''));
        } elseif (is_array($entry)) {
            $offeredVersion = sanitize_text_field((string) ($entry['new_version'] ?? $entry['version'] ?? ''));
        } else {
            $offeredVersion = '';
        }

        if ($offeredVersion === '' || version_compare($offeredVersion, $installedVersion, '<=')) {
            unset($transient->response[$key]);
        }
    }

    /**
     * @param object|mixed $result
     * @return object|mixed
     */
    public function pluginsApiInfo($result, string $action, ?object $args)
    {
        if ($action !== 'plugin_information' || ! is_object($args)) {
            return $result;
        }

        $slug = sanitize_key((string) ($args->slug ?? ''));
        if ($slug !== self::PRODUCT_ID) {
            return $result;
        }

        $metadata = $this->updateMetadata();
        $latestVersion = sanitize_text_field((string) ($metadata['latest_version'] ?? H4BS_VERSION));
        $package = esc_url_raw((string) ($metadata['package_url'] ?? ''));
        $requires = sanitize_text_field((string) ($metadata['min_wp'] ?? '6.5'));
        $requiresPhp = sanitize_text_field((string) ($metadata['min_php'] ?? '8.1'));
        $tested = sanitize_text_field((string) ($metadata['tested_up_to'] ?? ''));
        $downloaded = absint($metadata['downloads'] ?? 0);
        $lastUpdated = sanitize_text_field((string) ($metadata['issued_at'] ?? gmdate('Y-m-d H:i:s')));
        $changelogUrl = esc_url_raw((string) ($metadata['changelog_url'] ?? ($this->updatePageUrl() . 'changelog.txt')));

        $response = (object) [
            'name'          => 'Help4 Builder Suite',
            'slug'          => self::PRODUCT_ID,
            'version'       => $latestVersion,
            'author'        => '<a href="https://help4wordpress.com/about-us/">Help4 WordPress</a>',
            'homepage'      => esc_url_raw(home_url('/')),
            'requires'      => $requires,
            'requires_php'  => $requiresPhp,
            'tested'        => $tested,
            'downloaded'    => $downloaded,
            'last_updated'  => $lastUpdated,
            'download_link' => $package,
            'sections'      => [
                'description' => 'Help4 Builder Suite update channel.',
                'changelog'   => '<p><a href="' . esc_url($changelogUrl) . '" target="_blank" rel="noopener">View changelog</a></p>',
            ],
        ];

        return $response;
    }

    /**
     * Supply update data for the public package's first-party Update URI.
     *
     * @param array<string, mixed>|false $update
     * @param array<string, mixed>       $pluginData
     * @param array<int, string>         $locales
     * @return array<string, mixed>|false
     */
    public function injectUpdateUri($update, array $pluginData, string $pluginFile, array $locales)
    {
        unset($pluginData, $locales);

        if ($pluginFile !== plugin_basename(H4BS_FILE)) {
            return $update;
        }

        $metadata = $this->updateMetadata();
        $newVersion = sanitize_text_field((string) ($metadata['latest_version'] ?? ''));
        $package = esc_url_raw((string) ($metadata['package_url'] ?? ''));
        $updateAvailable = rest_sanitize_boolean($metadata['update_available'] ?? false);

        if ($newVersion === '' || version_compare(H4BS_VERSION, $newVersion, '>=')) {
            return false;
        }
        if ($package === '' || ! $this->isAllowedPluginPackageUrl($package) || ! $updateAvailable) {
            return $update;
        }

        return [
            'id'           => self::UPDATE_PAGE_URL,
            'slug'         => self::PRODUCT_ID,
            'version'      => $newVersion,
            'url'          => esc_url_raw((string) ($metadata['changelog_url'] ?? self::UPDATE_PAGE_URL)),
            'package'      => $package,
            'tested'       => sanitize_text_field((string) ($metadata['tested_up_to'] ?? '')),
            'requires'     => sanitize_text_field((string) ($metadata['min_wp'] ?? '')),
            'requires_php' => sanitize_text_field((string) ($metadata['min_php'] ?? '')),
            'autoupdate'   => false,
        ];
    }

    /**
     * @return array<string, mixed>
     */
    private function licenseStatus(bool $forceRefresh = false): array
    {
        $serverIp = IpTools::currentIp();
        $host = (string) (wp_parse_url(home_url('/'), PHP_URL_HOST) ?: '');
        $rootDomain = $this->rootDomain($host);
        $raw = $this->updateMetadata($forceRefresh);

        return [
            'status'       => 'valid',
            'valid'        => true,
            'whitelist'    => false,
            'ip'           => $serverIp,
            'bound_domain' => $rootDomain,
            'message'      => 'Free edition: no license key required.',
            'last_check'   => time(),
            'key'          => '',
            'raw'          => $raw,
        ];
    }

    /**
     * @return array<string, mixed>
     */
    private function updateMetadata(bool $forceRefresh = false): array
    {
        if ($forceRefresh) {
            delete_transient(self::UPDATE_CACHE_KEY);
        }

        $cached = get_transient(self::UPDATE_CACHE_KEY);
        if (is_array($cached) && $cached) {
            return $cached;
        }

        $remote = $this->fetchRemoteUpdateMetadata($forceRefresh);
        if (! $remote) {
            return [];
        }

        set_transient(self::UPDATE_CACHE_KEY, $remote, self::UPDATE_CACHE_TTL);
        return $remote;
    }

    /**
     * @return array<string, mixed>
     */
    private function themeMetadata(bool $forceRefresh = false): array
    {
        if ($forceRefresh) {
            delete_transient(self::THEME_UPDATE_CACHE_KEY);
        }

        $cached = get_transient(self::THEME_UPDATE_CACHE_KEY);
        if (is_array($cached) && $cached) {
            return $cached;
        }

        $remote = $this->fetchRemoteThemeMetadata($forceRefresh);
        if (! $remote) {
            return [];
        }

        set_transient(self::THEME_UPDATE_CACHE_KEY, $remote, self::UPDATE_CACHE_TTL);
        return $remote;
    }

    /**
     * @return array<string, mixed>
     */
    private function fetchRemoteUpdateMetadata(bool $forceRefresh = false): array
    {
        $manifests = $this->updateManifestUrls();
        $candidates = [];

        foreach ($manifests as $manifestUrl) {
            $response = wp_remote_get(
                $this->manifestRequestUrl($manifestUrl, $forceRefresh),
                [
                    'timeout' => self::REQUEST_TIMEOUT,
                    'headers' => [
                        'Accept' => 'application/json',
                    ],
                ]
            );

            if (is_wp_error($response)) {
                continue;
            }

            $statusCode = (int) wp_remote_retrieve_response_code($response);
            if ($statusCode < 200 || $statusCode >= 300) {
                continue;
            }

            $decoded = json_decode((string) wp_remote_retrieve_body($response), true);
            if (! is_array($decoded)) {
                continue;
            }

            $normalized = $this->normalizeRemoteUpdateMetadata($decoded);
            if ($normalized) {
                $candidates[] = $normalized;
            }
            if (count($candidates) >= 2) {
                break;
            }
        }

        usort(
            $candidates,
            static fn (array $a, array $b): int => version_compare(
                (string) ($b['latest_version'] ?? ''),
                (string) ($a['latest_version'] ?? '')
            )
        );

        return $candidates[0] ?? [];
    }

    /**
     * @return array<string, mixed>
     */
    private function fetchRemoteThemeMetadata(bool $forceRefresh = false): array
    {
        $manifests = $this->themeManifestUrls();
        $candidates = [];

        foreach ($manifests as $manifestUrl) {
            $response = wp_remote_get(
                $this->manifestRequestUrl($manifestUrl, $forceRefresh),
                [
                    'timeout' => self::REQUEST_TIMEOUT,
                    'headers' => [
                        'Accept' => 'application/json',
                    ],
                ]
            );

            if (is_wp_error($response)) {
                continue;
            }

            $statusCode = (int) wp_remote_retrieve_response_code($response);
            if ($statusCode < 200 || $statusCode >= 300) {
                continue;
            }

            $decoded = json_decode((string) wp_remote_retrieve_body($response), true);
            if (! is_array($decoded)) {
                continue;
            }

            $normalized = $this->normalizeRemoteThemeMetadata($decoded);
            if ($normalized) {
                $candidates[] = $normalized;
            }
            if (count($candidates) >= 2) {
                break;
            }
        }

        usort(
            $candidates,
            static fn (array $a, array $b): int => version_compare(
                (string) ($b['latest_version'] ?? ''),
                (string) ($a['latest_version'] ?? '')
            )
        );

        return $candidates[0] ?? [];
    }

    private function manifestRequestUrl(string $manifestUrl, bool $forceRefresh): string
    {
        $slot = $forceRefresh
            ? time() . '-' . wp_rand(1000, 9999)
            : (string) floor(time() / self::UPDATE_CACHE_TTL);

        return add_query_arg(
            [
                'h4bs_client' => H4BS_VERSION,
                'h4bs_check'  => $slot,
            ],
            $manifestUrl
        );
    }

    /**
     * @param array<string, mixed> $raw
     * @return array<string, mixed>
     */
    private function normalizeRemoteUpdateMetadata(array $raw): array
    {
        $versionsMap = is_array($raw['versions'] ?? null) ? $raw['versions'] : [];
        $versions = [];

        foreach ($versionsMap as $version => $urls) {
            $versionKey = sanitize_text_field((string) $version);
            if ($versionKey !== '') {
                $versions[] = $versionKey;
            }
        }

        if (! $versions && is_array($raw['available_versions'] ?? null)) {
            foreach ($raw['available_versions'] as $version) {
                $versionKey = sanitize_text_field((string) $version);
                if ($versionKey !== '') {
                    $versions[] = $versionKey;
                }
            }
        }

        $versions = array_values(array_unique($versions));
        usort(
            $versions,
            static fn (string $a, string $b): int => version_compare($b, $a)
        );

        $latest = sanitize_text_field((string) ($raw['latest_version'] ?? $raw['version'] ?? ''));
        if (
            $latest === ''
            || ($versions && ! in_array($latest, $versions, true))
            || ($versions && version_compare($versions[0], $latest, '>'))
        ) {
            $latest = $versions[0] ?? '';
        }
        if ($latest === '') {
            return [];
        }
        if (! $versions) {
            $versions[] = $latest;
        }

        $latestPackageUrls = [];
        if (is_array($raw['latest_package_urls'] ?? null)) {
            foreach ($raw['latest_package_urls'] as $candidate) {
                $url = esc_url_raw((string) $candidate);
                if ($url !== '' && $this->isAllowedPluginPackageUrl($url)) {
                    $latestPackageUrls[] = $url;
                }
            }
        }

        if (! $latestPackageUrls && is_array($versionsMap[$latest] ?? null)) {
            foreach ((array) $versionsMap[$latest] as $candidate) {
                $url = esc_url_raw((string) $candidate);
                if ($url !== '' && $this->isAllowedPluginPackageUrl($url)) {
                    $latestPackageUrls[] = $url;
                }
            }
        }
        $downloadLink = esc_url_raw((string) ($raw['download_link'] ?? ''));
        if ($downloadLink !== '' && $this->isAllowedPluginPackageUrl($downloadLink)) {
            $latestPackageUrls[] = $downloadLink;
        }
        $latestPackageUrls = array_values(array_unique($latestPackageUrls));

        $packageCandidates = [];
        foreach ($latestPackageUrls as $candidateUrl) {
            if ($this->isCanonicalLatestPluginZip($candidateUrl)) {
                $packageCandidates[] = $candidateUrl;
            }
        }
        foreach ($latestPackageUrls as $candidateUrl) {
            $packageCandidates[] = $candidateUrl;
        }

        $rawPackage = esc_url_raw((string) ($raw['package_url'] ?? $raw['download_link'] ?? ''));
        if ($rawPackage !== '' && $this->isAllowedPluginPackageUrl($rawPackage)) {
            $packageCandidates[] = $rawPackage;
        }
        $packageCandidates = array_values(array_unique($packageCandidates));
        $packageUrl = $this->firstReachableUrl($packageCandidates);
        if ($packageUrl === '') {
            $packageUrl = $this->preferredLatestPluginZipUrl();
        }

        $detectedRoot = $this->rootDomain((string) (wp_parse_url(home_url('/'), PHP_URL_HOST) ?: ''));
        $detectedIp = IpTools::currentIp();

        return [
            'ok'                       => true,
            'status'                   => sanitize_key((string) ($raw['status'] ?? 'valid')),
            'valid'                    => true,
            'message'                  => sanitize_text_field((string) ($raw['message'] ?? 'Update channel active.')),
            'plugin_slug'              => sanitize_key((string) ($raw['plugin_slug'] ?? self::PRODUCT_ID)),
            'plugin_name'              => sanitize_text_field((string) ($raw['plugin_name'] ?? 'Help4 Builder Suite')),
            'installed_version'        => H4BS_VERSION,
            'latest_version'           => $latest,
            'update_available'         => version_compare(H4BS_VERSION, $latest, '<'),
            'package_url'              => $packageUrl,
            'latest_package_urls'      => $latestPackageUrls,
            'changelog_url'            => esc_url_raw((string) ($raw['changelog_url'] ?? ($this->updatePageUrl() . 'changelog.txt'))),
            'min_wp'                   => sanitize_text_field((string) ($raw['min_wp'] ?? '6.5')),
            'min_php'                  => sanitize_text_field((string) ($raw['min_php'] ?? '8.1')),
            'tested_up_to'             => sanitize_text_field((string) ($raw['tested_up_to'] ?? $raw['tested'] ?? '6.9.4')),
            'available_versions'       => $versions ?: [$latest],
            'available_versions_count' => count($versions ?: [$latest]),
            'detected_root_domain'     => sanitize_text_field((string) ($raw['detected_root_domain'] ?? $detectedRoot)),
            'detected_server_ip'       => sanitize_text_field((string) ($raw['detected_server_ip'] ?? $detectedIp)),
            'issued_at'                => sanitize_text_field((string) ($raw['issued_at'] ?? gmdate('c'))),
            'versions'                 => $versionsMap,
        ];
    }

    /**
     * @param array<string, mixed> $raw
     * @return array<string, mixed>
     */
    private function normalizeRemoteThemeMetadata(array $raw): array
    {
        $versionsMap = is_array($raw['versions'] ?? null) ? $raw['versions'] : [];
        $versions = [];

        foreach ($versionsMap as $version => $urls) {
            $versionKey = sanitize_text_field((string) $version);
            if ($versionKey !== '') {
                $versions[] = $versionKey;
            }
        }

        if (! $versions && is_array($raw['available_versions'] ?? null)) {
            foreach ($raw['available_versions'] as $version) {
                $versionKey = sanitize_text_field((string) $version);
                if ($versionKey !== '') {
                    $versions[] = $versionKey;
                }
            }
        }

        $versions = array_values(array_unique($versions));
        usort(
            $versions,
            static fn (string $a, string $b): int => version_compare($b, $a)
        );

        $latest = sanitize_text_field((string) ($raw['latest_version'] ?? $raw['version'] ?? ''));
        if (
            $latest === ''
            || ($versions && ! in_array($latest, $versions, true))
            || ($versions && version_compare($versions[0], $latest, '>'))
        ) {
            $latest = $versions[0] ?? '';
        }
        if ($latest === '') {
            return [];
        }
        if (! $versions) {
            $versions[] = $latest;
        }

        $latestPackageUrls = [];
        if (is_array($raw['latest_package_urls'] ?? null)) {
            foreach ($raw['latest_package_urls'] as $candidate) {
                $url = esc_url_raw((string) $candidate);
                if ($url !== '' && $this->isAllowedThemePackageUrl($url)) {
                    $latestPackageUrls[] = $url;
                }
            }
        }

        if (! $latestPackageUrls && is_array($versionsMap[$latest] ?? null)) {
            foreach ((array) $versionsMap[$latest] as $candidate) {
                $url = esc_url_raw((string) $candidate);
                if ($url !== '' && $this->isAllowedThemePackageUrl($url)) {
                    $latestPackageUrls[] = $url;
                }
            }
        }
        $downloadLink = esc_url_raw((string) ($raw['download_link'] ?? ''));
        if ($downloadLink !== '' && $this->isAllowedThemePackageUrl($downloadLink)) {
            $latestPackageUrls[] = $downloadLink;
        }
        $latestPackageUrls = array_values(array_unique($latestPackageUrls));

        $packageCandidates = [];
        $rawPackage = esc_url_raw((string) ($raw['package_url'] ?? $raw['download_link'] ?? ''));
        if ($rawPackage !== '' && $this->isAllowedThemePackageUrl($rawPackage)) {
            $packageCandidates[] = $rawPackage;
        }
        foreach ($latestPackageUrls as $candidateUrl) {
            $packageCandidates[] = $candidateUrl;
        }
        $packageCandidates = array_values(array_unique($packageCandidates));
        $packageUrl = $this->firstReachableUrl($packageCandidates);
        if ($packageUrl === '') {
            $packageUrl = $this->preferredLatestThemeZipUrl();
        }

        $installed = (string) wp_get_theme(self::THEME_SLUG)->get('Version');

        return [
            'ok'                       => true,
            'status'                   => sanitize_key((string) ($raw['status'] ?? 'valid')),
            'valid'                    => true,
            'message'                  => sanitize_text_field((string) ($raw['message'] ?? 'Theme update channel active.')),
            'theme_slug'               => sanitize_key((string) ($raw['theme_slug'] ?? self::THEME_SLUG)),
            'theme_name'               => sanitize_text_field((string) ($raw['theme_name'] ?? 'Help4 Blank')),
            'installed_version'        => $installed,
            'latest_version'           => $latest,
            'update_available'         => $installed !== '' ? version_compare($installed, $latest, '<') : true,
            'package_url'              => $packageUrl,
            'latest_package_urls'      => $latestPackageUrls,
            'changelog_url'            => esc_url_raw((string) ($raw['changelog_url'] ?? ($this->themeUpdatePageUrl() . 'changelog.txt'))),
            'min_wp'                   => sanitize_text_field((string) ($raw['min_wp'] ?? '6.5')),
            'min_php'                  => sanitize_text_field((string) ($raw['min_php'] ?? '8.1')),
            'tested_up_to'             => sanitize_text_field((string) ($raw['tested_up_to'] ?? $raw['tested'] ?? '6.9.4')),
            'available_versions'       => $versions ?: [$latest],
            'available_versions_count' => count($versions ?: [$latest]),
            'issued_at'                => sanitize_text_field((string) ($raw['issued_at'] ?? gmdate('c'))),
            'versions'                 => $versionsMap,
        ];
    }

    private function rootDomain(string $host): string
    {
        $host = strtolower(trim($host));
        $host = preg_replace('/:\d+$/', '', $host) ?: '';
        if ($host === '') {
            return '';
        }
        if (filter_var($host, FILTER_VALIDATE_IP)) {
            return $host;
        }

        $parts = array_values(array_filter(explode('.', $host), static fn (string $part): bool => $part !== ''));
        if (count($parts) <= 2) {
            return $host;
        }

        $twoLevelTlds = [
            'co.uk',
            'org.uk',
            'com.au',
            'net.au',
            'co.nz',
            'com.br',
            'co.za',
            'com.mx',
            'com.sg',
        ];

        $suffix = $parts[count($parts) - 2] . '.' . $parts[count($parts) - 1];
        if (in_array($suffix, $twoLevelTlds, true) && count($parts) >= 3) {
            return $parts[count($parts) - 3] . '.' . $suffix;
        }

        return $parts[count($parts) - 2] . '.' . $parts[count($parts) - 1];
    }

    private function isLicenseEnforcementPaused(): bool
    {
        $paused = apply_filters('h4bs_license_enforcement_paused', false);
        return (bool) rest_sanitize_boolean($paused);
    }

    private function isCanonicalLatestPluginZip(string $url): bool
    {
        $candidate = esc_url_raw($url);
        if ($candidate === '') {
            return false;
        }
        return $this->isAllowedPluginPackageUrl($candidate)
            && (bool) preg_match('#/(?:help4-beta/)?help4-builder-suite/(latest|help4-builder-suite)\.zip$#i', $candidate);
    }

    private function isAllowedPluginPackageUrl(string $url): bool
    {
        return $this->isAllowedPackageUrl($url, 'plugin');
    }

    private function isAllowedThemePackageUrl(string $url): bool
    {
        return $this->isAllowedPackageUrl($url, 'theme');
    }

    private function isAllowedPackageUrl(string $url, string $type): bool
    {
        $candidate = esc_url_raw($url);
        if ($candidate === '' || ! wp_http_validate_url($candidate)) {
            return false;
        }

        $scheme = strtolower((string) wp_parse_url($candidate, PHP_URL_SCHEME));
        $host = strtolower((string) wp_parse_url($candidate, PHP_URL_HOST));
        $path = (string) wp_parse_url($candidate, PHP_URL_PATH);

        if ($scheme !== 'https' || ! in_array($host, self::PACKAGE_HOSTS, true)) {
            return false;
        }

        if ($type === 'plugin') {
            return (bool) preg_match(
                '#^/(?:help4-beta/)?help4-builder-suite/(?:help4-builder-suite(?:-\d+\.\d+\.\d+)?|latest)\.zip$#i',
                $path
            );
        }

        if ($type === 'theme') {
            return (bool) preg_match(
                '#^/(?:help4-beta/)?help4-blank/(?:help4-blank(?:-\d+\.\d+\.\d+)?|latest)\.zip$#i',
                $path
            );
        }

        return false;
    }

    /**
     * @return array<int, string>
     */
    private function updateManifestUrls(): array
    {
        $fallback = $this->updateManifestFallbackUrls();
        $urls = apply_filters('h4bs_update_manifest_urls', $fallback);
        return $this->sanitizeUrlList($urls, $fallback);
    }

    /**
     * @return array<int, string>
     */
    private function themeManifestUrls(): array
    {
        $fallback = $this->themeManifestFallbackUrls();
        $urls = apply_filters('h4bs_theme_manifest_urls', $fallback);
        return $this->sanitizeUrlList($urls, $fallback);
    }

    /**
     * @return array<int, string>
     */
    private function updateManifestFallbackUrls(): array
    {
        return array_values(array_unique(array_filter(array_merge(
            [
                esc_url_raw((string) Settings::get('update_manifest_url', '')),
                $this->updatePageUrl() . 'manifest.json',
            ],
            self::UPDATE_MANIFEST_URLS
        ))));
    }

    /**
     * @return array<int, string>
     */
    private function themeManifestFallbackUrls(): array
    {
        return array_values(array_unique(array_filter(array_merge(
            [
                $this->themeUpdatePageUrl() . 'manifest.json',
            ],
            self::THEME_MANIFEST_URLS
        ))));
    }

    /**
     * @param mixed $candidateUrls
     * @param array<int, string> $fallback
     * @return array<int, string>
     */
    private function sanitizeUrlList(mixed $candidateUrls, array $fallback): array
    {
        if (! is_array($candidateUrls)) {
            $candidateUrls = $fallback;
        }

        $urls = [];
        foreach ($candidateUrls as $candidateUrl) {
            $url = esc_url_raw((string) $candidateUrl);
            if ($url !== '' && wp_http_validate_url($url)) {
                $urls[] = $url;
            }
        }

        if ($urls) {
            return array_values(array_unique($urls));
        }

        $fallbackUrls = [];
        foreach ($fallback as $fallbackUrl) {
            $url = esc_url_raw((string) $fallbackUrl);
            if ($url !== '' && wp_http_validate_url($url)) {
                $fallbackUrls[] = $url;
            }
        }
        return array_values(array_unique($fallbackUrls));
    }

    private function preferredLatestPluginZipUrl(): string
    {
        $urls = $this->latestPluginZipFallbackUrls();
        $reachable = $this->firstReachableUrl($urls);
        if ($reachable !== '') {
            return $reachable;
        }
        return $this->firstValidUrl($urls);
    }

    private function preferredLatestThemeZipUrl(): string
    {
        $urls = $this->latestThemeZipFallbackUrls();
        $reachable = $this->firstReachableUrl($urls);
        if ($reachable !== '') {
            return $reachable;
        }
        return $this->firstValidUrl($urls);
    }

    /**
     * @return array<int, string>
     */
    private function latestPluginZipFallbackUrls(): array
    {
        return array_values(array_unique(array_filter(array_merge(
            [
                esc_url_raw((string) Settings::get('required_plugin_zip_url', '')),
                $this->updatePageUrl() . 'latest.zip',
            ],
            self::UPDATE_LATEST_ZIP_URLS
        ))));
    }

    /**
     * @return array<int, string>
     */
    private function latestThemeZipFallbackUrls(): array
    {
        return array_values(array_unique(array_filter(array_merge(
            [
                esc_url_raw((string) Settings::get('required_theme_zip_url', '')),
                $this->themeUpdatePageUrl() . 'help4-blank.zip',
            ],
            self::THEME_LATEST_ZIP_URLS
        ))));
    }

    private function updatePageUrl(): string
    {
        return $this->updateChannelBaseUrl() . 'help4-builder-suite/';
    }

    private function themeUpdatePageUrl(): string
    {
        return $this->updateChannelBaseUrl() . 'help4-blank/';
    }

    private function updateChannelBaseUrl(): string
    {
        if (defined('H4BS_UPDATE_CHANNEL_BASE_URL')) {
            $constant = (string) constant('H4BS_UPDATE_CHANNEL_BASE_URL');
            if ($constant !== '') {
                return trailingslashit($constant);
            }
        }

        $filtered = apply_filters('h4bs_update_channel_base_url', '');
        if (is_string($filtered) && $filtered !== '') {
            return trailingslashit($filtered);
        }

        $host = (string) wp_parse_url(home_url('/'), PHP_URL_HOST);
        if ($host === 'md.help4wordpress.com') {
            return 'https://md.help4wordpress.com/';
        }

        return 'https://help4wordpress.com/';
    }

    /**
     * @param array<int, string> $urls
     */
    private function firstValidUrl(array $urls): string
    {
        foreach ($urls as $candidateUrl) {
            $url = esc_url_raw((string) $candidateUrl);
            if ($url !== '' && wp_http_validate_url($url)) {
                return $url;
            }
        }
        return '';
    }

    /**
     * @param array<int, string> $urls
     */
    private function firstReachableUrl(array $urls): string
    {
        foreach ($urls as $candidateUrl) {
            $url = esc_url_raw((string) $candidateUrl);
            if ($url === '' || ! wp_http_validate_url($url)) {
                continue;
            }
            if ($this->isUrlReachable($url)) {
                return $url;
            }
        }
        return '';
    }

    private function isUrlReachable(string $url): bool
    {
        if (isset($this->urlReachabilityCache[$url])) {
            return $this->urlReachabilityCache[$url];
        }

        $host = (string) (wp_parse_url($url, PHP_URL_HOST) ?: '');
        if ($host !== '' && ! filter_var($host, FILTER_VALIDATE_IP)) {
            $resolved = gethostbyname($host);
            if ($resolved === $host) {
                $this->urlReachabilityCache[$url] = false;
                return false;
            }
        }

        $head = wp_remote_head(
            $url,
            [
                'timeout'     => 8,
                'redirection' => 4,
            ]
        );
        if (! is_wp_error($head)) {
            $statusCode = (int) wp_remote_retrieve_response_code($head);
            if ($statusCode >= 200 && $statusCode < 400) {
                $this->urlReachabilityCache[$url] = true;
                return true;
            }
            if (! in_array($statusCode, [403, 405, 500, 501], true)) {
                $this->urlReachabilityCache[$url] = false;
                return false;
            }
        }

        $probe = wp_remote_get(
            $url,
            [
                'timeout'             => 10,
                'redirection'         => 4,
                'headers'             => ['Range' => 'bytes=0-0'],
                'limit_response_size' => 1,
            ]
        );
        if (is_wp_error($probe)) {
            $this->urlReachabilityCache[$url] = false;
            return false;
        }

        $statusCode = (int) wp_remote_retrieve_response_code($probe);
        $isReachable = $statusCode >= 200 && $statusCode < 400;
        $this->urlReachabilityCache[$url] = $isReachable;
        return $isReachable;
    }
}
