<?php

/**
 * Model for Locks
 *
 * @package northmill-online-shared
 */
declare (strict_types=1);
namespace Northmill_Online_Shared_Checkout\Northmill_Online_Shared\Modules\Cron\Models;

use Northmill_Online_Shared_Checkout\Northmill_Online_Shared\Modules\Cron\Locks;
defined('ABSPATH') || exit;
/**
 * Used to lock operation to only run once
 */
class Lock_Model
{
    /**
     * Key for time of lock.
     *
     * @var string
     */
    public string $key_time;
    /**
     * Key for value of lock.
     *
     * @var string
     */
    public string $key_value;
    /**
     * Lock value.
     *
     * @var string
     */
    public string $value;
    /**
     * Setup lock class.
     *
     * @param string $slug Lock name using only lowercase and underscores.
     */
    public function __construct(string $slug)
    {
        $this->key_time = Locks::LOCK_OPTION_PREFIX . get_current_blog_id() . '_time_' . $slug;
        $this->key_value = Locks::LOCK_OPTION_PREFIX . get_current_blog_id() . '_value_' . $slug;
        $this->value = wp_generate_uuid4();
    }
    /**
     * Creates a lock. This function will sleep for 50 ms to ensure lock is not overwritten by another process.
     *
     * @return bool True on successful lock, false on already locked
     */
    public function lock(): bool
    {
        if ($this->is_any_lock()) {
            // Another process uses this lock.
            return \false;
        }
        $this->set_lock();
        if (!$this->is_my_lock()) {
            // Another process has overwritten this lock.
            return \false;
        }
        // Our lock is OK.
        return \true;
    }
    /**
     * Clears all locks of set name. Set by this process or otherwise.
     */
    public function clear_lock(): void
    {
        global $wpdb;
        $wpdb->delete($wpdb->options, array('option_name' => $this->key_time), array('%s'));
        $wpdb->delete($wpdb->options, array('option_name' => $this->key_value), array('%s'));
    }
    /**
     * Gets current lock value
     *
     * @return false|string
     */
    public function get_lock_value(): false|string
    {
        global $wpdb;
        $inserted_time = $wpdb->get_var($wpdb->prepare("SELECT option_value FROM {$wpdb->options} WHERE option_name = %s ORDER BY option_id ASC", $this->key_time));
        $inserted_value = $wpdb->get_var($wpdb->prepare("SELECT option_value FROM {$wpdb->options} WHERE option_name = %s ORDER BY option_id ASC", $this->key_value));
        if (is_null($inserted_time) || intval($inserted_time) + 300 < time()) {
            $this->clear_lock();
            return \false;
        }
        return $inserted_value ?? \false;
    }
    /**
     * Internal function
     */
    public function is_any_lock(): bool
    {
        return \false !== $this->get_lock_value();
    }
    /**
     * Internal function
     */
    private function set_lock(): void
    {
        global $wpdb;
        $inserted_time = $wpdb->replace($wpdb->options, array('option_name' => $this->key_time, 'option_value' => time(), 'autoload' => 'no'), array('%s', '%s', '%s'));
        $inserted_val = $wpdb->replace($wpdb->options, array('option_name' => $this->key_value, 'option_value' => $this->value, 'autoload' => 'no'), array('%s', '%s', '%s'));
        if (!$inserted_time || !$inserted_val) {
            return;
        }
        usleep(wp_rand(10, 50) * 1000);
        // Sleep 10ms – 50ms.
    }
    /**
     * Internal function
     */
    private function is_my_lock(): bool
    {
        return $this->value === $this->get_lock_value();
    }
}
