using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Xunit;
using Moq;
using Microsoft.Extensions.Caching.Memory;
using Haoliang.Core.Services;
using Haoliang.Models.Models.Device;
using Haoliang.Models.Models.Production;

namespace Haoliang.Tests.Services
{
    public class CacheServiceTests
    {
        private readonly Mock<IMemoryCache> _mockMemoryCache;
        private readonly Mock<IProductionRepository> _mockProductionRepository;
        private readonly Mock<IDeviceRepository> _mockDeviceRepository;
        private readonly Mock<ICollectionRepository> _mockCollectionRepository;
        private readonly CacheService _cacheService;

        public CacheServiceTests()
        {
            _mockMemoryCache = new Mock<IMemoryCache>();
            _mockProductionRepository = new Mock<IProductionRepository>();
            _mockDeviceRepository = new Mock<IDeviceRepository>();
            _mockCollectionRepository = new Mock<ICollectionRepository>();

            _cacheService = new CacheService(_mockMemoryCache.Object);
        }

        [Fact]
        public async Task GetOrSetAsync_ValueNotInCache_ExecutesFactoryAndCachesResult()
        {
            // Arrange
            var key = "test-key";
            var factoryResult = "test-value";
            
            var factoryMock = new Mock<Func<Task<string>>>();
            factoryMock.Setup(f => f()).ReturnsAsync(factoryResult);

            // Setup cache miss
            _mockMemoryCache.Setup(cache => cache.TryGetValue(key, out It.Ref<string>.IsAny))
                .Returns(false);

            // Setup cache set
            _mockMemoryCache.Setup(cache => cache.Set(
                key, 
                factoryResult, 
                It.IsAny<MemoryCacheEntryOptions>()))
                .Callback<string, object, MemoryCacheEntryOptions>((k, v, o) => { });

            // Act
            var result = await _cacheService.GetOrSetAsync(key, factoryMock.Object);

            // Assert
            Assert.Equal(factoryResult, result);
            factoryMock.Verify(f => f(), Times.Once);
            _mockMemoryCache.Verify(cache => cache.TryGetValue(key, out It.Ref<string>.IsAny), Times.Once);
            _mockMemoryCache.Verify(cache => cache.Set(
                key, 
                factoryResult, 
                It.IsAny<MemoryCacheEntryOptions>()), Times.Once);
        }

        [Fact]
        public async Task GetOrSetAsync_ValueInCache_ReturnsCachedValue()
        {
            // Arrange
            var key = "test-key";
            var cachedValue = "cached-value";
            
            var factoryMock = new Mock<Func<Task<string>>>();
            factoryMock.Setup(f => f()).ReturnsAsync("new-value");

            // Setup cache hit
            _mockMemoryCache.Setup(cache => cache.TryGetValue(key, out It.Ref<string>.IsAny))
                .Callback<string, out string>((k, out string v) => v = cachedValue)
                .Returns(true);

            // Act
            var result = await _cacheService.GetOrSetAsync(key, factoryMock.Object);

            // Assert
            Assert.Equal(cachedValue, result);
            factoryMock.Verify(f => f(), Times.Never);
            _mockMemoryCache.Verify(cache => cache.TryGetValue(key, out It.Ref<string>.IsAny), Times.Once);
            _mockMemoryCache.Verify(cache => cache.Set(
                key, 
                It.IsAny<string>(), 
                It.IsAny<MemoryCacheEntryOptions>()), Times.Never);
        }

        [Fact]
        public void Get_ValueExists_ReturnsCachedValue()
        {
            // Arrange
            var key = "test-key";
            var cachedValue = "cached-value";

            // Setup cache hit
            _mockMemoryCache.Setup(cache => cache.TryGetValue(key, out It.Ref<string>.IsAny))
                .Callback<string, out string>((k, out string v) => v = cachedValue)
                .Returns(true);

            // Act
            var result = _cacheService.Get<string>(key);

            // Assert
            Assert.Equal(cachedValue, result);
            _mockMemoryCache.Verify(cache => cache.TryGetValue(key, out It.Ref<string>.IsAny), Times.Once);
        }

        [Fact]
        public void Get_ValueNotExists_ReturnsDefault()
        {
            // Arrange
            var key = "test-key";

            // Setup cache miss
            _mockMemoryCache.Setup(cache => cache.TryGetValue(key, out It.Ref<string>.IsAny))
                .Returns(false);

            // Act
            var result = _cacheService.Get<string>(key);

            // Assert
            Assert.Null(result);
            _mockMemoryCache.Verify(cache => cache.TryGetValue(key, out It.Ref<string>.IsAny), Times.Once);
        }

        [Fact]
        public void Set_ValidValue_CachesValue()
        {
            // Arrange
            var key = "test-key";
            var value = "test-value";

            var mockEntryOptions = new Mock<MemoryCacheEntryOptions>();
            _mockMemoryCache.Setup(cache => cache.Set(key, value, mockEntryOptions.Object))
                .Verifiable();

            // Act
            _cacheService.Set(key, value, mockEntryOptions.Object);

            // Assert
            _mockMemoryCache.Verify(cache => cache.Set(key, value, mockEntryOptions.Object), Times.Once);
        }

