<?php

/**
 * @author WP Cloud Plugins
 * @copyright Copyright (c) 2023, WP Cloud Plugins
 *
 * @since       2.0
 * @see https://www.wpcloudplugins.com
 */

namespace TheLion\ShareoneDrive;

defined('ABSPATH') || exit;

class License
{
    private const MISSING_PAYLOAD_RECOVERY_MAX_ATTEMPTS = 2;

    public static $license_code;
    public static $payload_recovery_attempted = [];

    /**
     * Register license hooks and process activation callbacks.
     */
    public static function init()
    {
        // Health Check test
        add_filter('site_status_tests', [__CLASS__, 'add_health_tests']);

        add_action('wp_ajax_shareonedrive-license', [__CLASS__, 'ajax_call']);

        if (isset($_REQUEST['purchase_code'], $_REQUEST['plugin_id']) && (Core::$plugin_id == $_REQUEST['plugin_id'])) {
            $license_code = sanitize_key($_REQUEST['purchase_code']);
            $activation_error = null;

            self::save($license_code, false);

            if (false === self::sync_license_data($license_code, true) && defined('LICENSE_ERROR')) {
                $activation_error = LICENSE_ERROR;
            }

            echo '<script type="text/javascript">';

            if (!empty($activation_error)) {
                echo 'localStorage.setItem("wpcp_activation_error", '.wp_json_encode($activation_error).');';
            }

            echo 'localStorage.setItem("wpcp_refreshParent", "true"); window.close();</script>';

            exit;
        }
    }

    /**
     * Save the active license code in the plugin settings.
     *
     * @param string $license_code      license code to store
     * @param bool   $clear_stored_data whether stored payload for this code should be cleared
     */
    public static function save($license_code, $clear_stored_data = true)
    {
        $license_code = sanitize_key($license_code);
        Settings::save('purchase_code', $license_code);

        if (self::is_global()) {
            update_site_option('shareonedrive_purchaseid', $license_code);
        }

        if ($clear_stored_data && !empty($license_code)) {
            self::delete_stored_license_data($license_code);
        }

        self::$license_code = trim(apply_filters('shareonedrive_purchasecode', $license_code ?? ''));
    }

    /**
     * Retrieve the active license code.
     *
     * @return string
     */
    public static function get()
    {
        if (null !== self::$license_code) {
            return self::$license_code;
        }

        $license_code = Settings::get('purchase_code');

        if (self::is_global()) {
            $site_license_code = get_site_option('shareonedrive_purchaseid');

            if (!empty($site_license_code)) {
                $license_code = $site_license_code;
            }
        }

        self::$license_code = trim(apply_filters('shareonedrive_purchasecode', $license_code ?? ''));

        return self::$license_code;
    }

    /**
     * Retrieve the locally stored license payload without contacting the license server.
     *
     * @param null|string $license_code optional specific license code
     *
     * @return array|false
     */
    public static function get_stored_license_data($license_code = null)
    {
        $license_code = empty($license_code) ? self::get() : sanitize_key($license_code);

        if (empty($license_code)) {
            return false;
        }

        $stored_license = get_site_option(self::get_storage_key($license_code));

        if (!is_array($stored_license)) {
            return self::maybe_recover_stored_license_payload($license_code);
        }

        $license_data = $stored_license['license_data'] ?? $stored_license;

        if (!self::has_valid_stored_payload($license_data)) {
            return self::maybe_recover_stored_license_payload($license_code);
        }

        return $license_data;
    }

