using System;
using System.IO.Pipes;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Threading;
using Serilog;

namespace OutlookAddin.Services
{
    /// <summary>
    /// Hosts a named pipe (<c>\\.\pipe\MarcusLaw.OutlookAddin</c>) that the
    /// out-of-process URL-protocol handler writes lines to. Each line is
    /// dispatched to <see cref="MessageReceived"/> on the WPF UI thread so
    /// callers can freely touch the Outlook COM model.
    /// </summary>
    public sealed class NamedPipeListener : IDisposable
    {
        public const string PipeName = "MarcusLaw.OutlookAddin";

        private readonly CancellationTokenSource _cts = new CancellationTokenSource();
        private readonly Dispatcher _dispatcher;
        private readonly ILogger _logger;
        private Task? _loop;

        public event EventHandler<string>? MessageReceived;

        public NamedPipeListener(Dispatcher dispatcher, ILogger logger)
        {
            _dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher));
            _logger = logger ?? throw new ArgumentNullException(nameof(logger));
        }

        public void Start()
        {
            if (_loop != null) return;
            _loop = Task.Run(() => RunAsync(_cts.Token));
            _logger.Information("NamedPipeListener started ({Pipe})", PipeName);
        }

        private async Task RunAsync(CancellationToken ct)
        {
            while (!ct.IsCancellationRequested)
            {
                try
                {
                    using var server = new NamedPipeServerStream(
                        PipeName,
                        PipeDirection.InOut,
                        maxNumberOfServerInstances: 1,
                        PipeTransmissionMode.Byte,
                        PipeOptions.Asynchronous);

                    await server.WaitForConnectionAsync(ct).ConfigureAwait(false);

                    using var reader = new System.IO.StreamReader(server, Encoding.UTF8, detectEncodingFromByteOrderMarks: false, bufferSize: 1024, leaveOpen: true);
                    string? line;
                    while ((line = await reader.ReadLineAsync().ConfigureAwait(false)) != null)
                    {
                        var captured = line;
                        try
                        {
                            _dispatcher.BeginInvoke((Action)(() => MessageReceived?.Invoke(this, captured)));
                        }
                        catch (Exception ex)
                        {
                            _logger.Warning(ex, "NamedPipeListener dispatcher invoke failed");
                        }
                    }
                }
                catch (OperationCanceledException) { return; }
                catch (Exception ex)
                {
                    _logger.Warning(ex, "NamedPipeListener loop iteration failed — retrying");
                    try { await Task.Delay(TimeSpan.FromSeconds(2), ct).ConfigureAwait(false); }
                    catch (OperationCanceledException) { return; }
                }
            }
        }

        public void Dispose()
        {
            try { _cts.Cancel(); } catch { /* ignore */ }
            _cts.Dispose();
        }
    }
}
