using C4IT.FASD.Base;
using C4IT.FASD.Cockpit.Communication;
using FasdDesktopUi.Basics.Services.RelationService;
using NSubstitute;
using ServiceUnderTest = FasdDesktopUi.Basics.Services.RelationService;

namespace F4SD.Cockpit.Client.Test.Basics.Sevices.RelationService;

public class RelationServiceTest
{
    private readonly ServiceUnderTest.RelationService _relationService = new();
    private readonly cFasdCockpitCommunicationBase _communication = Substitute.For<cFasdCockpitCommunicationBase>();

    public RelationServiceTest()
    {
        cFasdCockpitCommunicationBase.Instance = _communication;
    }

    [Fact(Timeout = 2_000)]
    public async Task LoadedRelation_IsIn_GetLoadedRelations_Test()
    {
        // Arrange
        Guid taskGuid = Guid.NewGuid();
        cF4sdApiSearchResultRelation mockRelation = new()
        {
            id = Guid.NewGuid(),
            Type = enumF4sdSearchResultClass.User
        };

        _communication.StartGatheringRelations(Arg.Any<IEnumerable<cFasdApiSearchResultEntry>>(), Arg.Any<CancellationToken>())
            .Returns(Task.FromResult(new cF4sdStagedSearchResultRelationTaskId() { Id = taskGuid }));
        _communication.GetStagedRelations(taskGuid, Arg.Any<CancellationToken>())
            .Returns(Task.FromResult(new cF4sdStagedSearchResultRelations()
            {
                PendingInformationClasses = [],
                Relations = [mockRelation]
            }));

        TaskCompletionSource tcs = new(TaskCreationOptions.RunContinuationsAsynchronously);
        _relationService.RelationsFound += OnRelationsFound;

        try
        {
            // Act
            await _relationService.LoadRelationsAsync(null, TestContext.Current.CancellationToken);

            // Assert
            await tcs.Task;

            var actual = _relationService.GetLoadedRelations();
            Assert.Contains(mockRelation, actual);
        }
        finally
        {
            _relationService.RelationsFound -= OnRelationsFound;
        }

        void OnRelationsFound(object? s, EventArgs e)
        {
            tcs.TrySetResult();
        }
    }

    [Fact(Timeout = 2_000)]
    public async Task LoadRelations_Raises_RelationsFound()
    {
        // Arrange
        Guid taskGuid = Guid.NewGuid();
        cF4sdApiSearchResultRelation mockRelation = new()
        {
            id = Guid.NewGuid(),
            Type = enumF4sdSearchResultClass.User
        };

        _communication.StartGatheringRelations(Arg.Any<IEnumerable<cFasdApiSearchResultEntry>>(), Arg.Any<CancellationToken>())
            .Returns(Task.FromResult(new cF4sdStagedSearchResultRelationTaskId() { Id = taskGuid }));
        _communication.GetStagedRelations(taskGuid, Arg.Any<CancellationToken>())
            .Returns(Task.FromResult(new cF4sdStagedSearchResultRelations()
            {
                PendingInformationClasses = [],
                Relations = [mockRelation]
            }));

        TaskCompletionSource<StagedSearchResultRelationsEventArgs> tcs = new(TaskCreationOptions.RunContinuationsAsynchronously);
        _relationService.RelationsFound += OnRelationsFound;

        try
        {
            await _relationService.LoadRelationsAsync(null, TestContext.Current.CancellationToken);

            // Assert

            var eventResult = await tcs.Task;

            Assert.NotNull(eventResult);
            Assert.Contains(mockRelation, eventResult.StagedResultRelations.Relations);
        }
        finally
        {
            _relationService.RelationsFound -= OnRelationsFound;
        }

        void OnRelationsFound(object? sender, StagedSearchResultRelationsEventArgs e)
        {
            tcs.TrySetResult(e);
        }
    }

    [Fact(Timeout = 2_000)]
    public async Task GetLoadedRelations_ContainsOnly_ActualRelations_Test()
    {
        // Arrange
        Guid taskGuid = Guid.NewGuid();
        cF4sdApiSearchResultRelation mockRelation1 = new()
        {
            id = Guid.NewGuid(),
            Type = enumF4sdSearchResultClass.User
        };

        cF4sdApiSearchResultRelation mockRelation2 = new()
        {
            id = Guid.NewGuid(),
            Type = enumF4sdSearchResultClass.Computer
        };

        _communication.StartGatheringRelations(Arg.Any<IEnumerable<cFasdApiSearchResultEntry>>(), Arg.Any<CancellationToken>())
            .Returns(Task.FromResult(new cF4sdStagedSearchResultRelationTaskId() { Id = taskGuid }));

        TaskCompletionSource tcs = new(TaskCreationOptions.RunContinuationsAsynchronously);

        try
        {
            // Act

            // load relations first time
            _communication.GetStagedRelations(taskGuid, Arg.Any<CancellationToken>())
                .Returns(Task.FromResult(new cF4sdStagedSearchResultRelations()
                {
                    PendingInformationClasses = [],
                    Relations = [mockRelation1]
                }));

            _relationService.RelationsFound += OnRelationsFound;

            await _relationService.LoadRelationsAsync(null, TestContext.Current.CancellationToken);
            await tcs.Task;

            // load relations second time
            tcs = new(TaskCreationOptions.RunContinuationsAsynchronously);

            _communication.GetStagedRelations(taskGuid, Arg.Any<CancellationToken>())
                .Returns(Task.FromResult(new cF4sdStagedSearchResultRelations()
                {
                    PendingInformationClasses = [],
                    Relations = [mockRelation2]
                }));

            await _relationService.LoadRelationsAsync(null, TestContext.Current.CancellationToken);
            await tcs.Task;

            // Assert
            var actual = _relationService.GetLoadedRelations();
            Assert.DoesNotContain(mockRelation1, actual);
            Assert.Contains(mockRelation2, actual);
        }
        finally
        {
            _relationService.RelationsFound -= OnRelationsFound;
        }

        void OnRelationsFound(object? s, EventArgs e)
        {
            tcs.TrySetResult();
        }
    }
}