    /**
     * Sync license data from the remote server and persist it locally on success.
     *
     * @param null|string $license_code         optional specific license code
     * @param bool        $activate_if_possible whether the remote call may activate the license
     *
     * @return array|false
     */
    public static function sync_license_data($license_code = null, $activate_if_possible = false)
    {
        $license_code = empty($license_code) ? self::get() : sanitize_key($license_code);

        if (empty($license_code)) {
            self::set_license_error(esc_html__('No license code found. Please activate the license first.', 'wpcloudplugins'));

            return false;
        }

        $response = wp_remote_get('https://www.wpcloudplugins.com/updates_v2/?action=get_license&slug=share-one-drive&purchase_code='.$license_code.'&plugin_id='.Core::$plugin_id.'&force=1&activate='.(int) $activate_if_possible.'&installed_version='.SHAREONEDRIVE_VERSION.'&siteurl='.self::get_home_url());
        $response_code = wp_remote_retrieve_response_code($response);

        if (empty($response_code)) {
            if (is_wp_error($response)) {
                $error_msg = 'Cannot validate the License. Check <strong>[Tools] -> [Site Health]</strong> for communication problems if the problem persists. <small>'.$response->get_error_message().'</small>';
                self::set_license_error($error_msg, ['response' => $response->get_error_message()]);
            } else {
                self::set_license_error(esc_html__('Cannot validate the License because the license server did not return a valid response.', 'wpcloudplugins'));
            }

            return false;
        }

        if ($response_code >= 500) {
            self::set_license_error(
                esc_html__('Cannot validate the License because the license server is currently unavailable. The existing stored license data has been kept unchanged.', 'wpcloudplugins')
            );

            return false;
        }

        if (in_array($response_code, [401, 402, 403, 406], true)) {
            $result = json_decode(wp_remote_retrieve_body($response), true);
            $error_message = (is_array($result) && !empty($result['message'])) ? $result['message'] : esc_html__('Cannot validate the License. Check the code or try to activate via Envato Market.', 'wpcloudplugins');

            self::set_license_error($error_message, ['response' => $error_message]);

            return false;
        }

        if (200 !== $response_code) {
            self::set_license_error(
                esc_html__('Cannot validate the License because the license server returned an unexpected response. The existing stored license data has been kept unchanged.', 'wpcloudplugins'),
                ['response_code' => $response_code]
            );

            return false;
        }

        $license_data = json_decode(wp_remote_retrieve_body($response), true);

        if (!is_array($license_data)) {
            self::set_license_error(
                esc_html__('Cannot validate the License because the license server returned malformed data. The existing stored license data has been kept unchanged.', 'wpcloudplugins')
            );

            return false;
        }

        if (!self::has_valid_stored_payload($license_data)) {
            $error_message = !empty($license_data['message']) ? $license_data['message'] : esc_html__('Cannot validate the License. Check the code or try to activate via Envato Market.', 'wpcloudplugins');
            self::set_license_error($error_message, ['response' => $error_message]);

            return false;
        }

        self::store_license_data($license_code, $license_data);

        return $license_data;
    }

    public static function is_valid()
    {
        return !empty(self::get());
    }

    /**
     * Handle AJAX actions for activate, sync, get-details, and deactivate.
     */
    public static function ajax_call()
    {
        // Check AJAX call
        check_ajax_referer('shareonedrive-admin-action');

        $action = isset($_POST['type']) ? sanitize_key($_POST['type']) : 'get-details';
        $license_code = isset($_POST['license_code']) ? sanitize_key($_POST['license_code']) : self::get();

        $return = [
            'license_code' => $license_code,
            'valid' => false,
            'support_package' => false,
            'error_message' => '',
            'data' => [],
        ];

        if ('deactivate' === $action) {
            self::_revoke();
            self::deactivate_local_license();
            echo \json_encode($return);

            exit;
        }

        if ('activate' === $action) {
            $license_data = self::sync_license_data($license_code, true);

            if (false !== $license_data) {
                self::save($license_code, false);
            }
        } elseif ('sync' === $action) {
            $license_data = self::sync_license_data($license_code, false);
        } else {
            $license_data = self::get_stored_license_data($license_code);
        }

        if (false === $license_data) {
            if ('get-details' === $action && self::is_payload_missing($license_code)) {
                $return['error_message'] = esc_html__('This site is activated, but the local license payload is missing. The plugin tried to restore it automatically. Please click Sync to fetch the license payload manually.', 'wpcloudplugins');
            } elseif ('get-details' === $action) {
                $return['error_message'] = esc_html__('No stored license data found. Please click Sync to refresh the license data.', 'wpcloudplugins');
            } elseif ('sync' === $action) {
                $return['error_message'] = defined('LICENSE_ERROR') ? LICENSE_ERROR : 'Cannot sync the License right now. The currently stored license data has been kept unchanged.<br/> <small>Check <strong>[Tools] -> [Site Health]</strong> for communication problems if the problem persists.</small>';
            } else {
                $return['error_message'] = defined('LICENSE_ERROR') ? LICENSE_ERROR : 'Cannot validate the License. Check the code or try to activate via Envato Market.<br/> <small>Check <strong>[Tools] -> [Site Health]</strong> for communication problems if the problem persists.</small>';
            }

            echo \json_encode($return);

            exit;
        }

        $return['valid'] = true;
        $return['data'] = $license_data;
        $supported_until_str = isset($license_data['supported_until']) ? date_i18n(get_option('date_format'), strtotime($license_data['supported_until'])) : esc_html__('today', 'wpcloudplugins');
        $return['supported_until_str'] = sprintf(esc_html__('Support till %s', 'wpcloudplugins'), $supported_until_str);
        unset($return['data']['appdata']);

        if (isset($license_data['supported_until']) && $license_data['supported_until'] < date('c')) {
            $return['error_message'] = sprintf(esc_html__('The support period for this license has expired on %s.', 'wpcloudplugins'), $supported_until_str);
        } else {
            $return['support_package'] = true;
        }

        echo \json_encode($return);

        exit;
    }

