using System.IO.Pipes;
using System.Security.Cryptography;
using System.Text;

namespace Lumi.Companion.App;

internal sealed class SingleInstanceCoordinator : IDisposable
{
    private readonly Mutex _mutex;
    private readonly CancellationTokenSource _lifetime = new();
    private readonly string _pipeName;
    private Task? _listener;

    public SingleInstanceCoordinator()
    {
        var userKey = Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes($"{Environment.UserDomainName}\\{Environment.UserName}"))).Substring(0, 16);
        _pipeName = $"Lumi.Companion.Activate.v1.{userKey}";
        _mutex = new Mutex(true, $"Local\\Lumi.Companion.v1.{userKey}", out var created);
        IsPrimary = created;
        if (created) _listener = Task.Run(() => ListenAsync(_lifetime.Token));
    }

    public bool IsPrimary { get; }
    public event Action? ActivationRequested;

    public void SignalPrimary()
    {
        try
        {
            using var pipe = new NamedPipeClientStream(".", _pipeName, PipeDirection.Out, PipeOptions.Asynchronous);
            pipe.Connect(1000);
            pipe.WriteByte(1);
            pipe.Flush();
        }
        catch { }
    }

    private async Task ListenAsync(CancellationToken cancellationToken)
    {
        while (!cancellationToken.IsCancellationRequested)
        {
            try
            {
                await using var pipe = new NamedPipeServerStream(_pipeName, PipeDirection.In, 1, PipeTransmissionMode.Byte,
                    PipeOptions.Asynchronous | PipeOptions.CurrentUserOnly, 1, 1);
                await pipe.WaitForConnectionAsync(cancellationToken);
                if (pipe.ReadByte() == 1) ActivationRequested?.Invoke();
            }
            catch (OperationCanceledException) { break; }
            catch when (!cancellationToken.IsCancellationRequested) { await Task.Delay(200, cancellationToken); }
        }
    }

    public void Dispose()
    {
        _lifetime.Cancel();
        try { _listener?.Wait(500); } catch { }
        if (IsPrimary) try { _mutex.ReleaseMutex(); } catch { }
        _mutex.Dispose();
        _lifetime.Dispose();
    }
}
