using System;
using System.IO;
using System.IO.Pipes;
using System.Security.Principal;
using System.Threading;
using System.Threading.Tasks;
using MessagePack;
using Serilog;
using Serilog.Core;
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Host.Backend;
using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Host.Ipc;
using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Shared;
using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Shared.Contracts;

namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Host.Tests
{
    /// <summary>
    ///     Direct IPC handshake test — drives <see cref="PipeServer"/> with a hand-rolled client
    ///     built on <see cref="FrameReader"/>/<see cref="FrameWriter"/> from Shared. Stays in
    ///     net48 x86 alongside the Host (the Proxy's <c>GalaxyIpcClient</c> is net10 only and
    ///     cannot be loaded into this process). Functionally equivalent to going through
    ///     <c>GalaxyIpcClient</c> — proves the wire protocol + ACL + shared-secret enforcement.
    ///     Skipped on Administrator shells per the same PipeAcl-denies-Administrators guard.
    /// </summary>
    [Trait("Category", "Integration")]
    public sealed class IpcHandshakeIntegrationTests
    {
        private static bool IsAdministrator()
        {
            using var identity = WindowsIdentity.GetCurrent();
            return new WindowsPrincipal(identity).IsInRole(WindowsBuiltInRole.Administrator);
        }

        private static async Task<(NamedPipeClientStream Stream, FrameReader Reader, FrameWriter Writer)>
            ConnectAndHelloAsync(string pipeName, string secret, CancellationToken ct)
        {
            var stream = new NamedPipeClientStream(".", pipeName, PipeDirection.InOut, PipeOptions.Asynchronous);
            await stream.ConnectAsync(5_000, ct);

            var reader = new FrameReader(stream, leaveOpen: true);
            var writer = new FrameWriter(stream, leaveOpen: true);
            await writer.WriteAsync(MessageKind.Hello,
                new Hello { PeerName = "test-client", SharedSecret = secret }, ct);

            var ack = await reader.ReadFrameAsync(ct);
            if (ack is null) throw new EndOfStreamException("no HelloAck");
            if (ack.Value.Kind != MessageKind.HelloAck) throw new InvalidOperationException("unexpected first frame");
            var ackMsg = MessagePackSerializer.Deserialize<HelloAck>(ack.Value.Body);
            if (!ackMsg.Accepted) throw new UnauthorizedAccessException(ackMsg.RejectReason);

            return (stream, reader, writer);
        }

        [Fact]
        public async Task Handshake_with_correct_secret_succeeds_and_heartbeat_round_trips()
        {
            if (IsAdministrator()) return;

            using var identity = WindowsIdentity.GetCurrent();
            var sid = identity.User!;
            var pipe = $"OtOpcUaGalaxyTest-{Guid.NewGuid():N}";
            const string secret = "test-secret-2026";
            Logger log = new LoggerConfiguration().CreateLogger();
            using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));

            var server = new PipeServer(pipe, sid, secret, log);
            var serverTask = Task.Run(() => server.RunOneConnectionAsync(
                new GalaxyFrameHandler(new StubGalaxyBackend(), log), cts.Token));

            var (stream, reader, writer) = await ConnectAndHelloAsync(pipe, secret, cts.Token);
            using (stream)
            using (reader)
            using (writer)
            {
                await writer.WriteAsync(MessageKind.Heartbeat,
                    new Heartbeat { SequenceNumber = 42, UtcUnixMs = 1000 }, cts.Token);

                var hbAckFrame = await reader.ReadFrameAsync(cts.Token);
                hbAckFrame.HasValue.ShouldBeTrue();
                hbAckFrame!.Value.Kind.ShouldBe(MessageKind.HeartbeatAck);
                MessagePackSerializer.Deserialize<HeartbeatAck>(hbAckFrame.Value.Body).SequenceNumber.ShouldBe(42L);
            }

            cts.Cancel();
            try { await serverTask; } catch { /* shutdown */ }
            server.Dispose();
        }

        [Fact]
        public async Task Handshake_with_wrong_secret_is_rejected()
        {
            if (IsAdministrator()) return;

            using var identity = WindowsIdentity.GetCurrent();
            var sid = identity.User!;
            var pipe = $"OtOpcUaGalaxyTest-{Guid.NewGuid():N}";
            Logger log = new LoggerConfiguration().CreateLogger();
            using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));

            var server = new PipeServer(pipe, sid, "real-secret", log);
            var serverTask = Task.Run(() => server.RunOneConnectionAsync(
                new GalaxyFrameHandler(new StubGalaxyBackend(), log), cts.Token));

            await Should.ThrowAsync<UnauthorizedAccessException>(async () =>
            {
                var (s, r, w) = await ConnectAndHelloAsync(pipe, "wrong-secret", cts.Token);
                s.Dispose();
                r.Dispose();
                w.Dispose();
            });

            cts.Cancel();
            try { await serverTask; } catch { /* shutdown */ }
            server.Dispose();
        }
    }
}