    public static function reset()
    {
        $license_code = self::get();

        if (empty($license_code)) {
            return false;
        }

        self::delete_stored_license_data($license_code);

        return true;
    }

    public static function add_health_tests($tests)
    {
        $tests['direct']['wpcp_license_server'] = [
            'label' => __('Communication WP Cloud Plugin license server'),
            'test' => [__CLASS__, 'test_license_server'],
        ];

        return $tests;
    }

    public static function test_license_server()
    {
        $result = [
            'label' => __('The WP Cloud Plugins are able to communicate with their licence server.', 'wpcloudplugins'),
            'status' => 'good',
            'badge' => [
                'label' => 'WP Cloud Plugins',
                'color' => 'green',
            ],
            'description' => sprintf(
                '<p>%s</p>',
                __('To use the WP Cloud Plugins, you need a valid licence. The plugin contacts the licence server during activation, manual sync actions, and update checks.', 'wpcloudplugins')
            ),
            'actions' => '',
            'test' => 'wpcp_license_server',
        ];

        $error = false;

        try {
            $response = wp_remote_get('https://www.wpcloudplugins.com/updates_v2/');
        } catch (\Exception $ex) {
            $error = true;
            $message = $ex->getMessage();
        }

        if (is_wp_error($response)) {
            $error = true;
            $message = $response->get_error_message();
        }

        if ($error) {
            $result['status'] = 'critical';
            $result['label'] = __('WP Cloud Plugins cannot communicate with the licence server', 'wpcloudplugins');
            $result['badge']['color'] = 'red';
            $result['description'] = sprintf(
                '<p>%s</p><h3>Error information</h3><code>%s</code>',
                __('Your website cannot establish a secure connection with the licensing server. This will cause their plugins to stop working because the licence cannot be validated.', 'wpcloudplugins'),
                htmlentities($message, ENT_QUOTES | ENT_HTML401)
            );
            $result['actions'] = sprintf(
                '<p><a href="%s" target="_blank">%s <span class="dashicons dashicons-external"> </span></a> - <a href="%s" target="_blank">%s <span class="dashicons dashicons-external"</a></p>',
                esc_url('https://www.google.com/search?q=WordPress+wp_remote_get+'.urlencode(htmlentities($message, ENT_QUOTES | ENT_HTML401))),
                __('Find a solution'),
                esc_url('https://support.wpcloudplugins.com/hc/en-us/articles/201845893'),
                __('Contact Support')
            );
        }

        return $result;
    }

