using System.Security.Claims;
using adas_core.Application.Exceptions;
using adas_core.Application.Repositories.Interfaces;
using adas_core.Application.Services;
using adas_core.Application.Services.Interfaces;
using adas_core.Domain.Enums;
using adas_core.Domain.Models;
using adas_core.Domain.Models.AppSettings;
using adas_core.Domain.Models.MongoModels;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using MongoDB.Bson;
using Moq;
using Options = Microsoft.Extensions.Options.Options;

namespace adas_core.Test.Services;

[TestFixture]
public class ConfigObservationServiceTest
{
    private ConfigObservationService _service = null!;
    private Mock<IConfigObservationRepository> _repo = null!;
    private Mock<IUnitService> _unitSvc = null!;
    private Mock<ILocalAuditService> _auditSvc = null!;
    private Mock<IHttpContextAccessor> _http = null!;
    private Mock<ICacheService> _cache = null!;
    private Mock<ILogger<ConfigObservationService>> _logger = null!;

    private IOptions<ApiSettings> _settings = null!;
    private IOptions<CacheSettings> _cacheSettings = null!;

    private static readonly ObjectId Id = ObjectId.GenerateNewId();

    private static readonly ConfigObservation ConfigList = new()
    {
        Id = Id,
        Name = "FC",
        Code = "147842",
        CodingSystem = "MDC",
        ParentCode = "69965",
        ParentName = "MDC_DEV_MON_PHYSIO_MULTI_PARAM_MDS",
        MinAlert = 60,
        MaxAlert = 100,
        ForceAlert = true
    };

    private readonly List<ConfigObservation> _allConfigList =
    [
        new() { Id = ObjectId.GenerateNewId(), Name = "Hemoglobina", Code = "12345", CodingSystem = "SNM" },
        new() { Id = ObjectId.GenerateNewId(), Name = "Glucemia", Code = "54321", CodingSystem = "SNM", OriginalName = "GLU" },
        new() { Id = ObjectId.GenerateNewId(), Name = "Sodio", OriginalName = "Sodio" },
        new()
        {
            Id = ObjectId.GenerateNewId(),
            Name = "ph",
            Code = "555",
            CodingSystem = "MG4",
            ParentCode = "3333",
            ParentCodingSystem = "SNM"
        },
        new()
        {
            Id = ObjectId.GenerateNewId(),
            Name = "SOFA",
            Code = "278061009",
            OriginalName = "SOFA",
            CodingSystem = "SNM",
            MinAlert = 10,
            MinWarn = 14
        },
        ConfigList,
        new() { Id = ObjectId.GenerateNewId(), Name = "Alarm_BlueCode", CodingSystem = "ADAS_EVENT" }
    ];

    [SetUp]
    public void Setup()
    {
        _repo = new Mock<IConfigObservationRepository>();
        _unitSvc = new Mock<IUnitService>();
        _auditSvc = new Mock<ILocalAuditService>();
        _http = new Mock<IHttpContextAccessor>();
        _cache = new Mock<ICacheService>();
        _logger = new Mock<ILogger<ConfigObservationService>>();

        var user = new ClaimsPrincipal(
            new ClaimsIdentity([new Claim(ClaimTypes.Name, "TestUser")], "mock"));

        _http.Setup(x => x.HttpContext).Returns(new DefaultHttpContext { User = user });

        _settings = Options.Create(new ApiSettings
        {
            ConfigObservation = new ConfigObservationSettings
            {
                IgnoreUnknownObservation = false,
                Refresh = null
            },
        });

        _cacheSettings = Options.Create(new CacheSettings());

        // KEY: mock cache to execute repository calls
        _cache.Setup(c => c.GetOrSetObjectAsync(
                It.IsAny<string>(),
                It.IsAny<Func<Task<ICollection<ConfigObservation>>>>(),
                It.IsAny<TimeSpan?>(),
                It.IsAny<CancellationToken>()))
            .Returns((string _, Func<Task<ICollection<ConfigObservation>>> f, TimeSpan? __, CancellationToken ___) => f());

        _cache.Setup(c => c.GetOrSetObjectAsync(
                It.IsAny<string>(),
                It.IsAny<Func<Task<ConfigObservation>>>(),
                It.IsAny<TimeSpan?>(),
                It.IsAny<CancellationToken>()))
            .Returns((string _, Func<Task<ConfigObservation>> f, TimeSpan? __, CancellationToken ___) => f());

        _service = new ConfigObservationService(
            _repo.Object,
            _settings,
            _cacheSettings,
            _logger.Object,
            _unitSvc.Object,
            _http.Object,
            _auditSvc.Object,
            _cache.Object);

        _repo.Setup(x => x.FindAll()).ReturnsAsync([ConfigList]);
    }

