<?php

/**
 * Handles package upgrades.
 *
 * @package northmill-online-shared
 */
declare (strict_types=1);
namespace Northmill_Online_Shared_Checkout\Northmill_Online_Shared\Modules;

use DateTime;
use Exception;
use Northmill_Online_Shared_Checkout\Northmill_Online_Shared\Helpers\Hook_Helpers;
use Northmill_Online_Shared_Checkout\Northmill_Online_Shared\Helpers\Time_Helpers;
use Northmill_Online_Shared_Checkout\Northmill_Online_Shared\Modules\Loggers\Log_Level_Enum;
defined('ABSPATH') || exit;
/**
 * Upgrader class.
 */
class Upgrader_Module extends Abstract_Module
{
    /**
     * The upgrades to be processed.
     *
     * @var array
     */
    protected array $upgrades;
    /**
     * Module setup.
     *
     * @return void
     */
    public function setup(): void
    {
        /**
         * The filtered array should be in the format:
         * ```
         * array(
         *   '1.0.0' => function() { /* Upgrade code * / },
         * )
         * ```
         *
         * @return array
         */
        $upgrades = Hook_Helpers::apply_scoped_filters('northmill_online_shared_upgrades', array());
        if (!empty($upgrades)) {
            foreach ($upgrades as $version => $upgrade_processor) {
                if (!is_callable($upgrade_processor)) {
                    $this->shared->logger->log('upgrader', 'Upgrade processor is not callable', array('version' => $version), Log_Level_Enum::EMERGENCY);
                    unset($upgrades[$version]);
                }
            }
            $this->upgrades = $upgrades;
        }
        add_action('admin_init', array($this, 'upgrade'));
    }
    /**
     * Get the package version option name.
     *
     * @return string
     */
    public function get_package_version_option_name(): string
    {
        return Hook_Helpers::apply_scoped_filters('northmill_online_shared_package_version_option_name', $this->shared->package_info->get_service_name_underscored() . '_version');
    }
    /**
     * Get the package version option name.
     *
     * @return string
     */
    public function get_package_updates_option_name(): string
    {
        return $this->shared->package_info->get_service_name_underscored() . '_updates';
    }
    /**
     * Get a datetime of when an update was performed.
     *
     * @param string $version Semver of update.
     *
     * @return DateTime|null
     *
     * @used Northmill Sync
     */
    public function get_time_of_update(string $version): ?DateTime
    {
        try {
            $updates = get_option($this->get_package_updates_option_name(), array());
            $update = $updates[$version] ?? end($updates);
            $update_datetime = $update['datetime'] ?? DateTime::createFromFormat('Y-m-d H:i:s', '2000-01-01 00:00:00')->format('Y-m-d H:i:s');
            return new DateTime($update_datetime);
        } catch (Exception $e) {
            $this->shared->logger->log('upgrader', 'Could not get time of update', array('error' => $e->getMessage(), 'version' => $version, 'updates' => $updates ?? null), Log_Level_Enum::ERROR);
            return null;
        }
    }
    /**
     * Upgrade plugin settings from previous versions
     *
     * @return void
     */
    public function upgrade(): void
    {
        if (empty($this->upgrades)) {
            return;
        }
        $package_version = $this->shared->package_info->package_data->get_version();
        $stored_version = get_option($this->get_package_version_option_name(), '0.0.0');
        $is_new_install = '0.0.0' === $stored_version;
        // The package has been updated.
        if (version_compare($stored_version, $package_version, '<')) {
            foreach ($this->upgrades as $upgrade_version => $upgrade_processor) {
                if (version_compare($stored_version, $upgrade_version, '<') && version_compare($upgrade_version, $package_version, '<=')) {
                    $this->process_upgrade($upgrade_version, $upgrade_processor, $is_new_install);
                }
            }
            /**
             * Update the stored plugin version.
             */
            update_option($this->get_package_version_option_name(), $package_version);
            $updates = get_option($this->get_package_updates_option_name(), array());
            $updates[$package_version] = array('version' => $package_version, 'datetime' => Time_Helpers::get_current_api_time());
            update_option($this->get_package_updates_option_name(), $updates);
        }
    }
    /**
     * Process the upgrade.
     *
     * @param string   $version            The version of the upgrade.
     * @param callable $upgrade_processor  The upgrade processor.
     * @param bool     $is_new_install     Whether the plugin is a new installation.
     *
     * @return void
     */
    private function process_upgrade(string $version, callable $upgrade_processor, bool $is_new_install): void
    {
        $this->shared->logger->log('upgrader', 'Processing upgrade', array('version' => $version));
        call_user_func($upgrade_processor, $is_new_install);
        $this->shared->logger->log('upgrader', 'Upgrade processed', array('version' => $version));
    }
}
