using System;
using System.IO;
using System.IO.Pipes;
using System.Threading;
using System.Threading.Tasks;

using IGP.UnitySDK.Models;

namespace IGP.UnitySDK
{
    /// <summary>
    /// Windows-only local host bridge client used to redeem launch tickets
    /// through the Curio desktop host process.
    /// </summary>
    public sealed class IGPHostBridgeClient
    {
        private const int ConnectTimeoutMs = 5000;
        private const int MaxFramePayloadBytes = 1024 * 1024;
        private readonly string pipeEndpoint;
        private readonly string secret;

        public IGPHostBridgeClient(string pipeEndpoint, string secret)
        {
            this.pipeEndpoint = pipeEndpoint ?? string.Empty;
            this.secret = secret ?? string.Empty;
        }

        public async Task<LaunchTicketRedeemResponse> RedeemLaunchTicketAsync(
            string ticket,
            CancellationToken cancellationToken = default)
        {
            if (string.IsNullOrWhiteSpace(ticket))
            {
                throw new ArgumentException("Launch ticket is required", nameof(ticket));
            }

            if (string.IsNullOrWhiteSpace(pipeEndpoint))
            {
                throw new IGPSDKException("IGP bootstrap endpoint is missing");
            }

            if (string.IsNullOrWhiteSpace(secret))
            {
                throw new IGPSDKException("IGP bootstrap secret is missing");
            }

            var pipeName = ExtractPipeName(pipeEndpoint);
            using var pipe = new NamedPipeClientStream(
                ".",
                pipeName,
                PipeDirection.InOut,
                PipeOptions.Asynchronous);

            await Task.Run(() => pipe.Connect(ConnectTimeoutMs), cancellationToken);
            cancellationToken.ThrowIfCancellationRequested();
            var requestPayload = IGPHostBridgeProtocol.EncodeRedeemLaunchTicketRequest(ticket, secret);
            var requestFrame = EncodeFrame(requestPayload);
            await pipe.WriteAsync(requestFrame, 0, requestFrame.Length, cancellationToken);
            await pipe.FlushAsync(cancellationToken);

            cancellationToken.ThrowIfCancellationRequested();
            var responsePayload = await ReadFrameAsync(pipe, cancellationToken);
            var response = IGPHostBridgeProtocol.DecodeBootstrapResponse(responsePayload);

            if (string.IsNullOrWhiteSpace(response.roomId) ||
                string.IsNullOrWhiteSpace(response.playerId) ||
                string.IsNullOrWhiteSpace(response.token) ||
                string.IsNullOrWhiteSpace(response.serverUrl))
            {
                throw new IGPSDKException("Incomplete bootstrap response");
            }

            response.success = true;
            return response;
        }

        private static byte[] EncodeFrame(byte[] payload)
        {
            var frame = new byte[4 + payload.Length];
            var lengthBytes = BitConverter.GetBytes(payload.Length);
            Buffer.BlockCopy(lengthBytes, 0, frame, 0, 4);
            Buffer.BlockCopy(payload, 0, frame, 4, payload.Length);
            return frame;
        }

        private static async Task<byte[]> ReadFrameAsync(Stream stream, CancellationToken cancellationToken)
        {
            var header = await ReadExactAsync(stream, 4, cancellationToken);
            var payloadLength = BitConverter.ToInt32(header, 0);
            if (payloadLength <= 0 || payloadLength > MaxFramePayloadBytes)
            {
                throw new IGPSDKException("Invalid bootstrap frame length");
            }

            return await ReadExactAsync(stream, payloadLength, cancellationToken);
        }

        private static async Task<byte[]> ReadExactAsync(
            Stream stream,
            int length,
            CancellationToken cancellationToken)
        {
            var buffer = new byte[length];
            var offset = 0;

            while (offset < length)
            {
                var read = await stream.ReadAsync(buffer, offset, length - offset, cancellationToken);
                if (read <= 0)
                {
                    throw new IGPSDKException("Unexpected end of bootstrap stream");
                }

                offset += read;
            }

            return buffer;
        }

        private static string ExtractPipeName(string endpoint)
        {
            const string pipePrefix = @"\\.\pipe\";
            if (endpoint.StartsWith(pipePrefix, StringComparison.OrdinalIgnoreCase))
            {
                return endpoint.Substring(pipePrefix.Length);
            }

            return endpoint;
        }
    }
}
