<?php

declare(strict_types=1);

namespace Signocore\Multilanguage\Application;

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

use stdClass;
use Signocore\Multilanguage\Infrastructure\Traits\Singleton;
use WP_Upgrader;

/**
 * @phpstan-type RemotePluginInfo object{
 *     slug: string,
 *     name: string,
 *     version: string,
 *     download_url: string,
 *     tested: string,
 *     requires: string,
 *     requires_php: string,
 *     author: string,
 *     author_profile: string,
 *     last_updated: string,
 *     rating: int,
 *     num_ratings: int,
 *     active_installs: int,
 *     sections?: object{description?: string, installation?: string, changelog?: string},
 *     banners?: object,
 *     icons?: object,
 *     entitlements?: object{plan: string, product: string, site_url: string},
 *     entitlements_signature?: string
 * }
 */
final class Updater
{
	use Singleton;

	private string $cacheKey = Constants::TRANSIENT_PREFIX . 'plugin_update';

	private bool $forceCheck = false;

	protected function init(): void
	{
		$forceCheckUpdates = (bool) ($_GET['force-check'] ?? false);
		if ($forceCheckUpdates) {
			$this->forceCheck = true;
		}

		add_filter('plugins_api', [$this, 'info'], 20, 3);
		add_filter('pre_set_site_transient_update_plugins', [$this, 'update']);
		add_filter('site_transient_update_plugins', [$this, 'clearUpdateTransient']);
		add_action('upgrader_process_complete', [$this, 'purge'], 10, 2);
		add_action('in_plugin_update_message-' . Constants::$pluginBase, [$this, 'addCustomMessage'], 20, 2);
	}

	/**
	 * @param array<string, mixed> $pluginData
	 */
	public function addCustomMessage(array $pluginData, object $response): void
	{
		if (empty($response->package)) {
			if (empty(get_option(Constants::OPTION_PREFIX . 'license_key'))) {
				return;
			}

			$color = '#dba617';
			$message = __('%sNotice:%s To enable Pro features, please enter a valid license key %shere%s.', 'signocore-multilanguage');
			$licensePage = admin_url('admin.php?page=signocore-ml-settings');

			$notification = sprintf($message, '<span class="dashicons dashicons-warning" style="color: ' . $color . '"></span> <strong>', '</strong>', '<a href="' . esc_url($licensePage) . '">', '</a>');

			echo '<br>' . $notification;
		}
	}

	/**
	 * Fetch remote plugin information from the update server.
	 *
	 * @return RemotePluginInfo|false
	 */
	public function request(): object|false
	{
		if ($this->forceCheck) {
			delete_site_transient($this->cacheKey);
		}

		$cached = get_site_transient($this->cacheKey);
		if ($cached !== false) {
			return $cached === 'error' ? false : $cached;
		}

		$res = wp_remote_get(Constants::UPDATE_URL, [
			'timeout' => 5,
			'headers' => [
				'Accept' => 'application/json',
				'X-Signocore-License' => get_option(Constants::OPTION_PREFIX . 'license_key') ?: 'none',
				'X-Signocore-Email' => get_option(Constants::OPTION_PREFIX . 'license_email') ?: 'none',
				'X-Signocore-Home' => home_url(),
			],
		]);

		if (is_wp_error($res) || wp_remote_retrieve_response_code($res) !== 200) {
			set_site_transient($this->cacheKey, 'error', HOUR_IN_SECONDS);
			return false;
		}

		$body = wp_remote_retrieve_body($res);
		$obj = json_decode($body);
		if (!$obj instanceof stdClass || !isset($obj->version)) {
			set_site_transient($this->cacheKey, 'error', HOUR_IN_SECONDS);
			return false;
		}

		/** @var RemotePluginInfo $obj */ // @phpstan-ignore varTag.nativeType
		$this->processEntitlements($obj);

		set_site_transient($this->cacheKey, $obj, 6 * HOUR_IN_SECONDS);
		return $obj;
	}

