using EpsonPrintService;
using Inspectron.Epson;
using Inspectron.Epson.PrintServer.Telemetry;

namespace Inspectron.Epson.Tests.Telemetry;

public class InsinTelemetryDiscoveredPrintersReceiverTests
{
    private sealed class RecordingTelemetry : IInsinTelemetry
    {
        public List<(string Kind, string Message)> Events { get; } = new();
        public void Emit(string kind, string message) => Events.Add((kind, message));
    }

    private sealed class CountingInner : IDiscoveredPrintersReceiver
    {
        public int Calls;
        public Task OnPrintersDiscoveredAsync(IReadOnlyList<DiscoveredPrinter> printers)
        {
            Calls++;
            return Task.CompletedTask;
        }
    }

    private static DiscoveredPrinter Printer(string ip, string model = "TM-T30III") =>
        new() { IPAddress = ip, ModelName = model };

    [Fact]
    public async Task First_sighting_emits_discovered_and_online()
    {
        var telemetry = new RecordingTelemetry();
        var inner = new CountingInner();
        var sut = new InsinTelemetryDiscoveredPrintersReceiver(inner, telemetry);

        await sut.OnPrintersDiscoveredAsync(new[] { Printer("192.168.1.10") });

        Assert.Equal(1, inner.Calls);
        Assert.Collection(telemetry.Events,
            e => Assert.Equal("printer.discovered", e.Kind),
            e => Assert.Equal("printer.online",     e.Kind));
        Assert.Contains("printer=192.168.1.10", telemetry.Events[0].Message);
        Assert.Contains("model=TM-T30III",      telemetry.Events[0].Message);
    }

    [Fact]
    public async Task Repeat_sighting_emits_nothing()
    {
        var telemetry = new RecordingTelemetry();
        var sut = new InsinTelemetryDiscoveredPrintersReceiver(new CountingInner(), telemetry);
        await sut.OnPrintersDiscoveredAsync(new[] { Printer("192.168.1.10") });
        telemetry.Events.Clear();

        await sut.OnPrintersDiscoveredAsync(new[] { Printer("192.168.1.10") });

        Assert.Empty(telemetry.Events);
    }

    [Fact]
    public async Task Disappearance_emits_offline()
    {
        var telemetry = new RecordingTelemetry();
        var sut = new InsinTelemetryDiscoveredPrintersReceiver(new CountingInner(), telemetry);
        await sut.OnPrintersDiscoveredAsync(new[] { Printer("192.168.1.10") });
        telemetry.Events.Clear();

        await sut.OnPrintersDiscoveredAsync(Array.Empty<DiscoveredPrinter>());

        Assert.Single(telemetry.Events);
        Assert.Equal("printer.offline", telemetry.Events[0].Kind);
        Assert.Equal("printer=192.168.1.10", telemetry.Events[0].Message);
    }

    [Fact]
    public async Task Reappearance_emits_online_only_not_discovered()
    {
        var telemetry = new RecordingTelemetry();
        var sut = new InsinTelemetryDiscoveredPrintersReceiver(new CountingInner(), telemetry);
        await sut.OnPrintersDiscoveredAsync(new[] { Printer("192.168.1.10") });
        await sut.OnPrintersDiscoveredAsync(Array.Empty<DiscoveredPrinter>());
        telemetry.Events.Clear();

        await sut.OnPrintersDiscoveredAsync(new[] { Printer("192.168.1.10") });

        Assert.Single(telemetry.Events);
        Assert.Equal("printer.online", telemetry.Events[0].Kind);
    }

    [Fact]
    public async Task Telemetry_failures_do_not_break_inner()
    {
        var telemetry = new ThrowingTelemetry();
        var inner = new CountingInner();
        var sut = new InsinTelemetryDiscoveredPrintersReceiver(inner, telemetry);

        await sut.OnPrintersDiscoveredAsync(new[] { Printer("192.168.1.10") });

        Assert.Equal(1, inner.Calls);
    }

    private sealed class ThrowingTelemetry : IInsinTelemetry
    {
        public void Emit(string kind, string message) => throw new InvalidOperationException("boom");
    }
}