    public static function get_home_url()
    {
        if (!function_exists('is_plugin_active_for_network')) {
            require_once ABSPATH.'/wp-admin/includes/plugin.php';
        }

        // Load the home url as set in the WordPress Database without applying filters.
        $home_url = self::get_option_without_filters('option_home', function () {
            return _config_wp_home(get_option('home'));
        });
        if (empty($home_url)) {
            $home_url = get_bloginfo('url');
        }

        $home_url = set_url_scheme($home_url, 'admin');

        if (false === strpos($home_url, '?')) {
            $home_url .= '?';
        }

        if (false === is_multisite()) {
            return urlencode($home_url).'&multisite=0&network_enabled=0&network_wide=0';
        }
        if (!is_plugin_active_for_network(SHAREONEDRIVE_SLUG)) {
            return urlencode($home_url).'&multisite=1&network_enabled=0&network_wide=0';
        }

        if (self::is_global()) {
            // Load the network home url as set in the WordPress Database without applying filters.
            $home_url = self::get_option_without_filters('network_home_url', function () {
                return network_home_url();
            });

            if (empty($home_url)) {
                $home_url = get_bloginfo('url');
            }

            if (false === strpos($home_url, '?')) {
                $home_url .= '?';
            }

            return urlencode($home_url).'&multisite=1&network_enabled=1&network_wide=1';
        }

        return urlencode($home_url).'&multisite=1&network_enabled=1&network_wide=0';
    }

    /**
     * Load an option as set in the WordPress Database without applying filters.
     *
     * @param string   $hook     filter name
     * @param callable $callback function execited while filter disabled
     *
     * @return mixed value returned by $callback
     */
    public static function get_option_without_filters($hook, $callback)
    {
        global $wp_filter;

        $wp_hook = null;
        // Remove and cache the filter
        if (isset($wp_filter[$hook]) && $wp_filter[$hook] instanceof \WP_Hook) {
            $wp_hook = $wp_filter[$hook];
            unset($wp_filter[$hook]);
        }

        $retval = call_user_func($callback);

        // Add back the filter
        if ($wp_hook instanceof \WP_Hook) {
            $wp_filter[$hook] = $wp_hook;
        }

        return $retval;
    }

    public static function requires_single()
    {
        if (!function_exists('is_plugin_active_for_network')) {
            require_once ABSPATH.'/wp-admin/includes/plugin.php';
        }

        if (false === is_multisite()) {
            return true;
        }
        if (!is_plugin_active_for_network(SHAREONEDRIVE_SLUG)) {
            return true;
        }

        if (self::is_global()) {
            return true;
        }

        return false;
    }

    public static function is_global()
    {
        if (!function_exists('is_plugin_active_for_network')) {
            require_once ABSPATH.'/wp-admin/includes/plugin.php';
        }

        $network_settings = get_site_option(Settings::$db_network_key, []);
        $network_wide = isset($network_settings['network_wide']) && ('Yes' === $network_settings['network_wide']);

        return is_multisite() && is_plugin_active_for_network(SHAREONEDRIVE_SLUG) && $network_wide;
    }

    public static function mask_code($license_code)
    {
        // Split the UUID into its components
        $parts = explode('-', $license_code);

        // Mask the middle parts
        for ($i = 1; $i < count($parts) - 1; ++$i) {
            $parts[$i] = str_repeat('*', strlen($parts[$i]));
        }

        // Join the parts back together
        return implode('-', $parts);
    }

    /**
     * Build the site option key used to store license payloads.
     *
     * @param string $license_code license code
     *
     * @return string
     */
    private static function get_storage_key($license_code)
    {
        return 'wpcp_license_'.sanitize_key($license_code);
    }

    /**
     * Persist the full remote license payload in the database.
     *
     * @param string $license_code license code
     * @param array  $license_data remote payload
     */
    private static function store_license_data($license_code, array $license_data)
    {
        update_site_option(
            self::get_storage_key($license_code),
            [
                'license_data' => $license_data,
                'version' => \SHAREONEDRIVE_VERSION,
                'synced_at' => time(),
            ]
        );
    }