        [Fact]
        public void Set_NullValue_DoesNotCache()
        {
            // Arrange
            var key = "test-key";

            // Act
            _cacheService.Set<string>(key, null);

            // Assert
            _mockMemoryCache.Verify(cache => cache.Set(
                It.IsAny<string>(), 
                It.IsAny<string>(), 
                It.IsAny<MemoryCacheEntryOptions>()), Times.Never);
        }

        [Fact]
        public void Remove_KeyExists_RemovesFromCache()
        {
            // Arrange
            var key = "test-key";

            // Setup cache hit for removal check
            _mockMemoryCache.Setup(cache => cache.TryGetValue(key, out It.Ref<object>.IsAny))
                .Callback<string, out object>((k, out object v) => v = "some-value")
                .Returns(true);

            _mockMemoryCache.Setup(cache => cache.Remove(key))
                .Verifiable();

            // Act
            var result = _cacheService.Remove(key);

            // Assert
            Assert.True(result);
            _mockMemoryCache.Verify(cache => cache.Remove(key), Times.Once);
        }

        [Fact]
        public void Remove_KeyNotExists_ReturnsFalse()
        {
            // Arrange
            var key = "test-key";

            // Setup cache miss
            _mockMemoryCache.Setup(cache => cache.TryGetValue(key, out It.Ref<object>.IsAny))
                .Returns(false);

            // Act
            var result = _cacheService.Remove(key);

            // Assert
            Assert.False(result);
            _mockMemoryCache.Verify(cache => cache.Remove(key), Times.Never);
        }

        [Fact]
        public void Exists_KeyExists_ReturnsTrue()
        {
            // Arrange
            var key = "test-key";

            // Setup cache hit
            _mockMemoryCache.Setup(cache => cache.TryGetValue(key, out It.Ref<object>.IsAny))
                .Returns(true);

            // Act
            var result = _cacheService.Exists(key);

            // Assert
            Assert.True(result);
            _mockMemoryCache.Verify(cache => cache.TryGetValue(key, out It.Ref<object>.IsAny), Times.Once);
        }

        [Fact]
        public void Exists_KeyNotExists_ReturnsFalse()
        {
            // Arrange
            var key = "test-key";

            // Setup cache miss
            _mockMemoryCache.Setup(cache => cache.TryGetValue(key, out It.Ref<object>.IsAny))
                .Returns(false);

            // Act
            var result = _cacheService.Exists(key);

            // Assert
            Assert.False(result);
            _mockMemoryCache.Verify(cache => cache.TryGetValue(key, out It.Ref<object>.IsAny), Times.Once);
        }

        [Fact]
        public void Clear_ClearsAllCache()
        {
            // Act
            _cacheService.Clear();

            // Assert
            _mockMemoryCache.Verify(cache => cache.Compact(1.0), Times.Once);
        }

        [Fact]
        public void GetStatistics_ReturnsCacheStatistics()
        {
            // Arrange
            _mockMemoryCache.Setup(cache => cache.Count)
                .Returns(5);

            // Act
            var result = _cacheService.GetStatistics();

            // Assert
            Assert.NotNull(result);
            Assert.True(result.TotalItems >= 0);
            Assert.True(result.HitRate >= 0 && result.HitRate <= 1);
            Assert.True(result.MemoryUsageBytes >= 0);
            Assert.NotNull(result.ItemsByType);
        }

        [Fact]
        public void GetKeys_MatchingPattern_ReturnsKeys()
        {
            // Arrange
            var pattern = "device:*";
            var expectedKeys = new List<string> { "device:1", "device:2", "device:3" };

            // Mock cache keys
            var mockKeys = new List<string> { "device:1", "template:1", "device:2", "config:1", "device:3" };
            _mockMemoryCache.Setup(cache => cache.Keys)
                .Returns(mockKeys.Cast<object>().ToList());

            // Act
            var result = _cacheService.GetKeys(pattern).ToList();

            // Assert
            Assert.Equal(3, result.Count);
            Assert.Contains("device:1", result);
            Assert.Contains("device:2", result);
            Assert.Contains("device:3", result);
        }

        [Fact]
        public void GetKeys_NoMatches_ReturnsEmptyList()
        {
            // Arrange
            var pattern = "nonexistent:*";

            // Mock cache keys
            var mockKeys = new List<string> { "device:1", "template:1", "config:1" };
            _mockMemoryCache.Setup(cache => cache.Keys)
                .Returns(mockKeys.Cast<object>().ToList());

            // Act
            var result = _cacheService.GetKeys(pattern).ToList();

            // Assert
            Assert.Empty(result);
        }

