using System.Diagnostics;
using System.IO.Pipes;
using VPlasma.Core.Device;
using Xunit;

namespace VPlasma.Core.Tests;

public class VPlasmaPipeServiceTests
{
    private static string UniqueName() => $"vplasma-test-{Guid.NewGuid():N}";

    private static void WaitFor(Func<bool> condition, int timeoutMs = 2000)
    {
        var clock = Stopwatch.StartNew();
        while (!condition())
        {
            Assert.True(clock.ElapsedMilliseconds < timeoutMs, "timed out waiting for a condition");
            Thread.Sleep(10);
        }
    }

    private static NamedPipeClientStream Connect(string name)
    {
        var pipe = new NamedPipeClientStream(".", name, PipeDirection.InOut, PipeOptions.Asynchronous);
        var clock = Stopwatch.StartNew();
        while (true)
        {
            try
            {
                pipe.Connect(200);
                return pipe;
            }
            catch (Exception ex) when (ex is IOException or TimeoutException)
            {
                if (clock.ElapsedMilliseconds >= 3000)
                    throw;
                Thread.Sleep(25);
            }
        }
    }

    [Fact]
    public void Framed_text_reaches_the_display_and_lines_are_ignored()
    {
        string name = UniqueName();
        var device = new VPlasmaDevice();
        using var service = new VPlasmaPipeService(device, name);
        service.Start();

        using var client = Connect(name);
        var hello = new byte[2];
        Assert.Equal(1, client.Read(hello, 0, 1)); // on-connect lines frame,
        Assert.Equal(1, client.Read(hello, 1, 1)); // possibly split
        Assert.Equal(new byte[] { 0x01, 0x03 }, hello); // DTR+RTS: display present

        // RTS chatter (DOS console redirection toggles it per byte) then "HI",
        // the data frame torn in half mid-payload.
        client.Write(new byte[] { 0x01, 0x02, 0x00, 0x02, (byte)'H' }, 0, 5);
        client.Flush();
        Thread.Sleep(20);
        client.Write(new byte[] { (byte)'I' }, 0, 1);
        client.Flush();

        WaitFor(() => device.BytesReceived == 2);
        Assert.Equal(2, device.TextCharsDrawn);
    }

    [Fact]
    public void Unknown_frame_type_drops_the_client_and_server_recovers()
    {
        string name = UniqueName();
        var device = new VPlasmaDevice();
        using var service = new VPlasmaPipeService(device, name);
        service.Start();

        using (var bad = Connect(name))
        {
            var hello = new byte[2];
            bad.Read(hello, 0, 2);
            bad.Write(new byte[] { 0x7F }, 0, 1); // not a frame type
            bad.Flush();
            // Contract: log + drop, no resync — our next read sees the close.
            var eof = Task.Run(() =>
            {
                try { return bad.Read(new byte[1], 0, 1) == 0; }
                catch (IOException) { return true; }
            });
            Assert.True(eof.Wait(2000), "server did not drop the connection");
            Assert.True(eof.Result);
        }

        using var good = Connect(name);
        var again = new byte[2];
        Assert.Equal(1, good.Read(again, 0, 1));
        Assert.Equal(1, good.Read(again, 1, 1));
        Assert.Equal(new byte[] { 0x01, 0x03 }, again);
    }
}
