using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using ArchestrA.MxAccess;
using Serilog;
using ZB.MOM.WW.LmxProxy.Host.Domain;

namespace ZB.MOM.WW.LmxProxy.Host.MxAccess
{
    /// <summary>
    ///     Wraps the ArchestrA MXAccess COM API. All COM operations
    ///     execute on a dedicated STA thread with a Windows message pump
    ///     so that COM callbacks (OnDataChange, OnWriteComplete) are
    ///     delivered correctly.
    /// </summary>
    public sealed partial class MxAccessClient : IScadaClient
    {
        private static readonly ILogger Log = Serilog.Log.ForContext<MxAccessClient>();

        private readonly object _lock = new object();
        private readonly int _maxConcurrentOperations;
        private readonly int _readTimeoutMs;
        private readonly int _writeTimeoutMs;
        private readonly int _monitorIntervalMs;
        private readonly bool _autoReconnect;
        private readonly string? _nodeName;
        private readonly string? _galaxyName;
        private readonly string _clientName;

        private readonly SemaphoreSlim _readSemaphore;
        private readonly SemaphoreSlim _writeSemaphore;

        // STA thread for COM interop
        private readonly StaComThread _staThread;

        // COM objects — only accessed on the STA thread
        private LMXProxyServer? _lmxProxy;
        private int _connectionHandle;

        // State
        private ConnectionState _connectionState = ConnectionState.Disconnected;
        private DateTime _connectedSince;
        private bool _disposed;

        // Reconnect
        private CancellationTokenSource? _reconnectCts;

        // Probe configuration
        private readonly string? _probeTestTagAddress;
        private readonly int _probeStaleThresholdMs;

        // Probe state — updated by OnDataChange callback, read by monitor loop
        private DateTime _lastProbeValueTime;

        // Reconnect counter
        private int _reconnectCount;

        // Stored subscriptions for reconnect replay
        private readonly Dictionary<string, Action<string, Vtq>> _storedSubscriptions
            = new Dictionary<string, Action<string, Vtq>>(StringComparer.OrdinalIgnoreCase);

        // Handle-to-address mapping for resolving COM callbacks
        private readonly Dictionary<int, string> _handleToAddress = new Dictionary<int, string>();

        // Address-to-handle mapping for unsubscribe by address
        private readonly Dictionary<string, int> _addressToHandle
            = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);

        // Pending write operations tracked by item handle
        private readonly Dictionary<int, TaskCompletionSource<bool>> _pendingWrites
            = new Dictionary<int, TaskCompletionSource<bool>>();

        public MxAccessClient(
            int maxConcurrentOperations = 10,
            int readTimeoutSeconds = 5,
            int writeTimeoutSeconds = 5,
            int monitorIntervalSeconds = 5,
            bool autoReconnect = true,
            string? nodeName = null,
            string? galaxyName = null,
            string? probeTestTagAddress = null,
            int probeStaleThresholdMs = 5000,
            string? clientName = null)
        {
            _maxConcurrentOperations = maxConcurrentOperations;
            _readTimeoutMs = readTimeoutSeconds * 1000;
            _writeTimeoutMs = writeTimeoutSeconds * 1000;
            _monitorIntervalMs = monitorIntervalSeconds * 1000;
            _autoReconnect = autoReconnect;
            _nodeName = nodeName;
            _galaxyName = galaxyName;
            _probeTestTagAddress = probeTestTagAddress;
            _probeStaleThresholdMs = probeStaleThresholdMs;
            _clientName = clientName ?? "LmxProxy-" + Guid.NewGuid().ToString("N").Substring(0, 8);

            _readSemaphore = new SemaphoreSlim(maxConcurrentOperations, maxConcurrentOperations);
            _writeSemaphore = new SemaphoreSlim(maxConcurrentOperations, maxConcurrentOperations);

            _staThread = new StaComThread();
            _staThread.Start();
        }

        public bool IsConnected
        {
            get
            {
                lock (_lock)
                {
                    return _lmxProxy != null
                        && _connectionState == ConnectionState.Connected
                        && _connectionHandle > 0;
                }
            }
        }

        public ConnectionState ConnectionState
        {
            get { lock (_lock) { return _connectionState; } }
        }

        public event EventHandler<ConnectionStateChangedEventArgs>? ConnectionStateChanged;

        private void SetState(ConnectionState newState, string? message = null)
        {
            ConnectionState previousState;
            lock (_lock)
            {
                previousState = _connectionState;
                _connectionState = newState;
            }

            if (previousState != newState)
            {
                Log.Information("Connection state changed: {Previous} -> {Current} {Message}",
                    previousState, newState, message ?? "");
                ConnectionStateChanged?.Invoke(this,
                    new ConnectionStateChangedEventArgs(previousState, newState, message));
            }
        }

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

            _reconnectCts?.Cancel();

            try
            {
                await DisconnectAsync();
            }
            catch (Exception ex)
            {
                Log.Warning(ex, "Error during disposal disconnect");
            }

            _readSemaphore.Dispose();
            _writeSemaphore.Dispose();
            _reconnectCts?.Dispose();
            _staThread.Dispose();
        }
    }
}
