<?php

namespace Nextend\SmartSlider3\Platform\WordPress\Admin\Pro;

use Nextend\Framework\Pattern\SingletonTrait;
use Nextend\SmartSlider3\Application\Model\ModelLicense;
use Nextend\SmartSlider3\SmartSlider3Info;
use WP_Error;

class WordPressUpdate {

    use SingletonTrait;

    const UPDATE_CACHE_KEY = 'ss3_pro_plugin_information';
    const CLEAR_CACHE_KEY = 'ss3_pro_clear_update_cache';
    const UPDATE_SUCCESS_CACHE_TTL = 30 * MINUTE_IN_SECONDS;
    const UPDATE_ERROR_CACHE_TTL = 15 * MINUTE_IN_SECONDS;
    const LICENSE_ERROR_CACHE_TTL = 1 * HOUR_IN_SECONDS;
    const RATE_LIMIT_CACHE_TTL = 5 * MINUTE_IN_SECONDS;

    protected function init() {

        add_filter('plugins_api', array(
            $this,
            'plugins_api'
        ), 20, 3);

        add_filter('pre_set_site_transient_update_plugins', array(
            $this,
            'injectUpdate'
        ));

        add_filter('upgrader_pre_download', array(
            $this,
            'upgrader_pre_download'
        ), 10, 3);

        add_action('upgrader_process_complete', [
            $this,
            'upgrader_process_complete'
        ], 10, 2);

        add_action('admin_init', [
            $this,
            'maybeClearUpdateCacheAfterUpgrade'
        ]);
    }

    public static function plugins_api($res, $action, $args) {

        if ($action !== 'plugin_information') {
            return $res;
        }

        if (!is_object($args) || empty($args->slug) || $args->slug !== NEXTEND_SMARTSLIDER_3_SLUG) {
            return $res;
        }

        $response = SmartSlider3Info::api(array(
            'action'  => 'plugin_information',
            'slug'    => NEXTEND_SMARTSLIDER_3_SLUG,
            'channel' => SmartSlider3Info::$channel
        ));

        if (!is_array($response)) {
            return new WP_Error('request_failed', 'Unable to contact the update server.');
        }

        if (!isset($response['status']) || $response['status'] !== 'OK' || empty($response['data']) || !is_array($response['data'])) {
            $message = !empty($response['message']) ? $response['message'] : 'Invalid plugin information response.';
            $code    = !empty($response['code']) ? (string)$response['code'] : 'invalid_response';

            return new WP_Error($code, $message);
        }

        return (object)$response['data'];
    }

    public static function injectUpdate($transient) {

        $filename = NEXTEND_SMARTSLIDER_3_BASENAME;

        /**
         * Never gate this on $transient->checked! Before WordPress 6.8 wp_update_plugins() only
         * filled the "checked" property when it returned early from its cached branch, so on the
         * path where the real update check runs the transient has no "checked" property at all.
         */
        if (!is_object($transient)) {
            return $transient;
        }

        $item = self::getPluginInformationForUpdateCheck();

        if (is_wp_error($item) || !is_object($item) || empty($item->version)) {
            return $transient;
        }

        $updateLink = SmartSlider3Info::api(array(
            'action'  => 'update',
            'channel' => SmartSlider3Info::$channel
        ), true);

        $item->plugin                   = NEXTEND_SMARTSLIDER_3_BASENAME;
        $item->package                  = $updateLink;
        $item->download_link            = $updateLink;
        $item->versions                 = array();
        $item->versions[$item->version] = $updateLink;

        if (version_compare(SmartSlider3Info::$version, $item->version, '<')) {
            $transient->response[$filename] = (object)$item;
            unset($transient->no_update[$filename]);
        } else {
            $transient->no_update[$filename] = (object)$item;
            unset($transient->response[$filename]);
        }

        return $transient;
    }

    private static function getPluginInformationForUpdateCheck() {

        $cached = get_site_transient(self::UPDATE_CACHE_KEY);

        if (is_array($cached) && isset($cached['success'])) {
            if ($cached['success'] && isset($cached['data']) && is_object($cached['data'])) {
                return $cached['data'];
            }

            if (!$cached['success'] && !empty($cached['error']) && is_array($cached['error'])) {
                $code    = !empty($cached['error']['code']) ? $cached['error']['code'] : 'request_failed';
                $message = !empty($cached['error']['message']) ? $cached['error']['message'] : 'Request failed.';

                return new WP_Error($code, $message);
            }
        }

        $response = SmartSlider3Info::api(array(
            'action'  => 'plugin_information',
            'slug'    => NEXTEND_SMARTSLIDER_3_SLUG,
            'channel' => SmartSlider3Info::$channel
        ));

        if (!is_array($response)) {
            return self::cacheErrorAndReturn('request_failed', 'Unable to contact the update server.');
        }

        if (!isset($response['status']) || $response['status'] !== 'OK') {
            $code    = !empty($response['code']) ? (string)$response['code'] : 'request_failed';
            $message = !empty($response['message']) ? $response['message'] : 'Plugin information request failed.';

            if ($code === '' || $code === '0') {
                $code = 'request_failed';
            }

            return self::cacheErrorAndReturn($code, $message);
        }

        if (empty($response['data']) || !is_array($response['data'])) {
            return self::cacheErrorAndReturn('invalid_response', 'Plugin information response does not contain valid data.');
        }

        $item = (object)$response['data'];

        if (empty($item->version)) {
            return self::cacheErrorAndReturn('invalid_response', 'Plugin information response does not contain a version.');
        }

        set_site_transient(self::UPDATE_CACHE_KEY, array(
            'success' => true,
            'data'    => $item,
        ), self::UPDATE_SUCCESS_CACHE_TTL);

        return $item;
    }

