<?php

namespace Duplicator\Core\Notifications;

use DUP_LITE_Plugin_Upgrade;
use DUP_Settings;
use Duplicator\Ajax\ServicesNotifications;
use Duplicator\Core\Views\TplMng;

/**
 * Notifications.
 */
class Notifications
{
    /**
     * Source of notifications content.
     *
     * @var string
     */
    const SOURCE_URL = 'https://notifications.duplicator.com/dp-notifications.json';

    /**
     * WordPress option key containing notification data
     *
     * @var string
     */
    const DUPLICATOR_NOTIFICATIONS_OPT_KEY = 'duplicator_notifications';

    /**
     * WordPress option key containing notification data
     *
     * @var string
     */
    const DUPLICATOR_BEFORE_PACKAGES_HOOK = 'duplicator_before_packages_table_action';

    /**
     * Duplicator notifications dismiss nonce key
     *
     * @var string
     */
    const DUPLICATOR_NOTIFICATION_NONCE_KEY = 'duplicator-notification-dismiss';

    /**
     * Option value.
     *
     * @var bool|array
     */
    public static $option = false;

    /**
     * Initialize class.
     *
     * @return void
     */
    public static function init()
    {
        // Delete data even if notifications are disabled.
        add_action('deactivate_plugin', array(__CLASS__, 'delete'), 10, 2);

        if (!DUP_Settings::Get('amNotices')) {
            return;
        }

        // Add notification count to menu label.
        add_filter('duplicator_menu_label_duplicator', function ($label) {
            if (self::getCount() === 0) {
                return $label;
            }
            return $label . '<span class="awaiting-mod">' . self::getCount() . '</span>';
        });

        self::update();

        add_action(self::DUPLICATOR_BEFORE_PACKAGES_HOOK, array(__CLASS__, 'output'));

        $notificationsService = new ServicesNotifications();
        $notificationsService->init();
    }

    /**
     * Check if user has access and is enabled.
     *
     * @return bool
     */
    public static function hasAccess()
    {
        return current_user_can('manage_options');
    }

    /**
     * Get option value.
     *
     * @param bool $cache Reference property cache if available.
     *
     * @return array
     */
    public static function getOption($cache = true)
    {
        if (self::$option && $cache) {
            return self::$option;
        }

        $option = get_option(self::DUPLICATOR_NOTIFICATIONS_OPT_KEY, array());

        self::$option = array(
            'update'    => !empty($option['update']) ? (int)$option['update'] : 0,
            'feed'      => !empty($option['feed']) ? (array)$option['feed'] : array(),
            'events'    => !empty($option['events']) ? (array)$option['events'] : array(),
            'dismissed' => !empty($option['dismissed']) ? (array)$option['dismissed'] : array()
        );

        return self::$option;
    }

    /**
     * Fetch notifications from feed.
     *
     * @return array
     */
    public static function fetchFeed()
    {
        $response = wp_remote_get(
            self::SOURCE_URL,
            array(
                'timeout'    => 10,
                'user-agent' => self::getUserAgent(),
            )
        );

        if (is_wp_error($response)) {
            return array();
        }

        $body = wp_remote_retrieve_body($response);

        if (empty($body)) {
            return array();
        }

        return self::verify(json_decode($body, true));
    }

    /**
     * Verify notification data before it is saved.
     *
     * @param array $notifications Array of notifications items to verify.
     *
     * @return array
     */
    public static function verify($notifications)
    {
        $data = array();
        if (!is_array($notifications) || empty($notifications)) {
            return $data;
        }

        foreach ($notifications as $notification) {
            // Ignore if one of the conditional checks is true:
            //
            // 1. notification message is empty.
            // 2. license type does not match.
            // 3. notification is expired.
            // 4. notification has already been dismissed.
            // 5. notification existed before installing Duplicator.
            // (Prevents bombarding the user with notifications after activation).
            if (
                empty($notification['content']) ||
                !self::isLicenseTypeMatch($notification) ||
                self::isExpired($notification) ||
                self::isDismissed($notification) ||
                self::isExisted($notification)
            ) {
                continue;
            }

            $data[] = $notification;
        }

        return $data;
    }

    /**
     * Verify saved notification data for active notifications.
     *
     * @param array $notifications Array of notifications items to verify.
     *
     * @return array
     */
    public static function verifyActive($notifications)
    {
        if (!is_array($notifications) || empty($notifications)) {
            return array();
        }

        $current_timestamp = time();

        // Remove notifications that are not active.
        foreach ($notifications as $key => $notification) {
            if (
                (!empty($notification['start']) && $current_timestamp < strtotime($notification['start'])) ||
                (!empty($notification['end']) && $current_timestamp > strtotime($notification['end']))
            ) {
                unset($notifications[$key]);
            }
        }

        return $notifications;
    }