    // ---------------------------------------------------------
    // TESTS 
    // ---------------------------------------------------------

    [Test]
    public async Task Get_config_observation_item_by_codingSystem_only_return_null()
    {
        var obs = new PatientObservation
        {
            CodingSystem = "ADAS_EVENT",
            Code = "Pump_X",
            Name = "Alarm_Pump_X"
        };

        var result = await _service.Get(obs);

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

    [Test]
    public async Task Get_config_observation_item_by_code_and_codingSystem()
    {
        _repo.Setup(x => x.FindAll()).ReturnsAsync(_allConfigList);

        var obs = new BasePatientObservation
        {
            Name = "Hemo^GBr",
            Code = "12345",
            CodingSystem = "SNM"
        };

        var result = await _service.Get(obs);

        Assert.That(result, Is.Not.Null);
        Assert.That(result!.Name, Is.EqualTo("Hemoglobina"));
    }

    [Test]
    public async Task Get_config_observation_item_by_name()
    {
        _repo.Setup(x => x.FindAll()).ReturnsAsync(_allConfigList);

        var obs = new BasePatientObservation { Name = "Hemoglobina" };

        var result = await _service.Get(obs);

        Assert.That(result!.Name, Is.EqualTo("Hemoglobina"));
    }

    [Test]
    public async Task Get_config_observation_item_by_name_not_exist_returns_null()
    {
        _repo.Setup(x => x.FindAll()).ReturnsAsync(_allConfigList);

        var obs = new BasePatientObservation { Name = "XXX" };

        var result = await _service.Get(obs);

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

    [Test]
    public async Task Get_config_by_code_and_parent()
    {
        _repo.Setup(x => x.FindAll()).ReturnsAsync(_allConfigList);

        var obs = new BasePatientObservation
        {
            Code = "555",
            CodingSystem = "MG4",
            ParentData = new ParentDataClass
            {
                Code = "3333",
                CodingSystem = "SNM"
            }
        };

        var result = await _service.Get(obs);

        Assert.That(result!.Name, Is.EqualTo("ph"));
    }

    [Test]
    public async Task Map_unknown_ignore_true_returns_null()
    {
        _settings.Value.ConfigObservation!.IgnoreUnknownObservation = true;
        _repo.Setup(x => x.FindAll()).ReturnsAsync([]);

        var result = await _service.Map(new BasePatientObservation { Name = "XX" });

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

    [Test]
    public async Task Map_unknown_ignore_false_returns_obs()
    {
        _settings.Value.ConfigObservation!.IgnoreUnknownObservation = false;
        _repo.Setup(x => x.FindAll()).ReturnsAsync([]);

        var obs = new BasePatientObservation { Name = "XX" };

        var result = await _service.Map(obs);

        Assert.That(result, Is.EqualTo(obs));
    }

    [Test]
    public async Task Map_threshold_Ok()
    {
        _repo.Setup(x => x.FindAll()).ReturnsAsync(_allConfigList);

        var obs = new PatientObservation
        {
            Name = "SOFA",
            CodingSystem = "SNM",
            Value = 14
        };

        var result = await _service.Map(obs);

        Assert.That(result!.Status, Is.EqualTo(StatusEnum.Type.Ok));
    }

    [Test]
    public async Task Map_threshold_Warning()
    {
        _repo.Setup(x => x.FindAll()).ReturnsAsync(_allConfigList);

        var obs = new PatientObservation
        {
            Name = "SOFA",
            CodingSystem = "SNM",
            Value = 13
        };

        var result = await _service.Map(obs);

        Assert.That(result!.Status, Is.EqualTo(StatusEnum.Type.Warning));
    }

    [Test]
    public async Task Map_threshold_Alert()
    {
        _repo.Setup(x => x.FindAll()).ReturnsAsync(_allConfigList);

        var obs = new PatientObservation
        {
            Name = "SOFA",
            CodingSystem = "SNM",
            Value = 9
        };

        var result = await _service.Map(obs);

        Assert.That(result!.Status, Is.EqualTo(StatusEnum.Type.Alert));
    }

    [Test]
    public async Task Map_parent_ok()
    {
        _repo.Setup(x => x.FindById(Id)).ReturnsAsync(ConfigList);

        var fc = new PatientObservation
        {
            Id = Id,
            Name = "FC",
            Value = 70
        };

        var result = await _service.Map(fc);

        Assert.That(result!.Status, Is.EqualTo(StatusEnum.Type.Ok));
    }

    [Test]
    public async Task UpdateConfig_ok()
    {
        var newCfg = new ConfigObservation
        {
            Id = Id,
            Name = "New",
            Code = "X",
            CodingSystem = "S"
        };

        _repo.Setup(r => r.FindById(Id)).ReturnsAsync(ConfigList);
        _repo.Setup(r => r.Update(It.IsAny<ConfigObservation>())).ReturnsAsync(newCfg);

        var result = await _service.UpdateConfig(newCfg);

        Assert.That(result!.Name, Is.EqualTo("New"));
    }

    [Test]
    public async Task UpdateConfig_notfound()
    {
        _repo
            .Setup(r => r.FindById(Id))
            .ReturnsAsync((ConfigObservation?)null);

        Func<Task> act = () => _service.UpdateConfig(new ConfigObservation { Id = Id });

        Assert.ThrowsAsync<NotFoundException>(act);
    }


    [Test]
    public async Task CreateConfig_ok()
    {
        _repo.Setup(r => r.InsertOneAsyncAndReturn(ConfigList)).Returns(Task.FromResult(ConfigList));

        var result = await _service.CreateConfig(ConfigList);

        Assert.That(result, Is.EqualTo(ConfigList));
    }

    [Test]
    public void CreateConfig_duplicate_throws()
    {
        _repo.Setup(r => r.FindById(It.IsAny<ObjectId>())).ReturnsAsync(ConfigList);


        Func<Task> act = () => _service.CreateConfig(ConfigList);

        Assert.ThrowsAsync<BadRequestException>(act);

    }

    [Test]
    public async Task RemoveConfigItem_ok()
    {
        var deleted = new ConfigObservation { Id = ObjectId.GenerateNewId(), Name = "X" };

        _repo.Setup(r => r.FindById(It.IsAny<ObjectId>())).ReturnsAsync(ConfigList);
        _repo.Setup(r => r.Delete(It.IsAny<ObjectId>())).ReturnsAsync(deleted);

        var result = await _service.RemoveConfigItem(ObjectId.GenerateNewId());

        Assert.That(result, Is.EqualTo(deleted));
    }

    [Test]
    public async Task RemoveConfigItem_null()
    {
        _repo.Setup(r => r.FindById(Id)).ReturnsAsync(ConfigList);
        _repo.Setup(r => r.Delete(Id)).ReturnsAsync((ConfigObservation?)null);

        var result = await _service.RemoveConfigItem(Id);

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