using System.Collections.Concurrent;

namespace ScadaLink.DeploymentManager;

/// <summary>
/// WP-3: Per-instance operation lock. Only one mutating operation (deploy, disable, enable, delete)
/// may be in progress per instance at a time. Different instances can proceed in parallel.
///
/// Implementation: ConcurrentDictionary of SemaphoreSlim(1,1) keyed by instance unique name.
/// Lock released on completion, timeout, or failure.
/// Lost on central failover (acceptable per design -- in-progress treated as failed).
/// </summary>
public class OperationLockManager
{
    private readonly ConcurrentDictionary<string, SemaphoreSlim> _locks = new(StringComparer.Ordinal);

    /// <summary>
    /// Acquires the operation lock for the given instance. Returns a disposable that releases the lock.
    /// Throws TimeoutException if the lock cannot be acquired within the timeout.
    /// </summary>
    public async Task<IDisposable> AcquireAsync(string instanceUniqueName, TimeSpan timeout, CancellationToken cancellationToken = default)
    {
        var semaphore = _locks.GetOrAdd(instanceUniqueName, _ => new SemaphoreSlim(1, 1));

        if (!await semaphore.WaitAsync(timeout, cancellationToken))
        {
            throw new TimeoutException(
                $"Could not acquire operation lock for instance '{instanceUniqueName}' within {timeout.TotalSeconds}s. " +
                "Another mutating operation is in progress.");
        }

        return new LockRelease(semaphore);
    }

    /// <summary>
    /// Checks whether a lock is currently held for the given instance (for diagnostics).
    /// </summary>
    public bool IsLocked(string instanceUniqueName)
    {
        return _locks.TryGetValue(instanceUniqueName, out var semaphore) && semaphore.CurrentCount == 0;
    }

    private sealed class LockRelease : IDisposable
    {
        private readonly SemaphoreSlim _semaphore;
        private int _disposed;

        public LockRelease(SemaphoreSlim semaphore) => _semaphore = semaphore;

        public void Dispose()
        {
            if (Interlocked.CompareExchange(ref _disposed, 1, 0) == 0)
            {
                _semaphore.Release();
            }
        }
    }
}