        [Fact]
        public void Refresh_ExistingKey_RefreshesCache()
        {
            // Arrange
            var key = "test-key";
            var value = "test-value";
            var mockOptions = new MemoryCacheEntryOptions();

            // Setup cache hit
            _mockMemoryCache.Setup(cache => cache.TryGetValue(key, out It.Ref<object>.IsAny))
                .Callback<string, out object>((k, out object v) => v = value)
                .Returns(true);

            _mockMemoryCache.Setup(cache => cache.Remove(key))
                .Verifiable();

            _mockMemoryCache.Setup(cache => cache.Set(key, value, mockOptions))
                .Verifiable();

            // Act
            var result = _cacheService.Refresh<object>(key);

            // Assert
            Assert.True(result);
            _mockMemoryCache.Verify(cache => cache.Remove(key), Times.Once);
            _mockMemoryCache.Verify(cache => cache.Set(key, value, mockOptions), Times.Once);
        }

        [Fact]
        public void Refresh_NonExistingKey_ReturnsFalse()
        {
            // Arrange
            var key = "nonexistent-key";

            // Setup cache miss
            _mockMemoryCache.Setup(cache => cache.TryGetValue(key, out It.Ref<object>.IsAny))
                .Returns(false);

            // Act
            var result = _cacheService.Refresh<object>(key);

            // Assert
            Assert.False(result);
            _mockMemoryCache.Verify(cache => cache.Remove(key), Times.Never);
            _mockMemoryCache.Verify(cache => cache.Set(
                It.IsAny<string>(), 
                It.IsAny<object>(), 
                It.IsAny<MemoryCacheEntryOptions>()), Times.Never);
        }

        [Fact]
        public async Task GetOrSetDeviceAsync_ValidDevice_ReturnsDevice()
        {
            // Arrange
            var deviceId = 1;
            var device = new CNCDevice { Id = deviceId, Name = "Test Device" };

            var factoryMock = new Mock<Func<Task<CNCDevice>>>();
            factoryMock.Setup(f => f()).ReturnsAsync(device);

            // Setup cache miss
            _mockMemoryCache.Setup(cache => cache.TryGetValue(It.IsAny<string>(), out It.Ref<CNCDevice>.IsAny))
                .Returns(false);

            // Setup cache set
            _mockMemoryCache.Setup(cache => cache.Set(
                It.IsAny<string>(), 
                device, 
                It.IsAny<MemoryCacheEntryOptions>()))
                .Callback<string, object, MemoryCacheEntryOptions>((k, v, o) => { });

            // Act
            var result = await _cacheService.GetOrSetDeviceAsync(deviceId, factoryMock.Object);

            // Assert
            Assert.Equal(device, result);
            factoryMock.Verify(f => f(), Times.Once);
        }

        [Fact]
        public void InvalidateDeviceCache_ValidDevice_RemovesRelatedKeys()
        {
            // Arrange
            var deviceId = 1;

            // Setup cache hits for removal
            _mockMemoryCache.Setup(cache => cache.TryGetValue(It.IsAny<string>(), out It.Ref<object>.IsAny))
                .Callback<string, out object>((k, out object v) => v = "some-value")
                .Returns(true);

            _mockMemoryCache.Setup(cache => cache.Remove(It.IsAny<string>()))
                .Verifiable();

            // Act
            _cacheService.InvalidateDeviceCache(deviceId, "additional:key");

            // Assert
            _mockMemoryCache.Verify(cache => cache.Remove("device:1"), Times.Once);
            _mockMemoryCache.Verify(cache => cache.Remove("device:status:1"), Times.Once);
            _mockMemoryCache.Verify(cache => cache.Remove(It.IsAny<string>()), Times.AtLeast(3));
        }

        [Fact]
        public async Task GetOrSetSystemConfigurationAsync_ValidFactory_ReturnsConfiguration()
        {
            // Arrange
            var config = new SystemConfiguration { DailyProductionTarget = 100 };

            var factoryMock = new Mock<Func<Task<SystemConfiguration>>>();
            factoryMock.Setup(f => f()).ReturnsAsync(config);

            // Setup cache miss
            _mockMemoryCache.Setup(cache => cache.TryGetValue(It.IsAny<string>(), out It.Ref<SystemConfiguration>.IsAny))
                .Returns(false);

            // Setup cache set
            _mockMemoryCache.Setup(cache => cache.Set(
                It.IsAny<string>(), 
                config, 
                It.IsAny<MemoryCacheEntryOptions>()))
                .Callback<string, object, MemoryCacheEntryOptions>((k, v, o) => { });

            // Act
            var result = await _cacheService.GetOrSetSystemConfigurationAsync(factoryMock.Object);

            // Assert
            Assert.Equal(config, result);
            factoryMock.Verify(f => f(), Times.Once);
        }

        [Fact]
        public void InvalidateSystemConfigCache_RemovesSystemConfigKeys()
        {
            // Arrange
            // Setup cache hits for removal
            _mockMemoryCache.Setup(cache => cache.TryGetValue(It.IsAny<string>(), out It.Ref<object>.IsAny))
                .Callback<string, out object>((k, out object v) => v = "some-value")
                .Returns(true);

            _mockMemoryCache.Setup(cache => cache.Remove(It.IsAny<string>()))
                .Verifiable();

            // Act
            _cacheService.InvalidateSystemConfigCache();

            // Assert
            _mockMemoryCache.Verify(cache => cache.Remove("config:system"), Times.Once);
            _mockMemoryCache.Verify(cache => cache.Remove("config:alerts"), Times.Once);
        }
    }
}