	/**
	 * @param false|object|array<string, mixed> $result
	 * @param object{slug: string} $args
	 * @return false|object|array<string, mixed>|stdClass
	 */
	public function info(false|object|array $result, string $action, object $args): false|object|array
	{
		if ('plugin_information' !== $action || Constants::SLUG !== $args->slug) {
			return $result;
		}

		$remote = $this->request();
		if (!$remote) {
			return $result;
		}

		return $this->buildPluginInfo($remote);
	}

	/**
	 * @param RemotePluginInfo $remote
	 */
	private function buildPluginInfo(object $remote, bool $isUpdate = false): stdClass
	{
		$result = new stdClass();
		$result->name = $remote->name;
		$result->slug = $remote->slug;

		if ($isUpdate) {
			$result->new_version = $remote->version;
			$result->package = $remote->download_url;
		} else {
			$result->version = $remote->version;
		}

		$result->tested = $remote->tested;
		$result->requires = $remote->requires;
		$result->author = $remote->author;
		$result->author_profile = $remote->author_profile;
		$result->download_link = $remote->download_url;
		$result->trunk = $remote->download_url;
		$result->requires_php = $remote->requires_php;
		$result->last_updated = $remote->last_updated;
		$result->rating = $remote->rating;
		$result->num_ratings = $remote->num_ratings;
		$result->active_installs = (int) (ceil($remote->active_installs / 100) * 100);
		$result->sections = [
			'description' => $remote->sections->description ?? '',
			'installation' => $remote->sections->installation ?? '',
			'changelog' => $remote->sections->changelog ?? '',
		];

		if (!empty($remote->banners)) {
			$result->banners = (array) $remote->banners;
		}

		if (!empty($remote->icons)) {
			$result->icons = (array) $remote->icons;
		}

		return $result;
	}

	/**
	 * @param stdClass|false $transient
	 * @return stdClass|false
	 */
	public function update(object|false $transient): stdClass|false
	{
		if (empty($transient->checked)) {
			return $transient;
		}

		$remote = $this->request();
		if (
			!empty($remote) && !empty($remote->version)
			&& version_compare(Constants::$pluginVersion, $remote->version, '<')
			&& version_compare($remote->requires, get_bloginfo('version'), '<=')
			&& version_compare($remote->requires_php, PHP_VERSION, '<=')
		) {
			$transient->response[Constants::$pluginBase] = $this->buildPluginInfo($remote, true);
		}

		return $transient;
	}

	/**
	 * @param array<string, mixed> $options
	 */
	public function purge(WP_Upgrader $upgrader, array $options): void
	{
		if (
			$options['action'] === 'update'
			&& $options['type'] === 'plugin'
			&& !empty($options['plugins'])
			&& in_array(Constants::$pluginBase, $options['plugins'], true)
		) {
			delete_site_transient($this->cacheKey);
			LicenseManager::load()->invalidate();
		}
	}

	/**
	 * @param RemotePluginInfo $remote
	 */
	private function processEntitlements(object $remote): void
	{
		if (!isset($remote->entitlements, $remote->entitlements_signature)) {
			LicenseManager::load()->purge();
			return;
		}

		$entitlements = (array) $remote->entitlements;
		$signature = (string) $remote->entitlements_signature;

		if (
			empty($entitlements['plan']) || !is_string($entitlements['plan']) ||
			empty($entitlements['product']) || !is_string($entitlements['product']) ||
			empty($entitlements['site_url']) || !is_string($entitlements['site_url']) ||
			($signature === '' || $signature === '0')
		) {
			return;
		}

		/** @var array{plan: string, product: string, site_url: string} $entitlements */
		LicenseManager::load()->storeEntitlements($entitlements, $signature);
	}

	public function clearUpdateTransient(object|false $transient): object|false
	{
		if (
			!is_object($transient) ||
			empty($transient->response) ||
			!isset($transient->response[Constants::$pluginBase])
		) {
			return $transient;
		}

		$update = $transient->response[Constants::$pluginBase];
		$new = $update->new_version ?? null;

		if (!$new || !is_string($new)) {
			return $transient;
		}

		if (version_compare(Constants::$pluginVersion, $new, '>=')) {
			unset($transient->response[Constants::$pluginBase]);
		}

		return $transient;
	}
}
