<?php

use MemberPress\GroundLevel\Container\Concerns\InjectsDependencies;
use MemberPress\GroundLevel\Mothership\AbstractPluginConnection;
use MemberPress\GroundLevel\Mothership\Api\Request\Products;
use MemberPress\GroundLevel\Mothership\Credentials;
use MemberPress\GroundLevel\Mothership\Manager\LicenseManager;

if (!defined('ABSPATH')) {
    die('You are not allowed to call this page directly.');
}

/**
 * Handles plugin updates.
 *
 * Separated from MeprUpdateCtrl so that WP.org-hosted editions can exclude this file
 * (WP.org does not permit custom plugin updaters) while retaining license management.
 *
 * @property MemberPress\GroundLevel\Mothership\AbstractPluginConnection       $plugin
 * @property MemberPress\GroundLevel\Mothership\Manager\LicenseManager         $license_manager
 * @property MemberPress\GroundLevel\Mothership\Transients\ActivationTransient $activation
 * @property MemberPress\GroundLevel\Mothership\Credentials                    $credentials
 */
class MeprPluginUpdaterCtrl extends MeprBaseCtrl
{
    use InjectsDependencies;

    /**
     * Define the dependencies to be injected.
     */
    protected function inject(): array
    {
        return [
            'plugin'          => MemberPress\GroundLevel\Mothership\AbstractPluginConnection::class,
            'license_manager' => MemberPress\GroundLevel\Mothership\Manager\LicenseManager::class,
            'activation'      => MemberPress\GroundLevel\Mothership\Transients\ActivationTransient::class,
            'credentials'     => MemberPress\GroundLevel\Mothership\Credentials::class,
        ];
    }

    /**
     * Load hooks for plugin update mechanisms.
     *
     * @return void
     */
    public function load_hooks()
    {
        add_filter('auto_update_plugin', [$this, 'automatic_updates'], 10, 2);
        add_filter('plugins_api', 'MeprPluginUpdaterCtrl::plugin_info', 1, 3);
        add_filter($this->plugin->pluginId . '_plugin_information', [$this, 'plugin_information'], 10, 2);
        add_action('in_plugin_update_message-memberpress/memberpress.php', 'MeprPluginUpdaterCtrl::check_incorrect_edition');
        add_action('wp_ajax_mepr_edge_updates', 'MeprPluginUpdaterCtrl::mepr_edge_updates');
        add_action('admin_init', [$this, 'migrate_license_transient']);
    }

    /**
     * One-time migration from the legacy `mepr_license_info` site transient
     * to ActivationTransient. No-op once the legacy entry is gone.
     *
     * @return void
     */
    public function migrate_license_transient(): void
    {
        if (false === get_site_transient('mepr_license_info')) {
            return;
        }

        if (empty($this->credentials->getLicenseKey())) {
            delete_site_transient('mepr_license_info');
            return;
        }

        $this->license_manager->syncActivationTransient();

        if ($this->activation->exists()) {
            delete_site_transient('mepr_license_info');
        }
    }

    /**
     * Filters the auto update plugin routine to allow MemberPress to be
     * automatically updated.
     *
     * @todo Migrate each add-on off this central handler. Once all add-ons
     * register through ground-level-mothership UpdateService, they should
     * receive automatic updates out of the box and this method could be
     * retired.
     *
     * @param boolean $update Flag to update the plugin or not.
     * @param object  $item   Update data about a specific plugin.
     *
     * @return boolean The new update state.
     */
    public function automatic_updates($update, $item)
    {
        // If this is multisite and is not on the main site, return early.
        if (is_multisite() && ! is_main_site()) {
            return $update;
        }

        // If we don't have everything we need, return early.
        if (! isset($item->new_version) || ! isset($item->slug) || ! isset($item->plugin)) {
            return $update;
        }

        // Now handled by ground-level-mothership.
        if ('memberpress' === $item->slug) {
            return $update;
        }

        // If the plugin isn't ours, return early.
        if (0 !== strpos($item->slug, 'memberpress-')) {
            return $update;
        }

        // If the plugin folder is a git repo, return early.
        if (@is_dir(WP_PLUGIN_DIR . '/' . dirname($item->plugin) . '/.git')) {
            return $update;
        }

        $policy = $this->plugin->automaticUpdates();

        if (AbstractPluginConnection::AUTOMATIC_UPDATE_NONE === $policy) {
            return false;
        }

        if (AbstractPluginConnection::AUTOMATIC_UPDATE_ALL === $policy) {
            return true;
        }

        // {@see GroundLevel\Mothership\AbstractPluginConnection::AUTOMATIC_UPDATE_MINOR} policy: allow minor
        // and patch updates, block major version bumps.
        $plugin_file       = WP_PLUGIN_DIR . '/' . $item->plugin;
        $plugin_data       = get_file_data($plugin_file, ['Version' => 'Version']);
        $installed_version = $plugin_data['Version'] ?: '0';
        $new_version       = $item->new_version ?? '0';
        $current_major     = (int) explode('.', $installed_version)[0];
        $new_major         = (int) explode('.', $new_version)[0];

        return $new_major === $current_major;
    }

