using System;
using System.Collections.Generic;
using System.IO;
using System.Text.Json;
using Tesla.Net;
using Xunit;

namespace TeslaConsole.DiffTests
{
    /// <summary>
    /// Exercises the framed JSON RPC protocol (PodRpc) that replaced the OFB +
    /// BinaryFormatter wire. Both the Console client (PodManagerConnection) and the
    /// Launcher Service share this exact code, so an in-process round-trip
    /// (encode request → decode → encode response → decode) validates the wire
    /// contract both ends depend on. The live console↔pod path still needs a pod;
    /// this covers the serialization, which is the part that can silently break.
    /// </summary>
    public class PodRpcProtocolTests
    {
        private static readonly Guid Key = new Guid("7D241B1F-AB6D-4e08-9C20-12294E743D94");

        [Fact]
        public void Request_RoundTrips_Method_And_Args()
        {
            var ms = new MemoryStream();
            PodRpc.WriteRequest(ms, "KillApp", new object[] { Key, 4242 });
            ms.Position = 0;

            var req = PodRpc.ReadRequest(ms);
            Assert.Equal("KillApp", req.Method);
            Assert.Equal(2, req.Args.Count);
            Assert.Equal(Key, req.Args[0].GetGuid());
            Assert.Equal(4242, req.Args[1].GetInt32());
        }

        [Fact]
        public void Request_With_No_Args_RoundTrips()
        {
            var ms = new MemoryStream();
            PodRpc.WriteRequest(ms, "FullUpdate", null);
            ms.Position = 0;

            var req = PodRpc.ReadRequest(ms);
            Assert.Equal("FullUpdate", req.Method);
            Assert.Empty(req.Args);
        }

        [Fact]
        public void Two_Frames_Read_Back_To_Back()
        {
            var ms = new MemoryStream();
            PodRpc.WriteRequest(ms, "Ping", new object[] { new DateTime(2026, 6, 30, 12, 0, 0, DateTimeKind.Utc) });
            PodRpc.WriteRequest(ms, "KillAllApps", null);
            ms.Position = 0;

            Assert.Equal("Ping", PodRpc.ReadRequest(ms).Method);
            Assert.Equal("KillAllApps", PodRpc.ReadRequest(ms).Method);
        }

        [Fact]
        public void Response_Error_RoundTrips()
        {
            var ms = new MemoryStream();
            PodRpc.WriteResponse(ms, null, "boom on the pod");
            ms.Position = 0;

            var resp = PodRpc.ReadResponse(ms);
            Assert.Equal("boom on the pod", resp.Error);
        }

        [Fact]
        public void Response_Primitive_Results_RoundTrip()
        {
            Assert.Equal(99, RoundTripResult<int>(99));
            Assert.Equal(Key, RoundTripResult<Guid>(Key));
            Assert.Equal(0.5f, RoundTripResult<float>(0.5f));
            Assert.Equal(new DateTime(2026, 6, 30), RoundTripResult<DateTime>(new DateTime(2026, 6, 30)));
        }

        [Fact]
        public void Response_LaunchData_Array_RoundTrips_With_Fields()
        {
            var apps = new[]
            {
                new LaunchData
                {
                    LaunchPair = new LaunchPair { LaunchKey = Key, DisplayName = "Red Planet" },
                    WorkingDirectory = @"C:\Games\RedPlanet",
                    ExeFile = "RedPlanet.exe",
                    Arguments = "-res 1024 768",
                    AutoRestart = true,
                },
            };

            var got = RoundTripResult<LaunchData[]>(apps);
            Assert.Single(got);
            Assert.Equal(Key, got[0].LaunchPair.LaunchKey);
            Assert.Equal("Red Planet", got[0].LaunchPair.DisplayName);
            Assert.Equal(@"C:\Games\RedPlanet", got[0].WorkingDirectory);
            Assert.Equal("RedPlanet.exe", got[0].ExeFile);
            Assert.Equal("-res 1024 768", got[0].Arguments);
            Assert.True(got[0].AutoRestart);
        }

        [Fact]
        public void Response_FullUpdateData_RoundTrips()
        {
            var full = new FullUpdateData
            {
                InstalledApps = new[]
                {
                    new LaunchData { LaunchPair = new LaunchPair { LaunchKey = Key, DisplayName = "RP" } },
                },
                LaunchedApps = new[] { new LaunchedAppData { LaunchKey = Key, ProcessId = 1234 } },
                VolumeLevel = 0.75f,
            };

            var got = RoundTripResult<FullUpdateData>(full);
            Assert.Equal(0.75f, got.VolumeLevel);
            Assert.Equal("RP", got.InstalledApps[0].LaunchPair.DisplayName);
            Assert.Equal(1234, got.LaunchedApps[0].ProcessId);
            Assert.Equal(Key, got.LaunchedApps[0].LaunchKey);
        }

        [Fact]
        public void Response_OutOfBandProgress_RoundTrips()
        {
            var p = new OutOfBandProgress { PercentComplete = 73, Status = "Extracting...", IsCompleted = false };
            var got = RoundTripResult<OutOfBandProgress>(p);
            Assert.Equal(73, got.PercentComplete);
            Assert.Equal("Extracting...", got.Status);
            Assert.False(got.IsCompleted);
        }

        [Fact]
        public void Oversized_Frame_Length_Is_Rejected()
        {
            var ms = new MemoryStream();
            // Craft a frame header claiming more than MaxFrameBytes.
            ms.Write(BitConverter.GetBytes(PodRpc.MaxFrameBytes + 1), 0, 4);
            ms.Position = 0;
            Assert.Throws<IOException>(() => PodRpc.ReadFrame(ms));
        }

        // Serialize a result the way the Launcher does, read it the way the Console does.
        private static T RoundTripResult<T>(object result)
        {
            var ms = new MemoryStream();
            PodRpc.WriteResponse(ms, result, null);
            ms.Position = 0;
            var resp = PodRpc.ReadResponse(ms);
            Assert.Null(resp.Error);
            return resp.Result.Deserialize<T>(PodRpc.JsonOptions);
        }
    }
}