    /**
     * Remove the stored payload for a specific license code.
     *
     * @param string $license_code license code
     */
    private static function delete_stored_license_data($license_code)
    {
        delete_site_option(self::get_storage_key($license_code));
        wp_cache_delete(self::get_storage_key($license_code), 'site-options');
    }

    /**
     * Determine whether the active license code exists but the cached payload is missing.
     *
     * @param null|string $license_code optional specific license code
     *
     * @return bool
     */
    private static function is_payload_missing($license_code = null)
    {
        $license_code = empty($license_code) ? self::get() : sanitize_key($license_code);

        if (empty($license_code)) {
            return false;
        }

        $stored_license = get_site_option(self::get_storage_key($license_code));

        if (!is_array($stored_license)) {
            return true;
        }

        $license_data = $stored_license['license_data'] ?? $stored_license;

        return !self::has_valid_stored_payload($license_data);
    }

    /**
     * Try to restore missing cached payload data for legacy activated licenses.
     *
     * This recovery path is intentionally limited to avoid turning normal runtime
     * back into an unbounded live license check loop.
     *
     * @param string $license_code license code
     *
     * @return array|false
     */
    private static function maybe_recover_stored_license_payload($license_code)
    {
        if (empty($license_code)) {
            return false;
        }

        $recovery_key = self::get_payload_recovery_key($license_code);
        $attempt_count = (int) get_site_option($recovery_key, 0);

        if (isset(self::$payload_recovery_attempted[$license_code])) {
            return false;
        }

        if ($attempt_count >= self::MISSING_PAYLOAD_RECOVERY_MAX_ATTEMPTS) {
            return false;
        }

        self::$payload_recovery_attempted[$license_code] = true;
        update_site_option($recovery_key, $attempt_count + 1);

        return self::sync_license_data($license_code, false);
    }

    /**
     * Build the site option key used to track limited automatic payload recovery.
     *
     * @param string $license_code license code
     *
     * @return string
     */
    private static function get_payload_recovery_key($license_code)
    {
        return 'wpcp_license_recovery_'.sanitize_key($license_code);
    }

    /**
     * Check whether a stored or remote payload contains the required app credentials.
     *
     * @param mixed $license_data license payload
     *
     * @return bool
     */
    private static function has_valid_stored_payload($license_data)
    {
        return is_array($license_data) && !empty($license_data['appdata']['key']) && !empty($license_data['appdata']['secret']);
    }

    /**
     * Store an activation or sync error in a way the existing UI can reuse.
     *
     * @param string     $message error message
     * @param null|array $context optional log context
     */
    private static function set_license_error($message, ?array $context = null)
    {
        if (!defined('LICENSE_ERROR')) {
            define('LICENSE_ERROR', $message);
        }

        if (!empty($context)) {
            Helpers::log_error('Cannot validate the License', 'License', $context, __LINE__);
        }
    }

    /**
     * Clear the active local license state without touching linked authorizations.
     */
    private static function deactivate_local_license()
    {
        $license_code = self::get();

        if (!empty($license_code)) {
            self::delete_stored_license_data($license_code);
            delete_site_option(self::get_payload_recovery_key($license_code));
            wp_cache_delete(self::get_payload_recovery_key($license_code), 'site-options');
            unset(self::$payload_recovery_attempted[$license_code]);
        }

        Settings::save('purchase_code', '');

        if (self::is_global()) {
            update_site_option('shareonedrive_purchaseid', '');
        }

        self::$license_code = '';
    }

    private static function _revoke()
    {
        $license_code = self::get();

        if (empty($license_code)) {
            return;
        }

        $cached_data = get_site_option(self::get_storage_key($license_code));

        if (!empty($cached_data) && isset($cached_data['license_data']['secret'])) {
            $secret = $cached_data['license_data']['secret'];
            wp_remote_get('https://www.wpcloudplugins.com/updates_v2/?action=deactivate_license&secret='.$secret.'&slug=share-one-drive&purchase_code='.$license_code.'&plugin_id='.Core::$plugin_id.'&installed_version='.SHAREONEDRIVE_VERSION.'&siteurl='.self::get_home_url());
        }

        Processor::reset_complete_cache();
    }
}
