using FasdDesktopUi.Basics.Services;
using FasdDesktopUi.Basics.Services.Models;

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

public class TicketOverviewUpdateServiceTest
{
    [Fact]
    public async Task UpdateAvailability_FirstFetch_EmitsInitializationEventsForBothScopes()
    {
        // Arrange
        var communication = new FakeCommunication();
        communication.SetCounts(TileScope.Personal, new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase)
        {
            ["TicketsNew"] = 2
        });
        communication.SetCounts(TileScope.Role, new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase)
        {
            ["TicketsNew"] = 5
        });

        var service = CreateService(communication, out _);
        var events = new List<TicketOverviewCountsChangedEventArgs>();
        service.OverviewCountsChanged += (_, args) => events.Add(args);

        // Act
        service.UpdateAvailability(true);
        await WaitUntilAsync(() => service.AreAllScopesInitialized);

        // Assert
        Assert.Equal(2, events.Count);
        Assert.All(events, e => Assert.Empty(e.Changes));
        Assert.Contains(events, e => e.InitializedScope == TileScope.Personal);
        Assert.Contains(events, e => e.InitializedScope == TileScope.Role);
        Assert.Equal(2, service.CurrentCounts["TicketsNew"].Personal);
        Assert.Equal(5, service.CurrentCounts["TicketsNew"].Role);
    }

    [Fact]
    public async Task FetchAsync_ChangedAndUnchangedCounts_EmitsOnlyRealDeltaEvent()
    {
        // Arrange
        var communication = new FakeCommunication();
        communication.SetCounts(TileScope.Personal, new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase)
        {
            ["TicketsNew"] = 1
        });
        communication.SetCounts(TileScope.Role, new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase)
        {
            ["TicketsNew"] = 3
        });

        var service = CreateService(communication, out _);
        var events = new List<TicketOverviewCountsChangedEventArgs>();
        service.OverviewCountsChanged += (_, args) => events.Add(args);

        service.UpdateAvailability(true);
        await WaitUntilAsync(() => service.AreAllScopesInitialized);
        events.Clear();

        // Act + Assert (changed)
        communication.SetCounts(TileScope.Personal, new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase)
        {
            ["TicketsNew"] = 4
        });
        await service.FetchAsync(TileScope.Personal);

        Assert.Single(events);
        Assert.Single(events[0].Changes);
        Assert.Equal("TicketsNew", events[0].Changes[0].Key);
        Assert.Equal(1, events[0].Changes[0].OldCount);
        Assert.Equal(4, events[0].Changes[0].NewCount);
        Assert.Equal(3, events[0].Changes[0].Delta);

        // Act + Assert (unchanged)
        events.Clear();
        await service.FetchAsync(TileScope.Personal);
        Assert.Empty(events);
    }

    [Fact]
    public async Task Stop_ResetsInitializationAndCounts()
    {
        // Arrange
        var communication = new FakeCommunication();
        communication.SetCounts(TileScope.Personal, new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase)
        {
            ["TicketsNew"] = 7
        });
        communication.SetCounts(TileScope.Role, new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase)
        {
            ["IncidentNew"] = 9
        });

        var service = CreateService(communication, out _);

        service.UpdateAvailability(true);
        await WaitUntilAsync(() => service.AreAllScopesInitialized);

        // Act
        service.Stop();

        // Assert
        Assert.False(service.IsScopeInitialized(TileScope.Personal));
        Assert.False(service.IsScopeInitialized(TileScope.Role));
        Assert.All(service.CurrentCounts.Values, value =>
        {
            Assert.Equal(0, value.Personal);
            Assert.Equal(0, value.Role);
        });
    }

    private static TicketOverviewUpdateService CreateService(FakeCommunication communication, out FakeDispatcher dispatcher)
    {
        dispatcher = new FakeDispatcher();
        var source = new FakeCommunicationSource(communication);
        var settings = new FakeSettingsProvider();
        return new TicketOverviewUpdateService(source, dispatcher, settings);
    }

    private static async Task WaitUntilAsync(Func<bool> condition, int timeoutMs = 2000)
    {
        var startedAt = DateTime.UtcNow;
        while (!condition())
        {
            if ((DateTime.UtcNow - startedAt).TotalMilliseconds > timeoutMs)
            {
                throw new TimeoutException("Condition was not reached within timeout.");
            }

            await Task.Delay(10);
        }
    }

    private sealed class FakeCommunicationSource : ITicketOverviewCommunicationSource
    {
        private readonly ITicketOverviewCommunication _communication;

        public FakeCommunicationSource(ITicketOverviewCommunication communication)
        {
            _communication = communication;
        }

        public ITicketOverviewCommunication Resolve()
        {
            return _communication;
        }
    }

    private sealed class FakeCommunication : ITicketOverviewCommunication
    {
        private Dictionary<string, int> _personalCounts = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
        private Dictionary<string, int> _roleCounts = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);

        public bool IsDemo()
        {
            return false;
        }

        public Task<Dictionary<string, int>> GetTicketOverviewCounts(string[] overviewKeys, bool useRoleScope)
        {
            var source = useRoleScope ? _roleCounts : _personalCounts;
            return Task.FromResult(new Dictionary<string, int>(source, StringComparer.OrdinalIgnoreCase));
        }

        public void RegisterGeneratedTicket(FasdCockpitCommunicationDemo.DemoTicketRecord record)
        {
        }

        public void SetCounts(TileScope scope, Dictionary<string, int> counts)
        {
            var copy = counts == null
                ? new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase)
                : new Dictionary<string, int>(counts, StringComparer.OrdinalIgnoreCase);

            if (scope == TileScope.Role)
            {
                _roleCounts = copy;
            }
            else
            {
                _personalCounts = copy;
            }
        }
    }

    private sealed class FakeDispatcher : ITicketOverviewDispatcher
    {
        public Task InvokeAsync(Action action)
        {
            action();
            return Task.CompletedTask;
        }

        public Task InvokeAsync(Func<Task> action)
        {
            return action();
        }

        public ITicketOverviewTimer CreateTimer(TimeSpan interval, Action tick)
        {
            return new FakeTimer(interval, tick);
        }
    }

    private sealed class FakeSettingsProvider : ITicketOverviewSettingsProvider
    {
        public int GetPollingMinutes(TileScope scope)
        {
            return 1;
        }
    }

    private sealed class FakeTimer : ITicketOverviewTimer
    {
        private readonly Action _tick;

        public FakeTimer(TimeSpan interval, Action tick)
        {
            Interval = interval;
            _tick = tick;
        }

        public TimeSpan Interval { get; set; }

        public bool IsEnabled { get; private set; }

        public void Start()
        {
            IsEnabled = true;
        }

        public void Stop()
        {
            IsEnabled = false;
        }

        public void Fire()
        {
            _tick?.Invoke();
        }
    }
}
