using adas_core.Application.Services.Caching;
using adas_core.Application.Services.Interfaces;
using adas_core.Domain.Enums;
using adas_core.Domain.Models.AppSettings;
using adas_core.Domain.Models.GroupedObservations;
using adas_core.Domain.Utils;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using MongoDB.Bson;
using Moq;

namespace adas_core.Test.Services;




public class FakeLockProvider : ILockProvider
{
    public Task<bool> AcquireAsync(string key, TimeSpan timeout) => Task.FromResult(true);
    public Task ReleaseAsync(string key) => Task.CompletedTask;
}

public class FakeLockManagerService : LockManagerService
{
    public FakeLockManagerService() : base(
        Mock.Of<ILogger<LockManagerService>>(),
        new FakeLockProvider())
    {
    }
}


public class FakeRedisService : RedisService, ICacheService
{
    public bool WasCalled { get; private set; }
    public string? LastKey { get; private set; }

    public FakeRedisService() : base(
        Options.Create(new CacheSettings()),
        Mock.Of<ILogger<RedisService>>(),
        new FakeLockManagerService())
    {
    }


    Task<T> ICacheService.GetOrSetObjectAsync<T>(
        string key,
        Func<Task<T>> factory,
        TimeSpan? ttl,
        CancellationToken cancellationToken)
    {
        WasCalled = true;
        LastKey = key;
        return factory();
    }

    Task<T> ICacheService.GetOrSetObjectAsync<T>(
        GroupedField groupedField,
        ObjectId patientId,
        Func<Task<T>> factory,
        TimeSpan? ttl,
        CancellationToken cancellationToken)
    {
        WasCalled = true;
        return factory();
    }
}


public class FakeCacheService : CacheService, ICacheService
{
    public bool WasCalled { get; private set; }
    public string? LastKey { get; private set; }

    public FakeCacheService() : base(new FakeLockManagerService())
    {
    }

    Task<T> ICacheService.GetOrSetObjectAsync<T>(
        string key,
        Func<Task<T>> factory,
        TimeSpan? ttl,
        CancellationToken cancellationToken)
    {
        WasCalled = true;
        LastKey = key;
        return factory();
    }

    Task<T> ICacheService.GetOrSetObjectAsync<T>(
        GroupedField groupedField,
        ObjectId patientId,
        Func<Task<T>> factory,
        TimeSpan? ttl,
        CancellationToken cancellationToken)
    {
        WasCalled = true;
        return factory();
    }
}


public class FakeNoCacheService : NoCacheService, ICacheService
{
    public bool WasCalled { get; private set; }
    public string? LastKey { get; private set; }

    Task<T> ICacheService.GetOrSetObjectAsync<T>(
        string key,
        Func<Task<T>> factory,
        TimeSpan? ttl,
        CancellationToken cancellationToken)
    {
        WasCalled = true;
        LastKey = key;
        return factory();
    }

    Task<T> ICacheService.GetOrSetObjectAsync<T>(
        GroupedField groupedField,
        ObjectId patientId,
        Func<Task<T>> factory,
        TimeSpan? ttl,
        CancellationToken cancellationToken)
    {
        WasCalled = true;
        return factory();
    }
}


[TestFixture]
public class CacheDispatcherTest
{
    private FakeRedisService _redisServiceFake = null!;
    private FakeCacheService _memoryServiceFake = null!;
    private FakeNoCacheService _noopServiceFake = null!;
    private CacheSettings _cacheSettings = null!;
    private CacheDispatcher _cacheDispatcher = null!;

    [SetUp]
    public void SetUp()
    {
        _redisServiceFake = new FakeRedisService();
        _memoryServiceFake = new FakeCacheService();
        _noopServiceFake = new FakeNoCacheService();

        _cacheSettings = new CacheSettings();

        _cacheDispatcher = new CacheDispatcher(
            _redisServiceFake,
            _memoryServiceFake,
            _noopServiceFake,
            _cacheSettings);
    }

    #region TC-23