    private static function cacheErrorAndReturn($code, $message) {

        set_site_transient(self::UPDATE_CACHE_KEY, array(
            'success' => false,
            'error'   => array(
                'code'    => (string)$code,
                'message' => (string)$message,
            ),
        ), self::getErrorCacheTtl($code));

        return new WP_Error($code, $message);
    }

    private static function getErrorCacheTtl($code) {

        switch ((string)$code) {
            case 'LICENSE_INVALID':
            case 'ASSET_PREMIUM':
            case 'LICENSE_EXPIRED':
            case 'DOMAIN_REGISTER_FAILED':
            case 'PLATFORM_NOT_ALLOWED':
            case 'license_invalid':
            case 'expired_license':
                return self::LICENSE_ERROR_CACHE_TTL;

            case 'rate_limited':
            case 'RATE_LIMITED':
            case '429':
                return self::RATE_LIMIT_CACHE_TTL;

            default:
                return self::UPDATE_ERROR_CACHE_TTL;
        }
    }

    public static function clearUpdateCache() {
        delete_site_transient('update_plugins');
        delete_site_transient(self::UPDATE_CACHE_KEY);
    }

    public static function forceUpdateCheck() {
        self::clearUpdateCache();
        wp_update_plugins();
    }

    public function upgrader_pre_download($reply, $package, $upgrader) {

        if (strpos($package, 'product=smartslider3') === false) {
            return $reply;
        }

        $status = ModelLicense::getInstance()
                              ->isActive(false);

        $message = '';

        switch ($status) {
            case 'OK':
                return $reply;

            case 'ASSET_PREMIUM':
            case 'LICENSE_EXPIRED':
                $message = 'Your <a href="https://smartslider.helpscoutdocs.com/article/1718-activation" target="_blank">license</a> has expired! Get new one: <a href="https://smartslider3.com/pricing" target="_blank">smartslider3.com</a>.';
                break;

            case 'DOMAIN_REGISTER_FAILED':
                $message = 'Smart Slider 3 Pro license is not registered on the current website. Please activate this website by following <a href="https://smartslider.helpscoutdocs.com/article/1718-activation" target="_blank">the license activation documentation</a>.';
                break;

            case 'LICENSE_INVALID':
                $message = 'Smart Slider 3 Pro license is not registered on the current website. Please activate this website by following <a href="https://smartslider.helpscoutdocs.com/article/1718-activation" target="_blank">the license activation documentation</a>.';
                ModelLicense::getInstance()
                            ->setKey('');
                break;

            case 'PLATFORM_NOT_ALLOWED':
                $message = 'Your <a href="https://smartslider.helpscoutdocs.com/article/1718-activation" target="_blank">license</a> is not valid for WordPress! Get a license for WordPress: <a href="https://smartslider3.com/pricing" target="_blank">smartslider3.com</a>';
                break;

            case '503':
                $message = 'Licensing server is down, try again later!';
                break;

            case null:
                $message = 'Licensing server not reachable, try again later!';
                break;

            default:
                $message = 'Unknown error, please write an email to support@nextendweb.com with the following status: ' . $status;
                break;
        }

        $reply                  = new WP_Error('SS3_ERROR', $message);
        $upgrader->result       = null;
        $upgrader->skin->result = $reply;

        return $reply;
    }

    public function upgrader_process_complete($upgrader, $hook_extra) {

        if (!isset($hook_extra['action']) || $hook_extra['action'] != 'update') {
            return;
        }

        if (!isset($hook_extra['type']) || $hook_extra['type'] != 'plugin') {
            return;
        }

        /**
         * Plugin_Upgrader::bulk_upgrade() passes the updated plugins in "plugins", but
         * Plugin_Upgrader::upgrade() - used by the automatic background updates - passes
         * a single plugin file in "plugin", so both have to be handled here.
         */
        if (isset($hook_extra['plugins']) && is_array($hook_extra['plugins'])) {
            $plugins = $hook_extra['plugins'];
        } else if (!empty($hook_extra['plugin'])) {
            $plugins = array($hook_extra['plugin']);
        } else {
            return;
        }

        if (in_array(NEXTEND_SMARTSLIDER_3_BASENAME, $plugins, true)) {
            update_site_option(self::CLEAR_CACHE_KEY, 1);
        }
    }

    public function maybeClearUpdateCacheAfterUpgrade() {
        if (get_site_option(self::CLEAR_CACHE_KEY)) {
            delete_site_option(self::CLEAR_CACHE_KEY);
            self::clearUpdateCache();
        }
    }
}