    /**
     * Return up-to-date plugin information for the add-ons.
     *
     * @todo Migrate each add-on off this central handler. Add-ons should provide
     *       their own metadata by hooking into `{$slug}_plugin_information`.
     *       Once every add-on owns its metadata, this method can be removed.
     *
     * @param  object $api    The API object.
     * @param  string $action The action.
     * @param  array  $args   The arguments.
     * @return object The API object.
     */
    public static function plugin_info($api, $action, $args)
    {
        global $wp_version;

        if (!isset($action) || $action !== 'plugin_information') {
            return $api;
        } elseif (isset($args->slug) && preg_match('#^(affiliate-royale)#', $args->slug)) {
            // If AR is installed we allow it to take care of updates.
            if (is_plugin_active('affiliate-royale/affiliate-royale.php')) {
                return $api;
            }
        } elseif (isset($args->slug) && !preg_match('#^(memberpress|affiliate-royale)#', $args->slug)) {
            return $api;
        }

        // Now handled by {@see self::plugin_information}.
        if ($args->slug === 'memberpress') {
            return $api;
        }

        $mothership_slug = $args->slug;
        $faq             = 'You can read more about MemberPress Add-Ons by visiting <a href="' . esc_url(MeprUtils::get_link_url('docs_addons')) . '">the user manual</a>.';
        $addon_info      = self::mepr_addon_info($args->slug);
        if (!empty($addon_info)) {
            $display_name = $addon_info['Name'];
            $description  = $addon_info['Description'];
        } else {
            $display_name = 'MemberPress Add-On';
            $description  = 'MemberPress Add-On';
        }
        if (
            in_array($display_name, [
                'MemberPress Courses',
                'MemberPress Downloads',
                'MemberPress Developer Tools',
                'MemberPress Corporate Accounts',
                'MemberPress PDF Invoice',
                'MemberPress + BuddyPress Integration',
            ], true)
        ) {
            $plugin_slug = ($args->slug === 'memberpress-courses') ? 'memberpress-courses' : str_replace('memberpress', '', $addon_info['TextDomain']);
            $plugin_url  = rtrim(MeprUtils::get_link_url('addons'), '/') . '/' . rawurlencode($plugin_slug) . '/';
            $changelog   = sprintf(
                // Translators: %1$s: the add-on name, %2$s: change log link open tag, %3$s: close link tag.
                esc_html__('You can read more about the latest changes to %1$s by visiting %2$sthe change log%3$s.', 'memberpress'),
                esc_html($display_name),
                '<a href="' . esc_url($plugin_url) . '">',
                '</a>'
            );
        }

        $mepr_options = MeprOptions::fetch();

        $mothership_args = [];
        if ($mepr_options->edge_updates || (defined('MEMBERPRESS_EDGE') && MEMBERPRESS_EDGE)) {
            $mothership_args['edge'] = 'true';
        }
        $download_url = '';
        $products     = memberpress()->container()->get(Products::class);
        $prerelease   = memberpress()->container()->get(AbstractPluginConnection::class)->allowPrereleaseVersions();

        $check = $products->getVersionCheck(
            $mothership_slug,
            [
                'prerelease' => $prerelease,
                '_embed'     => 'version',
            ]
        );


        if ($check->isSuccess()) {
            $version      = $check->getEmbed('version');
            $download_url = $version->url ?? '';
            $version_info = [
                'version'      => $version->number ?? '',
                'version_date' => $version->created_at ?? '',
            ];
        } else {
            MeprUtils::debug_log($check->getMessage());
            $version_info = [
                'version'      => '',
                'version_date' => '',
            ];
        }

        $plugin_info = [
            'slug'           => $args->slug,
            'name'           => $display_name,
            'author'         => '<a href="https://blairwilliams.com/">Caseproof, LLC</a>',
            'author_profile' => 'https://blairwilliams.com/',
            'contributors'   => [
                [
                    'display_name' => 'Caseproof',
                    'profile'      => '',
                    'avatar'       => '',
                ],
            ],
            'homepage'       => MeprUtils::get_link_url('home'),
            'version'        => $version_info['version'],
            'requires'       => '3.8',
            'requires_php'   => '5.3',
            'tested'         => $wp_version,
            'compatibility'  => [$wp_version => [$wp_version => [100, 0, 0]]], // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
            'last_updated'   => $version_info['version_date'],
            'download_link'  => $download_url,
            'sections'       => [
                'description' => $description,
                'faq'         => $faq,
            ],
            'banners'        => [
                'low'  => MEPR_IMAGES_URL . '/banner-772x250.png',
                'high' => MEPR_IMAGES_URL . '/banner-1544x500.png',
            ],
        ];

        if (isset($changelog)) {
            $plugin_info['sections']['changelog'] = $changelog;
        }

        return (object) $plugin_info;
    }

