using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.IO.Pipes;
using LanMountainDesktop.Launcher.Models;

namespace LanMountainDesktop.Launcher.Services.Ipc;

internal sealed class LauncherCoordinatorIpcServer : IDisposable
{
    private const int LengthPrefixSize = 4;
    private const int MaxPayloadLength = 1024 * 1024;
    private const int BackoffBaseMs = 250;
    private const int BackoffMaxMs = 8000;
    private const int BackoffJitterMs = 150;
    private readonly string _pipeName;
    private readonly Func<LauncherCoordinatorRequest, LauncherCoordinatorStatus, Task<LauncherCoordinatorResponse>> _requestHandler;
    private readonly Action<LauncherCoordinatorStatus> _heartbeatHandler;
    private readonly CancellationTokenSource _cts = new();
    private readonly object _statusGate = new();
    private LauncherCoordinatorStatus _status;
    private Task? _listenTask;
    private Task? _heartbeatTask;

    public LauncherCoordinatorIpcServer(
        string pipeName,
        LauncherCoordinatorStatus initialStatus,
        Func<LauncherCoordinatorRequest, LauncherCoordinatorStatus, Task<LauncherCoordinatorResponse>> requestHandler,
        Action<LauncherCoordinatorStatus> heartbeatHandler)
    {
        _pipeName = pipeName;
        _status = initialStatus;
        _requestHandler = requestHandler;
        _heartbeatHandler = heartbeatHandler;
    }

    public static string CreatePipeName()
    {
        var seed = $"{Environment.UserName}:{Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)}";
        var bytes = SHA256.HashData(Encoding.UTF8.GetBytes(seed.ToLowerInvariant()));
        return $"LanMountainDesktop_Launcher_Coordinator_{Convert.ToHexString(bytes[..8])}";
    }

    public void Start()
    {
        _listenTask ??= Task.Run(ListenLoopAsync);
        _heartbeatTask ??= Task.Run(HeartbeatLoopAsync);
    }

    public LauncherCoordinatorStatus GetStatus()
    {
        lock (_statusGate)
        {
            return _status;
        }
    }

    public void UpdateStatus(LauncherCoordinatorStatus status)
    {
        lock (_statusGate)
        {
            _status = status;
        }
    }

    public void Dispose()
    {
        _cts.Cancel();

        try
        {
            _listenTask?.Wait(TimeSpan.FromSeconds(1));
            _heartbeatTask?.Wait(TimeSpan.FromSeconds(1));
        }
        catch
        {
        }

        _cts.Dispose();
    }

    private async Task ListenLoopAsync()
    {
        var consecutiveErrors = 0;

        while (!_cts.IsCancellationRequested)
        {
            NamedPipeServerStream? server = null;
            try
            {
                server = new NamedPipeServerStream(
                    _pipeName,
                    PipeDirection.InOut,
                    8,
                    PipeTransmissionMode.Byte,
                    PipeOptions.Asynchronous);

                await server.WaitForConnectionAsync(_cts.Token).ConfigureAwait(false);
                var connectedServer = server;
                _ = Task.Run(() => HandleConnectionAsync(connectedServer, _cts.Token), _cts.Token);
                server = null;
                consecutiveErrors = 0;
            }
            catch (OperationCanceledException)
            {
                break;
            }
            catch (Exception ex)
            {
                consecutiveErrors++;
                var delay = ComputeBackoff(consecutiveErrors);
                Logger.Warn($"Launcher coordinator IPC listener failed (attempt {consecutiveErrors}), retrying in {delay}ms: {ex.Message}");
                try
                {
                    await Task.Delay(delay, _cts.Token).ConfigureAwait(false);
                }
                catch (OperationCanceledException)
                {
                    break;
                }
            }
            finally
            {
                server?.Dispose();
            }
        }
    }

    private int ComputeBackoff(int attempt)
    {
        var exponential = BackoffBaseMs * (1 << Math.Min(attempt - 1, 5));
        var capped = Math.Min(exponential, BackoffMaxMs);
        var jitter = Random.Shared.Next(0, BackoffJitterMs);
        return capped + jitter;
    }

    private async Task HeartbeatLoopAsync()
    {
        while (!_cts.IsCancellationRequested)
        {
            try
            {
                _heartbeatHandler(GetStatus());
                await Task.Delay(TimeSpan.FromSeconds(2), _cts.Token).ConfigureAwait(false);
            }
            catch (OperationCanceledException)
            {
                break;
            }
            catch (Exception ex)
            {
                Logger.Warn($"Launcher coordinator heartbeat failed: {ex.Message}");
            }
        }
    }

    private async Task HandleConnectionAsync(NamedPipeServerStream server, CancellationToken cancellationToken)
    {
        try
        {
            var request = await ReadRequestAsync(server, cancellationToken).ConfigureAwait(false);
            var status = GetStatus();
            var response = request is null
                ? new LauncherCoordinatorResponse
                {
                    Accepted = false,
                    Code = "invalid_request",
                    Message = "Launcher coordinator request was invalid.",
                    Status = status
                }
                : await _requestHandler(request, status).ConfigureAwait(false);

            await WriteResponseAsync(server, response, cancellationToken).ConfigureAwait(false);
        }
        catch (Exception ex)
        {
            Logger.Warn($"Launcher coordinator IPC request failed: {ex.Message}");
        }
        finally
        {
            try
            {
                server.Dispose();
            }
            catch
            {
            }
        }
    }

    private static async Task<LauncherCoordinatorRequest?> ReadRequestAsync(
        Stream stream,
        CancellationToken cancellationToken)
    {
        var lengthBuffer = new byte[LengthPrefixSize];
        if (!await ReadExactAsync(stream, lengthBuffer, cancellationToken).ConfigureAwait(false))
        {
            return null;
        }

        var payloadLength = BitConverter.ToInt32(lengthBuffer, 0);
        if (payloadLength <= 0 || payloadLength > MaxPayloadLength)
        {
            return null;
        }

        var payload = new byte[payloadLength];
        if (!await ReadExactAsync(stream, payload, cancellationToken).ConfigureAwait(false))
        {
            return null;
        }

        return JsonSerializer.Deserialize(
            Encoding.UTF8.GetString(payload),
            AppJsonContext.Default.LauncherCoordinatorRequest);
    }

    private static async Task WriteResponseAsync(
        Stream stream,
        LauncherCoordinatorResponse response,
        CancellationToken cancellationToken)
    {
        var json = JsonSerializer.Serialize(response, AppJsonContext.Default.LauncherCoordinatorResponse);
        var payload = Encoding.UTF8.GetBytes(json);
        await stream.WriteAsync(BitConverter.GetBytes(payload.Length), cancellationToken).ConfigureAwait(false);
        await stream.WriteAsync(payload, cancellationToken).ConfigureAwait(false);
        await stream.FlushAsync(cancellationToken).ConfigureAwait(false);
    }

    private static async Task<bool> ReadExactAsync(
        Stream stream,
        byte[] buffer,
        CancellationToken cancellationToken)
    {
        var totalRead = 0;
        while (totalRead < buffer.Length)
        {
            var read = await stream
                .ReadAsync(buffer.AsMemory(totalRead, buffer.Length - totalRead), cancellationToken)
                .ConfigureAwait(false);
            if (read == 0)
            {
                return false;
            }

            totalRead += read;
        }

        return true;
    }
}
