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


namespace adas_core.Test.Utilities;

[TestFixture]
public class CacheKeyClassifierTest
{
   #region TC-01
    [Test]
    public void Classify_ReturnsPatients_ForKeyStartingWithPatients()
    {
        
        // Arrange
        var key = "patients:latestObs:abc123:HR:0";

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

        // Assert
        Assert.That(result, Is.EqualTo(CacheEnum.EntityType.Patients));
    }

    [Test]
    public void Classify_ReturnsPatients_ForKeyStartingWithPatientsXYZ()
    {
        // Arrange
        var key = "patients:xyz:any:key";

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

        // Assert
        Assert.That(result, Is.EqualTo(CacheEnum.EntityType.Patients));
    }

    

    #endregion

    #region TC-02
    [Test]
    public void Classify_ReturnsDisplays_ForKeyStartingWithConfigDisplaysBase()
    {
        // Arrange
        //var key2 = "configDisplays:display:id:base";
        var key = "configDisplays:display:690b341c4a349b5898898241:base ";

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

        // Assert
        Assert.That(result, Is.EqualTo(CacheEnum.EntityType.Displays));
        
    }

    [Test]
    public void Classify_ReturnsDisplays_ForKeyStartingWithConfigDisplaysConfig()
    {
        // Arrange
        var key = "configDisplays:display:id:config";

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

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

    #region TC-03
    [Test]
    public void Classify_ReturnsPumpObservations_ForKeyStartingWithPumpObs()
    {
        // Arrange
        var key = "pumpObs:latest:patientId:10";

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

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


    #region TC-04
    [Test]
    public void Classify_ReturnsAppointments_ForKeyStartingWithAppointmentsPatient()
    {
        // Arrange
        var key = "appointments:patient:id:20260224";

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

        // Assert
        Assert.That(result, Is.EqualTo(CacheEnum.EntityType.Appointments));
    }

    [Test]
    public void Classify_ReturnsAppointments_ForKeyStartingWithAppointmentsPoc()
    {
        // Arrange
        var key = "appointments:PoC:pocId:20260224";

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

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

    #region TC-05
    [Test]
    public void Classify_ReturnsGroupedObservations_ForKeyStartingWithGroupedObs()
    {
        // Arrange
        var key = "groupedObs:HR:patient:abc123";

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

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

    #region TC-06
    [Test]
    public void Classify_ReturnsConfigObservations_ForKeyStartingWithConfigObservations()
    {
        // Arrange
        var key = "configObservations:all";

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

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

    #region TC-07
    [Test]
    public void Classify_ReturnsUnknown_ForUnrecognizedPrefixDiagnostics()
    {
        // Arrange
        var key = "diagnostics:patient:abc";

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

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

    [Test]
    public void Classify_ReturnsUnknown_ForUnrecognizedPrefixRandom()
    {
        // Arrange
        var key = "random:key";

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

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

    #region TC-08
    [Test]
    public void Classify_ReturnsPatients_ForKeyInUpperCase()
    {
        // Arrange
        var key = "PATIENTS:latestObs:abc";

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

        // Assert
        Assert.That(result, Is.EqualTo(CacheEnum.EntityType.Patients));
    }

    [Test]
    public void Classify_ReturnsAppointments_ForKeyInMixedCase()
    {
        // Arrange
        var key = "Appointments:PoC:id:date";

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

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

    #region TC-09
    [Test]
    public void ResolveForEntity_ReturnsNull_WhenGlobalCacheDisabled()
    {
        // Arrange
        var settings = new CacheSettings
        {
            IsEnabled = false,
            Patients = CacheEnum.Mode.Redis,
            Redis = new RedisSettings
            {
                Ttl = new TtlSettings { PatientsSeconds = 600 }
            }
        };

        // Act
        var result = CacheKeyTtl.ResolveForEntity(settings, CacheEnum.EntityType.Patients);

        // Assert
        Assert.That(result, Is.Null);
    }

    [Test]
    public void ResolveForEntity_ReturnsNull_WhenGlobalDisabled_ForAllEntities()
    {
        // Arrange
        var entities = new[]
        {
            CacheEnum.EntityType.Patients,
            CacheEnum.EntityType.Displays,
            CacheEnum.EntityType.PumpObservations,
            CacheEnum.EntityType.Appointments,
            CacheEnum.EntityType.PontOfCare,
            CacheEnum.EntityType.GroupedObservations,
            CacheEnum.EntityType.PatientObservations,
            CacheEnum.EntityType.ConfigObservations
        };

        var settings = new CacheSettings { IsEnabled = false };

        // Act & Assert
        foreach (var entity in entities)
        {
            var result = CacheKeyTtl.ResolveForEntity(settings, entity);
            Assert.That(result, Is.Null, $"Expected null for entity {entity}");
        }
    }
    #endregion

    #region TC-10
    [Test]
    public void ResolveForEntity_ReturnsNull_WhenEntityModeIsNone()
    {
        // Arrange
        var settings = new CacheSettings
        {
            IsEnabled = true,
            PumpObservations = CacheEnum.Mode.None,
            InMemory = new InMemorySettings
            {
                Ttl = new TtlSettings { PumpObservationsSeconds = 300 }
            }
        };

        // Act
        var result = CacheKeyTtl.ResolveForEntity(settings, CacheEnum.EntityType.PumpObservations);

        // Assert
        Assert.That(result, Is.Null);
    }
    #endregion

    #region TC-11
    [Test]
    public void ResolveForEntity_UsesEntitySpecificTtl_WhenModeIsCache()
    {
        // Arrange
        var settings = new CacheSettings
        {
            IsEnabled = true,
            Patients = CacheEnum.Mode.Cache,
            InMemory = new InMemorySettings
            {
                Ttl = new TtlSettings
                {
                    GlobalSeconds = 3600,
                    PatientsSeconds = 600
                }
            }
        };

        // Act
        var result = CacheKeyTtl.ResolveForEntity(settings, CacheEnum.EntityType.Patients);

        // Assert
        Assert.That(result, Is.EqualTo(TimeSpan.FromSeconds(600)));
        Assert.That(result, Is.Not.EqualTo(TimeSpan.FromSeconds(3600)));
    }
    #endregion

    #region TC-12
    [Test]
    public void ResolveForEntity_UsesRedisTtl_WhenModeIsRedis()
    {
        // Arrange
        var settings = new CacheSettings
        {
            IsEnabled = true,
            Appointments = CacheEnum.Mode.Redis,
            Redis = new RedisSettings
            {
                Ttl = new TtlSettings
                {
                    GlobalSeconds = 3600,
                    AppointmentsSeconds = 900
                }
            }
        };

        // Act
        var result = CacheKeyTtl.ResolveForEntity(settings, CacheEnum.EntityType.Appointments);

        // Assert
        Assert.That(result, Is.EqualTo(TimeSpan.FromSeconds(900)));
    }
    #endregion

    #region TC-13
    [Test]
    public void ResolveForEntity_UsesGlobalSeconds_WhenNoEntitySpecificTtl()
    {
        // Arrange
        var settings = new CacheSettings
        {
            IsEnabled = true,
            Patients = CacheEnum.Mode.Cache,
            InMemory = new InMemorySettings
            {
                Ttl = new TtlSettings
                {
                    GlobalSeconds = 3600
                    // No PatientsSeconds defined
                }
            }
        };

        // Act
        var result = CacheKeyTtl.ResolveForEntity(settings, CacheEnum.EntityType.Patients);

        // Assert
        Assert.That(result, Is.EqualTo(TimeSpan.FromSeconds(3600)));
    }
    #endregion

    #region TC-14
    [Test]
    public void ResolveForEntity_ReturnsNull_WhenNoTtlConfigured()
    {
        // Arrange
        var settings = new CacheSettings
        {
            IsEnabled = true,
            Patients = CacheEnum.Mode.Cache,
            InMemory = new InMemorySettings
            {
                Ttl = new TtlSettings()
                // No GlobalSeconds, no PatientsSeconds
            }
        };

        // Act
        var result = CacheKeyTtl.ResolveForEntity(settings, CacheEnum.EntityType.Patients);

        // Assert
        Assert.That(result, Is.Null);
    }
    #endregion

    #region TC-15
    [Test]
    public void PatientAppointmentsTodayKeyWithTtl_GeneratesCorrectFormat()
    {
        // Arrange
        var patientId = ObjectId.GenerateNewId();
        var today = DateTime.UtcNow.ToString("yyyyMMdd");
        var settings = new CacheSettings
        {
            IsEnabled = true,
            Appointments = CacheEnum.Mode.Cache,
            InMemory = new InMemorySettings
            {
                Ttl = new TtlSettings { AppointmentsSeconds = 120 }
            }
        };

        // Act
        var (key, ttl) = CacheKeys.PatientAppointmentsTodayKeyWithTtl(settings, patientId);

        // Assert
        Assert.That(key, Is.EqualTo($"appointments:patient:{patientId}:{today}"));
        Assert.That(ttl, Is.EqualTo(TimeSpan.FromSeconds(120)));
    }
    #endregion

    #region TC-16
    [Test]
    public void PocAppointmentsTodayKeyWithTtl_GeneratesCorrectFormat()
    {
        // Arrange
        var pocId = ObjectId.GenerateNewId();
        var today = DateTime.UtcNow.ToString("yyyyMMdd");
        var settings = new CacheSettings
        {
            IsEnabled = true,
            PointOfCares = CacheEnum.Mode.Cache,
            InMemory = new InMemorySettings
            {
                Ttl = new TtlSettings { PointOfCaresSeconds = 180 }
            }
        };

        // Act
        var (key, ttl) = CacheKeys.PocAppointmentsTodayKeyWithTtl(settings, pocId);

        // Assert
        Assert.That(key, Is.EqualTo($"appointments:PoC:{pocId}:{today}"));
        Assert.That(ttl, Is.EqualTo(TimeSpan.FromSeconds(180)));
    }
    #endregion

    #region TC-17
    [Test]
    public void LatestObservationsKeyWithTtl_SortsFieldNamesAlphabetically()
    {
        // Arrange
        var patientId = ObjectId.GenerateNewId();
        var settings = new CacheSettings();

        // Act
        var (key1, _) = CacheKeys.LatestObservationsKeyWithTtl(
            settings, patientId, new[] { "Systolic", "HR", "Diastolic" });

        var (key2, _) = CacheKeys.LatestObservationsKeyWithTtl(
            settings, patientId, new[] { "HR", "Diastolic", "Systolic" });

        // Assert
        Assert.That(key1, Is.EqualTo(key2));
        Assert.That(key1, Does.Contain("Diastolic|HR|Systolic"));
    }
    #endregion

    #region TC-18
    [Test]
    public void LatestObservationsKeyWithTtl_ExcludesEmptyFields()
    {
        // Arrange
        var patientId = ObjectId.GenerateNewId();
        var settings = new CacheSettings();

        // Act
        var (key, _) = CacheKeys.LatestObservationsKeyWithTtl(
            settings, patientId, new[] { "HR", "", "Diastolic", null! });

        // Assert
        Assert.That(key.Contains("||"), Is.False);
        Assert.That(key, Does.Contain("Diastolic|HR"));
    }
    #endregion

    #region TC-19
    [Test]
    public void DisplayBaseKeyWithTtl_GeneratesCorrectFormat()
    {
        // Arrange
        var displayId = ObjectId.GenerateNewId();
        var settings = new CacheSettings
        {
            IsEnabled = true,
            Displays = CacheEnum.Mode.Cache,
            InMemory = new InMemorySettings
            {
                Ttl = new TtlSettings { DisplaysSeconds = 300 }
            }
        };

        // Act
        var (key, ttl) = CacheKeys.DisplayBaseKeyWithTtl(settings, displayId);

        // Assert
        Assert.That(key, Is.EqualTo($"configDisplays:display:{displayId}:base"));
        Assert.That(ttl, Is.EqualTo(TimeSpan.FromSeconds(300)));
    }
    #endregion

    #region TC-20
    [Test]
    public void DisplayWithConfigKeyWithTtl_GeneratesCorrectFormat()
    {
        // Arrange
        var displayId = ObjectId.GenerateNewId();
        var settings = new CacheSettings();

        // Act
        var (keyBase, _) = CacheKeys.DisplayBaseKeyWithTtl(settings, displayId);
        var (keyConfig, _) = CacheKeys.DisplayWithConfigKeyWithTtl(settings, displayId);

        // Assert
        Assert.That(keyConfig, Is.EqualTo($"configDisplays:display:{displayId}:config"));
        Assert.That(keyBase, Is.Not.EqualTo(keyConfig));
    }
    #endregion

    #region TC-21
    [Test]
    public void PointOfCareBaseKeyWithTtl_GeneratesCorrectFormat()
    {
        // Arrange
        var pocId = ObjectId.GenerateNewId();
        var settings = new CacheSettings
        {
            IsEnabled = true,
            PointOfCares = CacheEnum.Mode.Cache,
            InMemory = new InMemorySettings
            {
                Ttl = new TtlSettings { PointOfCaresSeconds = 240 }
            }
        };

        // Act
        var (key, ttl) = CacheKeys.PointOfCareBaseKeyWithTtl(settings, pocId);

        // Assert
        Assert.That(key, Is.EqualTo($"pointOfCare:{pocId}:base"));
        Assert.That(ttl, Is.EqualTo(TimeSpan.FromSeconds(240)));
    }
    #endregion

    #region TC-22
    [Test]
    public void ConfigObservationsAllKeyWithTtl_GeneratesCorrectFormat()
    {
        // Arrange
        var settings1 = new CacheSettings();
        var settings2 = new CacheSettings
        {
            IsEnabled = true,
            ConfigObservations = CacheEnum.Mode.Cache,
            InMemory = new InMemorySettings
            {
                Ttl = new TtlSettings { ConfigObservationsSeconds = 600 }
            }
        };

        // Act
        var (key1, _) = CacheKeys.ConfigObservationsAllKeyWithTtl(settings1);
        var (key2, _) = CacheKeys.ConfigObservationsAllKeyWithTtl(settings2);

        // Assert
        Assert.That(key1, Is.EqualTo("configObservations:all"));
        Assert.That(key2, Is.EqualTo("configObservations:all"));
        Assert.That(key1, Is.EqualTo(key2));
    }
    #endregion
    
    #region TC-59
    [TestCase(CacheEnum.EntityType.Patients,            "patients:*")]
    [TestCase(CacheEnum.EntityType.Displays,            "displays:*")]
    [TestCase(CacheEnum.EntityType.PumpObservations,    "pumpObs:*")]
    [TestCase(CacheEnum.EntityType.Appointments,        "appointments:*")]
    [TestCase(CacheEnum.EntityType.GroupedObservations, "groupedObs:*")]
    [TestCase(CacheEnum.EntityType.ConfigObservations,  "configObservation:*")]
    [TestCase(CacheEnum.EntityType.Unknown,             "*")]
    public void ForEntity_ReturnsCorrectPatternForEachEntityType(
        CacheEnum.EntityType entity, string expectedPattern)
    {
        var result = CacheKeyPatterns.ForEntity(entity);

        Assert.That(result, Is.EqualTo(expectedPattern));
    }
    #endregion

    #region TC-60
    [Test]
    public void ByPatient_ReturnsPatternContainingPrefixAndPatientId()
    {
        const string prefix = "patients:latestObs";
        const string patientId = "abc123";

        var pattern = CacheKeyPatterns.ByPatient(prefix, patientId);

        Assert.That(pattern, Is.EqualTo($"{prefix}:*:{patientId}*"));
        Assert.That(pattern, Does.Contain(prefix));
        Assert.That(pattern, Does.Contain(patientId));
    }

    [Test]
    public async Task ByPatient_DeleteByPatternAsync_OnlyRemovesMatchingPatientKeys()
    {
        var lockMgr = new LockManagerService(
            new Mock<ILogger<LockManagerService>>().Object,
            new InMemoryLockProvider());
        var svc = new CacheService(lockMgr);

        const string prefix = "patients:latestObs";
        const string patientId = "abc123";
        const string otherId = "def456";

        var pattern = CacheKeyPatterns.ByPatient(prefix, patientId);
        var effectiveSubstring = pattern.Replace("*", "");

        var matchingKey = $"{effectiveSubstring}:HR:0";
        var nonMatchingKey = $"patients:latestObs::{otherId}:HR:0";
        var differentEntityKey = $"appointments::{patientId}:data";

        await svc.SetObjectAsync(matchingKey, "val1");
        await svc.SetObjectAsync(nonMatchingKey, "val2");
        await svc.SetObjectAsync(differentEntityKey, "val3");

        var deleted = await svc.DeleteByPatternAsync(pattern);

        Assert.That(deleted, Is.EqualTo(1));
        Assert.That(await svc.GetObjectAsync<string>(matchingKey), Is.Null);
        Assert.That(await svc.GetObjectAsync<string>(nonMatchingKey), Is.EqualTo("val2"));
        Assert.That(await svc.GetObjectAsync<string>(differentEntityKey), Is.EqualTo("val3"));
    }
    #endregion

}