    /**
     * Constructs the plugin information object.
     *
     * @param  object $plugin_info The plugin information object.
     * @return object The updated plugin information object
     */
    public function plugin_information($plugin_info): object
    {
        global $wp_version;

        $plugin_info->name           = MEPR_DISPLAY_NAME;
        $plugin_info->author         = '<a href="https://blairwilliams.com/">Caseproof, LLC</a>';
        $plugin_info->author_profile = 'https://blairwilliams.com/';
        $plugin_info->homepage       = MeprUtils::get_link_url('home');
        $plugin_info->requires       = memberpress()->config()->getRequiresWP();
        $plugin_info->requires_php   = memberpress()->config()->getRequiresPHP();
        $plugin_info->tested         = $wp_version; // TODO: get real value from plugin headers.
        $plugin_info->compatibility  = [$wp_version => [$wp_version => [100, 0, 0]]]; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited

        $plugin_info->contributors = [
            [
                'display_name' => __('Caseproof', 'memberpress'),
                'profile'      => '',
                'avatar'       => '',
            ],
        ];

        $plugin_info->banners = [
            'low'  => MEPR_IMAGES_URL . '/banner-772x250.png',
            'high' => MEPR_IMAGES_URL . '/banner-1544x500.png',
        ];

        $description = <<<HTML
        <h3>The "All-In-One" Membership Plugin for WordPress</h3>
        <p>
          MemberPress will help you build astounding WordPress membership sites, accept credit cards securely, control who sees your content, and sell digital downloads ... all without the difficult setup.
        </p>
        <p>
          MemberPress will help you confidently create, manage and track membership subscriptions and sell digital download products. In addition to these features, MemberPress will allow you manage your members by granting and revoking their access to posts, pages, videos, categories, tags, feeds, communities, digital files and more based on what memberships they belong to.
        </p>
        <p>
          With MemberPress you’ll be able to create powerful and compelling WordPress membership sites that leverage all of the great features of WordPress, WordPress plugins and other 3rd party services including content management, forums, and social communities.
        </p>
        HTML;

        $faq = 'You can read more about how to use MemberPress by visiting <a href="'
            . esc_url(MeprUtils::get_link_url('docs'))
            . '">the user manual</a>.';

        $changelog = 'You can read more about the latest changes to MemberPress by visiting <a href="'
            . esc_url(MeprUtils::get_link_url('change_log'))
            . '">the change log</a>';

        $plugin_info->sections['description'] = $description;
        $plugin_info->sections['faq']         = $faq;
        $plugin_info->sections['changelog']   = $changelog;

        return $plugin_info;
    }

    /**
     * Get the addon info.
     *
     * @param  string $slug The slug.
     * @return array The addon info.
     */
    private static function mepr_addon_info($slug)
    {
        static $curr_plugins;

        if (!isset($curr_plugins)) {
            if (!function_exists('get_plugins')) {
                require_once(ABSPATH . '/wp-admin/includes/plugin.php');
            }
            $curr_plugins = get_plugins();
            wp_cache_delete('plugins', 'plugins');
        }

        if (isset($curr_plugins[$slug . '/main.php'])) {
            return $curr_plugins[$slug . '/main.php'];
        } elseif (isset($curr_plugins[$slug . "/{$slug}.php"])) {
            return $curr_plugins[$slug . "/{$slug}.php"];
        }

        return '';
    }

    /**
     * Handle edge updates for the MemberPress plugin.
     *
     * @return void
     */
    public static function mepr_edge_updates()
    {
        if (!MeprUtils::is_mepr_admin() || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['wpnonce'] ?? '')), 'wp-edge-updates')) {
            die(json_encode(['error' => __('You do not have access.', 'memberpress')]));
        }

        if (!isset($_POST['edge'])) {
            die(json_encode(['error' => __('Edge updates couldn\'t be updated.', 'memberpress')]));
        }

        $mepr_options               = MeprOptions::fetch();
        $mepr_options->edge_updates = ($_POST['edge'] === 'true');
        $mepr_options->store(false);

        // Force a fresh update check.
        delete_site_transient('update_plugins');

        die(json_encode(['state' => ($mepr_options->edge_updates ? 'true' : 'false')]));
    }

    /**
     * Check if the incorrect edition of MemberPress is installed.
     *
     * @return void
     */
    public static function check_incorrect_edition()
    {
        $license_manager = memberpress()->container()->get(LicenseManager::class);
        if ($license_manager->isIncorrectEditionInstalled()) {
            printf(
                // Translators: %1$s: open link tag, %2$s: close link tag.
                ' <strong>' . esc_html__('To restore automatic updates, %1$sinstall the correct edition%2$s of MemberPress.', 'memberpress') . '</strong>',
                sprintf('<a href="%s">', esc_url(admin_url('admin.php?page=memberpress-options#mepr-license'))),
                '</a>'
            );
        }
    }
}