    [Test]
    public async Task GetOrSetObjectAsync_UsesRedisService_WhenPatientsModeIsRedis()
    {
        // Arrange
        var key = "patients:latestObs:abc";
        var expectedResult = new { Name = "TestPatient" };
        Func<Task<object>> factory = () => Task.FromResult<object>(expectedResult);

        _cacheSettings.Patients = CacheEnum.Mode.Redis;

        // Act
        var result = await _cacheDispatcher.GetOrSetObjectAsync<object>(key, factory);

        // Assert
        Assert.That(_redisServiceFake.WasCalled,  Is.True,        "RedisService debería haber sido invocado");
        Assert.That(_redisServiceFake.LastKey,     Is.EqualTo(key));
        Assert.That(_memoryServiceFake.WasCalled,  Is.False,       "CacheService NO debería haber sido invocado");
        Assert.That(_noopServiceFake.WasCalled,    Is.False,       "NoCacheService NO debería haber sido invocado");
    }
    #endregion

    #region TC-24
    [Test]
    public async Task GetOrSetObjectAsync_UsesCacheService_WhenAppointmentsModeIsCache()
    {
        // Arrange
        var key = "appointments:patient:id:20260224";
        var expectedResult = new { Name = "TestAppointment" };
        Func<Task<object>> factory = () => Task.FromResult<object>(expectedResult);

        _cacheSettings.Appointments = CacheEnum.Mode.Cache;

        // Act
        var result = await _cacheDispatcher.GetOrSetObjectAsync<object>(key, factory);

        // Assert
        Assert.That(_memoryServiceFake.WasCalled, Is.True,         "CacheService debería haber sido invocado");
        Assert.That(_memoryServiceFake.LastKey,    Is.EqualTo(key));
        Assert.That(_redisServiceFake.WasCalled, Is.False,        "RedisService NO debería haber sido invocado");
        Assert.That(_noopServiceFake.WasCalled,  Is.False,        "NoCacheService NO debería haber sido invocado");
    }
    #endregion

    #region TC-25
    [Test]
    public async Task GetOrSetObjectAsync_UsesNoCacheService_WhenPumpObservationsModeIsNone()
    {
        // Arrange
        var key = "pumpObs:latest:abc:10";
        var expectedResult = new { Name = "TestPump" };
        Func<Task<object>> factory = () => Task.FromResult<object>(expectedResult);

        _cacheSettings.PumpObservations = CacheEnum.Mode.None;

        // Act
        var result = await _cacheDispatcher.GetOrSetObjectAsync<object>(key, factory);

        // Assert
        Assert.That(_noopServiceFake.WasCalled,   Is.True,         "NoCacheService debería haber sido invocado");
        Assert.That(_noopServiceFake.LastKey,    Is.EqualTo(key));
        Assert.That(_redisServiceFake.WasCalled, Is.False,        "RedisService NO debería haber sido invocado");
        Assert.That(_memoryServiceFake.WasCalled, Is.False,       "CacheService NO debería haber sido invocado");
    }
    #endregion

    #region TC-26
    [Test]
    public async Task GetOrSetObjectAsync_UsesCacheService_WhenKeyIsUnrecognized()
    {
        // Arrange
        var key = "unknown:prefix:key";
        var expectedResult = new { Name = "TestUnknown" };
        Func<Task<object>> factory = () => Task.FromResult<object>(expectedResult);

        // Act
        var result = await _cacheDispatcher.GetOrSetObjectAsync<object>(key, factory);

        // Assert - Unknown keys fallback to InMemory (Cache)
        Assert.That(_memoryServiceFake.WasCalled,  Is.True,        "CacheService debería haber sido invocado como fallback");
        Assert.That(_memoryServiceFake.LastKey,    Is.EqualTo(key));
        Assert.That(_redisServiceFake.WasCalled,   Is.False,       "RedisService NO debería haber sido invocado");
        Assert.That(_noopServiceFake.WasCalled,     Is.False,       "NoCacheService NO debería haber sido invocado");
    }

    [Test]
    public void Classify_ReturnsUnknown_ForUnrecognizedPrefix()
    {
        // Arrange
        var key = "unknown:prefix:key";

        // Act
        var result = CacheKeyClassifier.Classify(key);

        // Assert
        Assert.That(result, Is.EqualTo(CacheEnum.EntityType.Unknown));
    }
    #endregion
}
