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
    /// <summary>
    /// Verifies that <see cref="CacheKeyClassifier.Classify"/> returns <see cref="CacheEnum.EntityType.Patients"/> when the cache key starts with the "patients" prefix.
    /// </summary>
    [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));
        }

    /// <summary>
    /// Verifies that <see cref="CacheKeyClassifier.Classify"/> returns <see cref="CacheEnum.EntityType.Patients"/> when the cache key starts with the "patients:xyz:" prefix.
    /// </summary>
    [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
    /// <summary>
    /// Verifies that <see cref="CacheKeyClassifier.Classify"/> correctly returns <see cref="CacheEnum.EntityType.Displays"/>
    /// for a cache key matching the "configDisplays:display:&lt;id&gt;:base" pattern.
    /// </summary>
    [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));
            
        }

    /// <summary>
    /// Verifies that <see cref="CacheKeyClassifier.Classify"/> returns <see cref="CacheEnum.EntityType.Displays"/> when the supplied cache key begins with the <c>configDisplays:</c> prefix.
    /// </summary>
    [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
    /// <summary>
    /// Verifies that <see cref="CacheKeyClassifier.Classify"/> returns <see cref="CacheEnum.EntityType.PumpObservations"/>
    /// when the provided cache key starts with the "pumpObs" prefix.
    /// </summary>
    [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
    /// <summary>
    /// Verifies that <see cref="CacheKeyClassifier.Classify"/> returns <see cref="CacheEnum.EntityType.Appointments"/> when the cache key starts with the "appointments:patient:" prefix.
    /// </summary>
    [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));
        }

    /// <summary>
    /// Verifies that <see cref="CacheKeyClassifier.Classify"/> returns <see cref="CacheEnum.EntityType.Appointments"/> when the provided cache key begins with the "appointments:PoC:" segment.
    /// </summary>
    [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
    /// <summary>
    /// Verifies that <see cref="CacheKeyClassifier.Classify"/> returns <see cref="CacheEnum.EntityType.GroupedObservations"/> for cache keys that begin with the "groupedObs:" prefix, such as those following the "groupedObs:HR:patient:{id}" pattern.
    /// </summary>
    [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
    /// <summary>
    /// Verifies that <see cref="CacheKeyClassifier.Classify"/> returns <see cref="CacheEnum.EntityType.ConfigObservations"/> when the supplied cache key starts with the <c>configObservations:</c> prefix.
    /// </summary>
    [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
    /// <summary>
    /// Verifies that the cache key classifier returns <see cref="CacheEnum.EntityType.Unknown"/> when a key has a prefix that is not recognized.
    /// </summary>
    [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));
        }

    /// <summary>
    /// Verifies that <see cref="CacheKeyClassifier.Classify"/> returns <see cref="CacheEnum.EntityType.Unknown"/> when the supplied cache key uses an unrecognized prefix.
    /// </summary>
    [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
    /// <summary>
    /// Verifies that <see cref="CacheKeyClassifier.Classify"/> returns <see cref="CacheEnum.EntityType.Patients"/> when the cache key is provided in upper case.
    /// </summary>
    [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));
        }

    /// <summary>
    /// Verifies that <see cref="CacheKeyClassifier.Classify"/> correctly returns the <see cref="CacheEnum.EntityType.Appointments"/> entity type when the supplied cache key contains a mixture of uppercase and lowercase characters, confirming that key classification is case-insensitive.
    /// </summary>
    [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
    /// <summary>
    /// Verifies that <see cref="CacheKeyTtl.ResolveForEntity"/> returns <c>null</c> when the global cache is disabled, regardless of the configured entity-specific cache mode and Redis TTL settings.
    /// </summary>
    [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);
        }

    /// <summary>
    /// Verifies that <see cref="CacheKeyTtl.ResolveForEntity"/> returns <c>null</c> for every supported entity type
    /// when caching is globally disabled via <see cref="CacheSettings.IsEnabled"/> set to <c>false</c>.
    /// </summary>
    [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
    /// <summary>
    /// Verifies that <see cref="CacheKeyTtl.ResolveForEntity"/> returns <c>null</c> when the
    /// entity mode is set to <see cref="CacheEnum.Mode.None"/>, regardless of the in-memory TTL configuration.
    /// </summary>
    [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
    /// <summary>
    /// Verifies that <see cref="CacheKeyTtl.ResolveForEntity"/> returns the entity-specific TTL
    /// (configured via <c>PatientsSeconds</c>) when the cache mode for the entity is set to
    /// <see cref="CacheEnum.Mode.Cache"/>, instead of falling back to the global TTL.
    /// </summary>
    [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
    /// <summary>
    /// Verifies that <see cref="CacheKeyTtl.ResolveForEntity"/> returns the entity-specific Redis TTL (AppointmentsSeconds)
    /// when the cache mode for the entity is set to <see cref="CacheEnum.Mode.Redis"/>.
    /// </summary>
    [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
    /// <summary>
    /// Verifies that <see cref="CacheKeyTtl.ResolveForEntity"/> falls back to the globally configured TTL
    /// (in seconds) when no entity-specific TTL is defined for the requested entity type.
    /// </summary>
    [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
    /// <summary>
    /// Verifies that <c>CacheKeyTtl.ResolveForEntity</c> returns <c>null</c> when the provided <c>CacheSettings</c> enable caching for the entity but no TTL values are configured (neither <c>GlobalSeconds</c> nor the entity-specific seconds).
    /// </summary>
    [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
    /// <summary>
    /// Verifies that <c>CacheKeys.PatientAppointmentsTodayKeyWithTtl</c> generates a cache key in the format
    /// "appointments:patient:{patientId}:{yyyyMMdd}" and returns a TTL of 120 seconds when caching is enabled
    /// and the Appointments mode is set to <see cref="CacheEnum.Mode.Cache"/>.
    /// </summary>
    /// <returns>A <see cref="void"/> method; assertions are used to validate the generated key and TTL values.</returns>
    [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
    /// <summary>
    /// Verifies that <see cref="CacheKeys.PocAppointmentsTodayKeyWithTtl"/> generates the expected cache key format
    /// ("appointments:PoC:{pocId}:{yyyyMMdd}") and returns the TTL value configured for point of care entries.
    /// </summary>
    [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
    /// <summary>
    /// Verifies that <see cref="CacheKeys.LatestObservationsKeyWithTtl"/> generates a deterministic cache key by sorting the supplied field names alphabetically, regardless of their input order.
    /// </summary>
    [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
    /// <summary>
    /// Verifies that the cache key generated by <see cref="CacheKeys.LatestObservationsKeyWithTtl"/> excludes empty strings and null entries from the provided observation types, producing a clean delimiter-separated key for the remaining valid values.
    /// </summary>
    [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
    /// <summary>
    /// Verifies that <see cref="CacheKeys.DisplayBaseKeyWithTtl"/> generates the expected base display cache key
    /// using the "configDisplays:display:{displayId}:base" format and returns the TTL value
    /// configured in the in-memory cache settings.
    /// </summary>
    [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
    /// <summary>
    /// Verifies that <see cref="CacheKeys.DisplayWithConfigKeyWithTtl"/> produces a cache key in the expected
    /// "configDisplays:display:{displayId}:config" format and that it differs from the base display key
    /// generated by <see cref="CacheKeys.DisplayBaseKeyWithTtl"/>.
    /// </summary>
    [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
    /// <summary>
    /// Verifies that <see cref="CacheKeys.PointOfCareBaseKeyWithTtl"/> generates a cache key with the expected
    /// "pointOfCare:{pocId}:base" format and applies the configured point-of-care TTL in seconds from the in-memory settings.
    /// </summary>
    [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
    /// <summary>
    /// Verifies that <see cref="CacheKeys.ConfigObservationsAllKeyWithTtl"/> generates the cache key
    /// "configObservations:all" consistently regardless of the supplied <see cref="CacheSettings"/>,
    /// including when TTL configuration and enabled state differ from defaults.
    /// </summary>
    [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
    /// <summary>
    /// Verifies that <see cref="CacheKeyPatterns.ForEntity"/> returns the correct cache key pattern for each supported
    /// <see cref="CacheEnum.EntityType"/>, mapping known entity types to their specific namespace patterns (e.g.,
    /// <c>patients:*</c>, <c>displays:*</c>, <c>pumpObs:*</c>, <c>appointments:*</c>, <c>groupedObs:*</c>,
    /// <c>configObservation:*</c>) and falling back to a wildcard pattern (<c>*</c>) for <see cref="CacheEnum.EntityType.Unknown"/>.
    /// </summary>
    /// <param name="entity">The entity type under test for which a cache key pattern is requested.</param>
    /// <param name="expectedPattern">The cache key pattern that <see cref="CacheKeyPatterns.ForEntity"/> is expected to return for the given entity type.</param>
    [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
    /// <summary>
    /// Verifies that <see cref="CacheKeyPatterns.ByPatient"/> generates a cache key pattern
    /// that embeds both the supplied prefix and patient identifier, producing the expected
    /// format of <c>{prefix}:*:{patientId}*</c>.
    /// </summary>
    [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));
        }

    /// <summary>
    /// Verifies that <c>DeleteByPatternAsync</c> removes only cache entries whose keys match the
    /// patient-specific pattern built via <c>CacheKeyPatterns.ByPatient</c>, leaving entries
    /// belonging to other patients and unrelated entity types intact, and reports the count of deleted keys.
    /// </summary>
    [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

}