    /**
     * Get notification data.
     *
     * @return array
     */
    public static function get()
    {
        if (!self::hasAccess()) {
            return array();
        }

        $option = self::getOption();

        $feed   = !empty($option['feed']) ? self::verifyActive($option['feed']) : array();
        $events = !empty($option['events']) ? self::verifyActive($option['events']) : array();

        return array_merge($feed, $events);
    }

    /**
     * Get notification count.
     *
     * @return int
     */
    public static function getCount()
    {
        return count(self::get());
    }

    /**
     * Add a new Event Driven notification.
     *
     * @param array $notification Notification data.
     *
     * @return void
     */
    public static function add($notification)
    {
        if (!self::isValid($notification)) {
            return;
        }

        $option = self::getOption();

        // Notification ID already exists.
        if (!empty($option['events'][$notification['id']])) {
            return;
        }

        $notification = self::verify(array($notification));
        update_option(
            self::DUPLICATOR_NOTIFICATIONS_OPT_KEY,
            array(
                'update'    => $option['update'],
                'feed'      => $option['feed'],
                'events'    => array_merge($notification, $option['events']),
                'dismissed' => $option['dismissed'],
            )
        );
    }

    /**
     * Determine if notification data is valid.
     *
     * @param array $notification Notification data.
     *
     * @return bool
     */
    public static function isValid($notification)
    {
        if (empty($notification['id'])) {
            return false;
        }

        return count(self::verify(array($notification))) > 0;
    }

    /**
     * Determine if notification has already been dismissed.
     *
     * @param array $notification Notification data.
     *
     * @return bool
     */
    private static function isDismissed($notification)
    {
        $option = self::getOption();

        return !empty($option['dismissed']) && in_array($notification['id'], $option['dismissed']);
    }

    /**
     * Determine if license type is match.
     *
     * @param array $notification Notification data.
     *
     * @return bool
     */
    private static function isLicenseTypeMatch($notification)
    {
        // A specific license type is not required.
        $notification['type'] = (array)$notification['type'];
        if (empty($notification['type'])) {
            return false;
        }

        if (in_array('any', $notification['type'])) {
            return true;
        }

        return in_array(self::getLicenseType(), (array)$notification['type'], true);
    }

    /**
     * Determine if notification is expired.
     *
     * @param array $notification Notification data.
     *
     * @return bool
     */
    private static function isExpired($notification)
    {
        return !empty($notification['end']) && time() > strtotime($notification['end']);
    }

    /**
     * Determine if notification existed before installing Duplicator.
     *
     * @param array $notification Notification data.
     *
     * @return bool
     */
    private static function isExisted($notification)
    {
        $installInfo = DUP_LITE_Plugin_Upgrade::getInstallInfo();
        return (!empty($notification['start']) && $installInfo['time'] > strtotime($notification['start']));
    }

    /**
     * Update notification data from feed.
     *
     * @return void
     */
    public static function update()
    {
        $option = self::getOption();

        //Only update twice daily
        if ($option['update'] !== 0 && time() < $option['update'] + DAY_IN_SECONDS / 2) {
            return;
        }

        $data = array(
            'update'    => time(),
            'feed'      => self::fetchFeed(),
            'events'    => $option['events'],
            'dismissed' => $option['dismissed'],
        );

        /**
         * Allow changing notification data before it will be updated in database.
         *
         * @param array $data New notification data.
         */
        $data = (array)apply_filters('duplicator_admin_notifications_update_data', $data);

        update_option(self::DUPLICATOR_NOTIFICATIONS_OPT_KEY, $data);
    }

    /**
     * Remove notification data from database before a plugin is deactivated.
     *
     * @param string $plugin Path to the plugin file relative to the plugins directory.
     *
     * @return void
     */
    public static function delete($plugin)
    {
        $duplicator_plugins = array(
            'duplicator-lite/duplicator.php',
            'duplicator/duplicator.php',
        );

        if (!in_array($plugin, $duplicator_plugins, true)) {
            return;
        }

        delete_option(self::DUPLICATOR_NOTIFICATIONS_OPT_KEY);
    }

