<?php
/**
 * Handles plugin updates via GitHub
 *
 * @package CommonFrameworkPlugin
 */

use function CommonFrameworkPlugin\pretty_dump;

/**
 * Update WordPress plugin from GitHub Private Repository.
 */
class StrategyPluginUpdater {
	/**
	 * The base plugin file
	 *
	 * @var string
	 */
	private $file;

	/**
	 * The plugin detail information
	 *
	 * @var string
	 */
	private $plugin_data;

	/**
	 * The basename of the plugin
	 *
	 * @var string
	 */
	private $basename;

	/**
	 * Whether the plugin is active
	 *
	 * @var bool
	 */
	private $active = false;

	/**
	 * The response data from GitHub
	 *
	 * @var array
	 */
	private $github_response;

	/**
	 * Creates the plugin updater
	 *
	 * @param string $file      The base file for the plugin
	 */
	public function __construct( $file ) {
		$this->file = $file;
		$this->basename = plugin_basename( $this->file );
		require_once COMMON_FRAMEWORK_PLUGIN_INC . 'helpers/helpers.php';
	}

	/**
	 * Init GitHub Plugin Updater.
	 */
	public function init() {
		// add_filter( 'pre_set_site_transient_update_plugins', [ $this, 'modify_transient' ], 10, 1 );
		add_filter( 'site_transient_update_plugins', [ $this, 'modify_transient' ], 10, 1 );
		add_filter( 'http_request_args', [ $this, 'set_header_token' ], 10, 2 );
		add_filter( 'plugins_api', [ $this, 'plugin_popup' ], 10, 3 );
		add_filter( 'upgrader_post_install', [ $this, 'after_install' ], 10, 3 );
	}

	/**
	 * If new version exists, update transient with GitHub info.
	 *
	 * @param object $transient Transient object with plugins information.
	 */
	public function modify_transient( $transient ) {
		if ( empty( $transient->checked ) ) {
			return $transient;
		}

		$this->get_repository_info();
		$this->get_plugin_data();

		if ( version_compare( $this->github_response['tag_name'], $transient->checked[ $this->basename ], 'gt' ) ) {
			$plugin = [
				'url' => $this->plugin_data['PluginURI'],
				'slug' => current( explode( '/', $this->basename ) ),
				'package' => $this->github_response['zipball_url'],
				'new_version' => $this->github_response['tag_name'],
				'update-supported'  => true,
			];

			$transient->response[ $this->basename ] = (object) $plugin;
		}

		return $transient;
	}

	/**
	 * Complete details of new plugin version on popup.
	 *
	 * @param array|false|object $result The result object or array. Default false.
	 * @param string             $action The type of information being requested from the Plugin Installation API.
	 * @param object             $args   Plugin API arguments.
	 */
	public function plugin_popup( $result, $action, $args ) {
		if ( 'plugin_information' !== $action || empty( $args->slug ) ) {
			return false;
		}

		if ( current( explode( '/', $this->basename ) ) == $args->slug ) {
			$this->get_repository_info();
			$this->get_plugin_data();

			if ( null !== $this->github_response && null !== $this->plugin_data ) {
				$plugin = [
					'name' => $this->plugin_data['Name'],
					'slug' => $this->basename,
					'requires' => $this->plugin_data['RequiresWP'],
					'version' => $this->github_response['tag_name'],
					'author' => $this->plugin_data['AuthorName'],
					'author_profile' => $this->plugin_data['AuthorURI'],
					'last_updated' => $this->github_response['published_at'],
					'homepage' => $this->plugin_data['PluginURI'],
					'short_description' => $this->plugin_data['Description'],
					'sections' => [
						'Description' => $this->plugin_data['Description'],
						'Updates' => $this->github_response['body'],
					],
					'download_link' => $this->github_response['zipball_url'],
				];

				return (object) $plugin;
			}
		}

		return $result;
	}

	/**
	 * Active plugin after install new version.
	 *
	 * @param bool  $response       Installation response.
	 * @param array $hook_extra     Extra arguments passed to hooked filters.
	 * @param array $result         Installation result data.
	 */
	public function after_install( $response, $hook_extra, $result ) {
		global $wp_filesystem;

		$install_directory = plugin_dir_path( $this->file );
		$wp_filesystem->move( $result['destination'], $install_directory );
		$result['destination'] = $install_directory;

		if ( $this->active ) {
			activate_plugin( $this->basename );
		}

		return $response;
	}

	/**
	 * GitHub access_token param was deprecated. We need to set header with token for requests.
	 *
	 * @param array  $parsed_args   HTTP request arguments.
	 * @param string $url           The request URL.
	 */
	public function set_header_token( $parsed_args, $url ) {
		$parsed_url = parse_url( $url );

		if ( 'api.github.com' === ( $parsed_url['host'] ?? null ) && isset( $parsed_url['query'] ) ) {
			parse_str( $parsed_url['query'], $query );

			if ( isset( $query['access_token'] ) ) {
				$parsed_args['headers']['Authorization'] = 'token ' . $query['access_token'];

				$this->active = is_plugin_active( $this->basename );
			}
		}

		return $parsed_args;
	}

	/**
	 * Gets repository data from GitHub.
	 */
	private function get_repository_info() {
		if ( null !== $this->github_response || ! defined( 'STRATEGY_UPDATE_TOKEN' ) ) {
			return;
		}

		$args = [
			'method' => 'GET',
			'timeout' => 5,
			'redirection' => 5,
			'httpversion' => '1.0',
			'headers' => [
				'Authorization' => 'token ' . STRATEGY_UPDATE_TOKEN,
			],
			'sslverify' => true,
		];

		$repo = $this->get_repo();
		$branch = $this->get_branch();
		if ( ! $repo || ! $branch ) {
			return;
		}

		$getting_latest = 'master' == $branch || null == $branch;
		$url = 'https://api.github.com/repos/' . $repo . '/releases';
		if ( $getting_latest ) {
			$url .= '/latest';
		}

		$raw_response = wp_remote_get( $url, $args );
		if ( is_wp_error( $raw_response ) ) {
			return;
		}

		$data = json_decode( wp_remote_retrieve_body( $raw_response ), true );

		$response = null;
		if ( $getting_latest ) {
			if ( isset( $data['tag_name'] ) ) {
				$data['tag_name'] = ltrim( $data['tag_name'], 'v' );
			}
			$response = $data;
		} else {
			foreach ( $data as $release ) {
				if ( isset( $release['target_commitish'] ) && $branch == $release['target_commitish'] ) {
					if ( isset( $release['tag_name'] ) ) {
						$release['tag_name'] = ltrim( $release['tag_name'], 'v' );
					}
					$response = $release;
					break;
				}
			}
		}

		if ( ! $response ) {
			return;
		}

		if ( STRATEGY_UPDATE_TOKEN && isset( $response['zipball_url'] ) ) {
			$response['zipball_url'] = add_query_arg( 'access_token', STRATEGY_UPDATE_TOKEN, $response['zipball_url'] );
		}

		$this->github_response = $response;
	}

	/**
	 * Gets plugin data.
	 */
	private function get_plugin_data() {
		if ( null !== $this->plugin_data ) {
			return;
		}

		$this->plugin_data = get_plugin_data( $this->file );
	}

	/**
	 * Gets the repository slug
	 *
	 * @return false|string
	 */
	private function get_repo() {
		return get_option( 'common_framework_plugin_repo' );
	}

	/**
	 * Gets the repository branch
	 *
	 * @return false|string
	 */
	private function get_branch() {
		return get_option( 'common_framework_plugin_branch' );
	}
}
