<?php
/**
 * Lock.php
 *
 * DB advisory lock helpers (GET_LOCK / RELEASE_LOCK)
 */

namespace WcElectronInvoice\Functions;

if (! defined('ABSPATH')) {
    exit; // Exit if accessed directly.
}

/**
 * Option key used to cache DB lock support probe.
 */
const DB_LOCK_SUPPORT_OPTION = 'wc_el_inv-db_lock_support';

/**
 * Build a deterministic lock key (<= 64 chars).
 */
function buildDbLockKey(
    string $channel,
    string $action,
    int $orderID = 0,
    string $provider = 'woocommerce',
    string $resource = ''
): string {
    $channel  = preg_replace('/[^a-z0-9_\-]/i', '_', strtolower($channel));
    $action   = preg_replace('/[^a-z0-9_\-]/i', '_', strtolower($action));
    $provider = preg_replace('/[^a-z0-9_\-]/i', '_', strtolower($provider));
    $resource = preg_replace('/[^a-z0-9_\-]/i', '_', strtolower((string)$resource));

    $raw = implode('|', [
        'pop',
        $channel,
        $action,
        (string)max(0, $orderID),
        $provider,
        $resource,
    ]);

    return 'pop_lock_' . substr(sha1($raw), 0, 40);
}

/**
 * Whether DB lock feature is enabled.
 *
 * Filter: wc_el_inv-db_lock_enabled
 */
function isDbLockEnabled(): bool
{
    return (bool)apply_filters('wc_el_inv-db_lock_enabled', true);
}

/**
 * Check whether the current DB supports GET_LOCK/RELEASE_LOCK.
 *
 * Result is cached in an option to avoid probing every request.
 */
function isDbLockSupported(): bool
{
    global $wpdb;

    if (! isset($wpdb) || ! is_object($wpdb) || ! method_exists($wpdb, 'prepare') || ! method_exists($wpdb, 'get_var')) {
        return false;
    }

    $cache = get_option(DB_LOCK_SUPPORT_OPTION, []);
    if (! is_array($cache)) {
        $cache = [];
    }

    $checkedAt = isset($cache['checked_at']) ? (int)$cache['checked_at'] : 0;
    $supported = isset($cache['supported']) ? (bool)$cache['supported'] : null;
    $ttl       = (int)apply_filters('wc_el_inv-db_lock_support_ttl', DAY_IN_SECONDS);
    $ttl       = $ttl > 0 ? $ttl : DAY_IN_SECONDS;

    if (null !== $supported && $checkedAt > 0 && (time() - $checkedAt) < $ttl) {
        return $supported;
    }

    $probeKey    = 'pop_lock_probe_' . substr(sha1(home_url('/')), 0, 20);
    $probeQuery  = $wpdb->prepare('SELECT GET_LOCK(%s, %d)', $probeKey, 0);
    $probeResult = $wpdb->get_var($probeQuery);
    $supported   = in_array((string)$probeResult, ['0', '1'], true);

    if ($supported && '1' === (string)$probeResult) {
        $releaseQuery = $wpdb->prepare('SELECT RELEASE_LOCK(%s)', $probeKey);
        $wpdb->get_var($releaseQuery);
    }

    update_option(DB_LOCK_SUPPORT_OPTION, [
        'supported'  => $supported ? 1 : 0,
        'checked_at' => time(),
    ]);

    return $supported;
}

/**
 * Acquire DB advisory lock.
 */
function acquireDbLock(string $lockKey, int $timeout = 0): bool
{
    global $wpdb;

    if (! isDbLockEnabled()) {
        return true;
    }

    // Fallback compatibility for unsupported DB engines.
    if (! isDbLockSupported()) {
        return true;
    }

    if (! isset($wpdb) || ! is_object($wpdb) || ! method_exists($wpdb, 'prepare') || ! method_exists($wpdb, 'get_var')) {
        return false;
    }

    $timeout = max(0, (int)$timeout);
    $query   = $wpdb->prepare('SELECT GET_LOCK(%s, %d)', $lockKey, $timeout);
    $result  = $wpdb->get_var($query);

    return '1' === (string)$result;
}

/**
 * Release DB advisory lock.
 */
function releaseDbLock(string $lockKey): bool
{
    global $wpdb;

    if (! isDbLockEnabled()) {
        return true;
    }

    // Fallback compatibility for unsupported DB engines.
    if (! isDbLockSupported()) {
        return true;
    }

    if (! isset($wpdb) || ! is_object($wpdb) || ! method_exists($wpdb, 'prepare') || ! method_exists($wpdb, 'get_var')) {
        return false;
    }

    $query  = $wpdb->prepare('SELECT RELEASE_LOCK(%s)', $lockKey);
    $result = $wpdb->get_var($query);

    return '1' === (string)$result;
}