    /**
     * Enqueue assets on Form Overview admin page.
     *
     * @return void
     */
    public static function enqueues()
    {
        if (!self::getCount()) {
            return;
        }

        wp_enqueue_style(
            'dup-admin-notifications',
            DUPLICATOR_PLUGIN_URL . "assets/css/admin-notifications.css",
            array('dup-lity'),
            DUPLICATOR_VERSION
        );

        wp_enqueue_script(
            'dup-admin-notifications',
            DUPLICATOR_PLUGIN_URL . "assets/js/notifications/admin-notifications.js",
            array('jquery', 'dup-lity'),
            DUPLICATOR_VERSION,
            true
        );

        wp_localize_script(
            'dup-admin-notifications',
            'dup_admin_notifications',
            array(
                'ajax_url' => admin_url('admin-ajax.php'),
                'nonce'    => wp_create_nonce(self::DUPLICATOR_NOTIFICATION_NONCE_KEY),
            )
        );

        // Lity.
        wp_enqueue_style(
            'dup-lity',
            DUPLICATOR_PLUGIN_URL . 'assets/lib/lity/lity.min.css',
            array(),
            DUPLICATOR_VERSION
        );

        wp_enqueue_script(
            'dup-lity',
            DUPLICATOR_PLUGIN_URL . 'assets/lib/lity/lity.min.js',
            array('jquery'),
            DUPLICATOR_VERSION,
            true
        );
    }

    /**
     * Output notifications on Form Overview admin area.
     *
     * @return void
     */
    public static function output()
    {
        $notificationsData = self::get();

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


        $content_allowed_tags = array(
            'br'     => array(),
            'em'     => array(),
            'strong' => array(),
            'span'   => array(
                'style' => array()
            ),
            'p'      => array(
                'id'    => array(),
                'class' => array()
            ),
            'a'      => array(
                'href'   => array(),
                'target' => array(),
                'rel'    => array()
            )
        );

        $notifications = array();
        foreach ($notificationsData as $notificationData) {
            // Prepare required arguments.
            $notificationData = wp_parse_args(
                $notificationData,
                array(
                    'id'      => 0,
                    'title'   => '',
                    'content' => '',
                    'video'   => ''
                )
            );

            $title   = self::getComponentData($notificationData['title']);
            $content = self::getComponentData($notificationData['content']);

            if (!$title && !$content) {
                continue;
            }

            $notifications[] = array(
                'id'        => $notificationData['id'],
                'title'     => $title,
                'btns'      => self::getButtonsData($notificationData),
                'content'   => wp_kses(wpautop($content), $content_allowed_tags),
                'video_url' => wp_http_validate_url(self::getComponentData($notificationData['video'])),
            );
        }

        self::enqueues();
        TplMng::getInstance()->render(
            'parts/Notifications/main',
            array(
                'notifications' => $notifications
            )
        );
    }

    /**
     * Retrieve notification's buttons.
     *
     * @param array $notification Notification data.
     *
     * @return array
     */
    private static function getButtonsData($notification)
    {
        if (empty($notification['btn']) || !is_array($notification['btn'])) {
            return array();
        }

        $buttons = array();
        if (!empty($notification['btn']['main_text']) && !empty($notification['btn']['main_url'])) {
            $buttons[] = array(
                'type'   => 'primary',
                'text'   => $notification['btn']['main_text'],
                'url'    => self::prepareBtnUrl($notification['btn']['main_url']),
                'target' => '_blank'
            );
        }

        if (!empty($notification['btn']['alt_text']) && !empty($notification['btn']['alt_url'])) {
            $buttons[] = array(
                'type'   => 'secondary',
                'text'   => $notification['btn']['alt_text'],
                'url'    => self::prepareBtnUrl($notification['btn']['alt_url']),
                'target' => '_blank'
            );
        }

        return $buttons;
    }

    /**
     * Retrieve notification's component data by a license type.
     *
     * @param mixed $data Component data.
     *
     * @return false|mixed
     */
    private static function getComponentData($data)
    {
        if (empty($data['license'])) {
            return $data;
        }

        $license_type = self::getLicenseType();
        return !empty($data['license'][$license_type]) ? $data['license'][$license_type] : false;
    }

    /**
     * Retrieve the current installation license type (always lowercase).
     *
     * @return string
     */
    private static function getLicenseType()
    {
        return 'lite';
    }

    /**
     * Prepare button URL.
     *
     * @param string $btnUrl Button url.
     *
     * @return string
     */
    private static function prepareBtnUrl($btnUrl)
    {
        if (empty($btnUrl)) {
            return '';
        }

        $replace_tags = array(
            '{admin_url}' => admin_url()
        );

        return wp_http_validate_url(str_replace(array_keys($replace_tags), array_values($replace_tags), $btnUrl));
    }

    /**
     * User agent that will be used for the request
     *
     * @return string
     */
    private static function getUserAgent()
    {
        return 'WordPress/' . get_bloginfo('version') . '; ' . get_bloginfo('url') . '; Duplicator/Lite-' . DUPLICATOR_VERSION;
    }
}
