using System;
using System.Collections.Concurrent;
using System.Threading;
using AdvChkSys.Interfaces;

namespace AdvChkSys.Threading
{
    /// <summary>
    /// Provides synchronous locking for chunks.
    /// </summary>
    public class ChunkThreadSafetyManager : IDisposable
    {
        // Lock objects for each chunk
        private readonly ConcurrentDictionary<IChunk, ChunkLock> _locks = new();

        // Timer for cleanup
        private readonly Timer _cleanupTimer;

        // Last access time for each lock
        private readonly ConcurrentDictionary<IChunk, DateTime> _lastAccessTime = new();

        // Cleanup interval in minutes
        private readonly int _cleanupIntervalMinutes;

        /// <summary>
        /// Initializes a new instance of the ChunkThreadSafetyManager class.
        /// </summary>
        /// <param name="cleanupIntervalMinutes">Interval in minutes for cleaning up unused locks</param>
        public ChunkThreadSafetyManager(int cleanupIntervalMinutes = 10)
        {
            _cleanupIntervalMinutes = cleanupIntervalMinutes;
            _cleanupTimer = new Timer(CleanupUnusedLocks, null,
                TimeSpan.FromMinutes(cleanupIntervalMinutes),
                TimeSpan.FromMinutes(cleanupIntervalMinutes));
        }

        /// <summary>
        /// Acquires an exclusive lock on a chunk.
        /// </summary>
        /// <param name="chunk">The chunk to lock</param>
        /// <returns>A disposable that releases the lock when disposed</returns>
        public IDisposable AcquireLock(IChunk chunk)
        {
            var lockObj = _locks.GetOrAdd(chunk, _ => new ChunkLock());
            _lastAccessTime[chunk] = DateTime.UtcNow;

            lockObj.EnterWriteLock();

            return new LockReleaser(lockObj, chunk, this, LockType.Write);
        }

        /// <summary>
        /// Acquires a read lock on a chunk.
        /// </summary>
        /// <param name="chunk">The chunk to lock</param>
        /// <returns>A disposable that releases the lock when disposed</returns>
        public IDisposable AcquireReadLock(IChunk chunk)
        {
            var lockObj = _locks.GetOrAdd(chunk, _ => new ChunkLock());
            _lastAccessTime[chunk] = DateTime.UtcNow;

            lockObj.EnterReadLock();

            return new LockReleaser(lockObj, chunk, this, LockType.Read);
        }

        /// <summary>
        /// Acquires a write lock on a chunk.
        /// </summary>
        /// <param name="chunk">The chunk to lock</param>
        /// <returns>A disposable that releases the lock when disposed</returns>
        public IDisposable AcquireWriteLock(IChunk chunk)
        {
            var lockObj = _locks.GetOrAdd(chunk, _ => new ChunkLock());
            _lastAccessTime[chunk] = DateTime.UtcNow;

            lockObj.EnterWriteLock();

            return new LockReleaser(lockObj, chunk, this, LockType.Write);
        }

        /// <summary>
        /// Tries to acquire a read lock on a chunk.
        /// </summary>
        /// <param name="chunk">The chunk to lock</param>
        /// <param name="timeout">The timeout</param>
        /// <returns>A disposable that releases the lock when disposed, or null if the lock could not be acquired</returns>
        public IDisposable? TryAcquireReadLock(IChunk chunk, TimeSpan timeout)
        {
            var lockObj = _locks.GetOrAdd(chunk, _ => new ChunkLock());
            _lastAccessTime[chunk] = DateTime.UtcNow;

            if (lockObj.TryEnterReadLock(timeout))
            {
                return new LockReleaser(lockObj, chunk, this, LockType.Read);
            }

            // Track contention
            ChunkThreadingDiagnostics.TrackLockContention(chunk);

            return null;
        }

