using Microsoft.Extensions.Logging;
using ZB.MOM.WW.LmxProxy.Client.Domain;

namespace ZB.MOM.WW.LmxProxy.Client;

public partial class LmxProxyClient
{
    private class CodeFirstSubscription : ISubscription
    {
        private readonly IScadaService _client;
        private readonly string _sessionId;
        private readonly List<string> _tags;
        private readonly Action<string, Vtq> _onUpdate;
        private readonly Action<Exception>? _onStreamError;
        private readonly ILogger<LmxProxyClient> _logger;
        private readonly Action<ISubscription>? _onDispose;
        private readonly CancellationTokenSource _cts = new();
        private Task? _processingTask;
        private bool _disposed;
        private bool _streamErrorFired;

        public CodeFirstSubscription(
            IScadaService client,
            string sessionId,
            List<string> tags,
            Action<string, Vtq> onUpdate,
            Action<Exception>? onStreamError,
            ILogger<LmxProxyClient> logger,
            Action<ISubscription>? onDispose)
        {
            _client = client;
            _sessionId = sessionId;
            _tags = tags;
            _onUpdate = onUpdate;
            _onStreamError = onStreamError;
            _logger = logger;
            _onDispose = onDispose;
        }

        public Task StartAsync(CancellationToken cancellationToken)
        {
            _processingTask = ProcessUpdatesAsync(cancellationToken);
            return Task.CompletedTask;
        }

        private async Task ProcessUpdatesAsync(CancellationToken cancellationToken)
        {
            try
            {
                var request = new SubscribeRequest
                {
                    SessionId = _sessionId,
                    Tags = _tags,
                    SamplingMs = 1000
                };
                using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _cts.Token);

                await foreach (VtqMessage vtqMsg in _client.SubscribeAsync(request, linkedCts.Token))
                {
                    try
                    {
                        Vtq vtq = ConvertVtqMessage(vtqMsg);
                        _onUpdate(vtqMsg.Tag, vtq);
                    }
                    catch (Exception ex)
                    {
                        _logger.LogError(ex, "Error processing subscription update for {Tag}", vtqMsg.Tag);
                    }
                }
            }
            catch (OperationCanceledException) when (_cts.IsCancellationRequested || cancellationToken.IsCancellationRequested)
            {
                _logger.LogDebug("Subscription cancelled");
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error in subscription processing");
                FireStreamError(ex);
            }
            finally
            {
                if (!_disposed)
                {
                    _disposed = true;
                    _onDispose?.Invoke(this);
                }
            }
        }

        private void FireStreamError(Exception ex)
        {
            if (_streamErrorFired) return;
            _streamErrorFired = true;
            try { _onStreamError?.Invoke(ex); }
            catch (Exception cbEx) { _logger.LogWarning(cbEx, "onStreamError callback threw"); }
        }

        public async Task DisposeAsync()
        {
            if (_disposed) return;
            _disposed = true;

            await _cts.CancelAsync();

            if (_processingTask is not null)
            {
                try
                {
                    await _processingTask.WaitAsync(TimeSpan.FromSeconds(5));
                }
                catch { /* swallow timeout or cancellation */ }
            }

            _cts.Dispose();
        }

        public void Dispose()
        {
            if (_disposed) return;
            try
            {
                DisposeAsync().Wait(TimeSpan.FromSeconds(5));
            }
            catch { /* swallow */ }
        }
    }
}