        /// <summary>
        /// Tries to acquire a write lock on a chunk.
        /// </summary>
        /// <param name="chunk">The chunk to lock</param>
        /// <param name="timeout">The timeout</param>
        /// <returns>A disposable that releases the lock when disposed, or null if the lock could not be acquired</returns>
        public IDisposable? TryAcquireWriteLock(IChunk chunk, TimeSpan timeout)
        {
            var lockObj = _locks.GetOrAdd(chunk, _ => new ChunkLock());
            _lastAccessTime[chunk] = DateTime.UtcNow;

            if (lockObj.TryEnterWriteLock(timeout))
            {
                return new LockReleaser(lockObj, chunk, this, LockType.Write);
            }

            // Track contention
            ChunkThreadingDiagnostics.TrackLockContention(chunk);

            return null;
        }

        /// <summary>
        /// Cleans up unused locks.
        /// </summary>
        private void CleanupUnusedLocks(object? state)
        {
            var now = DateTime.UtcNow;
            var threshold = now.AddMinutes(-_cleanupIntervalMinutes);

            foreach (var chunk in _lastAccessTime.Keys)
            {
                if (_lastAccessTime.TryGetValue(chunk, out var lastAccess) &&
                    lastAccess < threshold &&
                    _locks.TryGetValue(chunk, out var lockObj))
                {
                    // Only remove if no one is using the lock
                    if (!lockObj.IsReadLockHeld && !lockObj.IsWriteLockHeld)
                    {
                        if (_locks.TryRemove(chunk, out var removedLock))
                        {
                            _lastAccessTime.TryRemove(chunk, out _);
                            removedLock.Dispose();
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Disposes resources.
        /// </summary>
        public void Dispose()
        {
            _cleanupTimer.Dispose();

            foreach (var lockObj in _locks.Values)
            {
                lockObj.Dispose();
            }

            _locks.Clear();
            _lastAccessTime.Clear();
        }

        /// <summary>
        /// Type of lock.
        /// </summary>
        private enum LockType
        {
            Read,
            Write
        }

        /// <summary>
        /// Wrapper around ReaderWriterLockSlim.
        /// </summary>
        private class ChunkLock : IDisposable
        {
            private readonly ReaderWriterLockSlim _lock = new(LockRecursionPolicy.SupportsRecursion);

            public void EnterReadLock() => _lock.EnterReadLock();
            public void ExitReadLock() => _lock.ExitReadLock();
            public void EnterWriteLock() => _lock.EnterWriteLock();
            public void ExitWriteLock() => _lock.ExitWriteLock();

            public bool TryEnterReadLock(TimeSpan timeout) => _lock.TryEnterReadLock(timeout);
            public bool TryEnterWriteLock(TimeSpan timeout) => _lock.TryEnterWriteLock(timeout);

            public bool IsReadLockHeld => _lock.IsReadLockHeld;
            public bool IsWriteLockHeld => _lock.IsWriteLockHeld;

            public void Dispose() => _lock.Dispose();
        }

        /// <summary>
        /// Releases a lock when disposed.
        /// </summary>
        private class LockReleaser : IDisposable
        {
            private readonly ChunkLock _lock;
            private readonly IChunk _chunk;
            private readonly ChunkThreadSafetyManager _parent;
            private readonly LockType _lockType;
            private bool _disposed;

            public LockReleaser(ChunkLock lockObj, IChunk chunk, ChunkThreadSafetyManager parent, LockType lockType)
            {
                _lock = lockObj;
                _chunk = chunk;
                _parent = parent;
                _lockType = lockType;
            }

            public void Dispose()
            {
                if (!_disposed)
                {
                    if (_lockType == LockType.Read)
                    {
                        _lock.ExitReadLock();
                    }
                    else
                    {
                        _lock.ExitWriteLock();
                    }

                    _parent._lastAccessTime[_chunk] = DateTime.UtcNow;
                    _disposed = true;
                }
            }
        }
    }
}