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

namespace ZB.MOM.WW.LmxProxy.Host.MxAccess
{
    public sealed partial class MxAccessClient
    {
        /// <summary>
        ///     Reads a single tag value from MxAccess.
        ///     Uses subscribe-get-first-value-unsubscribe pattern (same as v1).
        /// </summary>
        public async Task<Vtq> ReadAsync(string address, CancellationToken ct = default)
        {
            if (!IsConnected)
                return Vtq.New(null, Quality.Bad_NotConnected);

            await _readSemaphore.WaitAsync(ct);
            try
            {
                return await ReadSingleValueAsync(address, ct);
            }
            catch (System.Runtime.InteropServices.COMException comEx)
            {
                Log.Error(comEx, "COM read error for tag {Address}: HRESULT=0x{ErrorCode:X8}", address, comEx.ErrorCode);
                return Vtq.New(null, Quality.Bad_CommFailure);
            }
            catch (TimeoutException)
            {
                Log.Warning("Read timed out for tag {Address}", address);
                return Vtq.New(null, Quality.Bad_CommFailure);
            }
            catch (Exception ex)
            {
                Log.Error(ex, "ReadAsync failed for tag {Address}", address);
                return Vtq.New(null, Quality.Bad_CommFailure);
            }
            finally
            {
                _readSemaphore.Release();
            }
        }

        /// <summary>
        ///     Reads multiple tags with semaphore-controlled concurrency (max 10 concurrent).
        ///     Each tag is read independently. Partial failures return Bad quality for failed tags.
        /// </summary>
        public async Task<IReadOnlyDictionary<string, Vtq>> ReadBatchAsync(
            IEnumerable<string> addresses, CancellationToken ct = default)
        {
            var addressList = addresses.ToList();
            var results = new Dictionary<string, Vtq>(addressList.Count, StringComparer.OrdinalIgnoreCase);

            var tasks = addressList.Select(async address =>
            {
                var vtq = await ReadAsync(address, ct);
                return (address, vtq);
            });

            foreach (var task in await Task.WhenAll(tasks))
            {
                results[task.address] = task.vtq;
            }

            return results;
        }

        /// <summary>
        ///     Writes a single tag value to MxAccess.
        ///     Uses Task.Run for COM calls. Write completes synchronously (fire-and-forget).
        /// </summary>
        public async Task WriteAsync(string address, object value, CancellationToken ct = default)
        {
            if (!IsConnected)
                throw new InvalidOperationException("Not connected to MxAccess");

            await _writeSemaphore.WaitAsync(ct);
            try
            {
                await WriteInternalAsync(address, value, ct);
            }
            finally
            {
                _writeSemaphore.Release();
            }
        }

        /// <summary>
        ///     Writes multiple tag values with semaphore-controlled concurrency.
        /// </summary>
        public async Task WriteBatchAsync(
            IReadOnlyDictionary<string, object> values, CancellationToken ct = default)
        {
            var tasks = values.Select(async kvp =>
            {
                await WriteAsync(kvp.Key, kvp.Value, ct);
            });

            await Task.WhenAll(tasks);
        }

        /// <summary>
        ///     Writes a batch, then polls flagTag until it equals flagValue or timeout expires.
        ///     Uses type-aware comparison via TypedValueComparer.
        /// </summary>
        public async Task<(bool flagReached, int elapsedMs)> WriteBatchAndWaitAsync(
            IReadOnlyDictionary<string, object> values,
            string flagTag,
            object flagValue,
            int timeoutMs,
            int pollIntervalMs,
            CancellationToken ct = default)
        {
            // Write all values first
            await WriteBatchAsync(values, ct);

            // Poll flag tag
            var sw = System.Diagnostics.Stopwatch.StartNew();
            var effectiveTimeout = timeoutMs > 0 ? timeoutMs : 5000;
            var effectiveInterval = pollIntervalMs > 0 ? pollIntervalMs : 100;

            while (sw.ElapsedMilliseconds < effectiveTimeout)
            {
                ct.ThrowIfCancellationRequested();

                var vtq = await ReadAsync(flagTag, ct);
                if (vtq.Quality.IsGood() && TypedValueComparer.Equals(vtq.Value, flagValue))
                {
                    return (true, (int)sw.ElapsedMilliseconds);
                }

                await Task.Delay(effectiveInterval, ct);
            }

            return (false, (int)sw.ElapsedMilliseconds);
        }

        // ── Private read/write helpers ──────────

        /// <summary>
        ///     Reads a single value by subscribing, waiting for the first data change callback,
        ///     then unsubscribing. This is the same pattern as v1.
        /// </summary>
        private async Task<Vtq> ReadSingleValueAsync(string address, CancellationToken ct)
        {
            var tcs = new TaskCompletionSource<Vtq>();
            IAsyncDisposable? subscription = null;

            try
            {
                subscription = await SubscribeAsync(
                    new[] { address },
                    (addr, vtq) => { tcs.TrySetResult(vtq); },
                    ct);

                return await WaitForReadResultAsync(tcs, ct);
            }
            finally
            {
                if (subscription != null)
                {
                    await subscription.DisposeAsync();
                }
            }
        }

        /// <summary>
        ///     Waits for a read result with timeout.
        /// </summary>
        private async Task<Vtq> WaitForReadResultAsync(TaskCompletionSource<Vtq> tcs, CancellationToken ct)
        {
            using (var cts = new CancellationTokenSource(_readTimeoutMs))
            using (ct.Register(() => cts.Cancel()))
            {
                cts.Token.Register(() => tcs.TrySetException(
                    new TimeoutException("Read timeout")));
                return await tcs.Task;
            }
        }

        /// <summary>
        ///     Internal write implementation dispatched on the STA thread.
        ///     Registers a TaskCompletionSource, calls Write(), then awaits the
        ///     OnWriteComplete callback via the STA message pump. Falls back to
        ///     fire-and-forget if the callback doesn't arrive within the timeout.
        /// </summary>
        private async Task WriteInternalAsync(string address, object value, CancellationToken ct)
        {
            var tcs = new TaskCompletionSource<bool>();
            int itemHandle = 0;

            // Step 1: Setup and write on the STA thread
            await _staThread.RunAsync(() =>
            {
                lock (_lock)
                {
                    if (!IsConnected || _lmxProxy == null)
                        throw new InvalidOperationException("Not connected to MxAccess");

                    try
                    {
                        itemHandle = _lmxProxy.AddItem(_connectionHandle, address);
                        _lmxProxy.AdviseSupervisory(_connectionHandle, itemHandle);

                        // Register for OnWriteComplete callback
                        _pendingWrites[itemHandle] = tcs;

                        // Write the value (-1 = no security classification)
                        _lmxProxy.Write(_connectionHandle, itemHandle, value, -1);

                        Log.Debug("Write dispatched for {Address} (handle={Handle}), awaiting OnWriteComplete",
                            address, itemHandle);
                    }
                    catch (System.Runtime.InteropServices.COMException comEx)
                    {
                        _pendingWrites.Remove(itemHandle);
                        string enriched = string.Format("Write failed for '{0}': COM error 0x{1:X8} — {2}",
                            address, comEx.ErrorCode, comEx.Message);
                        Log.Error(comEx, "COM write error for {Address}: HRESULT=0x{ErrorCode:X8}",
                            address, comEx.ErrorCode);
                        throw new InvalidOperationException(enriched, comEx);
                    }
                    catch (Exception ex)
                    {
                        _pendingWrites.Remove(itemHandle);
                        Log.Error(ex, "Failed to write value to {Address}", address);
                        throw;
                    }
                }
            });

            // Step 2: Wait for OnWriteComplete callback (delivered via STA message pump)
            try
            {
                using (var cts = new CancellationTokenSource(_writeTimeoutMs))
                using (ct.Register(() => cts.Cancel()))
                {
                    cts.Token.Register(() => tcs.TrySetResult(true)); // timeout = assume success (fire-and-forget fallback)
                    await tcs.Task;
                }
            }
            finally
            {
                // Step 3: Clean up on the STA thread
                if (itemHandle > 0)
                {
                    try
                    {
                        await _staThread.RunAsync(() =>
                        {
                            lock (_lock)
                            {
                                _pendingWrites.Remove(itemHandle);
                                if (_lmxProxy != null && _connectionHandle > 0)
                                {
                                    try
                                    {
                                        _lmxProxy.UnAdvise(_connectionHandle, itemHandle);
                                        _lmxProxy.RemoveItem(_connectionHandle, itemHandle);
                                    }
                                    catch (Exception ex)
                                    {
                                        Log.Debug(ex, "Error cleaning up write item for {Address} (handle={Handle})", address, itemHandle);
                                    }
                                }
                            }
                        });
                    }
                    catch (Exception ex)
                    {
                        Log.Debug(ex, "Error dispatching write cleanup for {Address}", address);
                    }
                }
            }
        }

        /// <summary>
        ///     Maps an MxAccess OPC DA quality integer to the domain Quality enum.
        /// </summary>
        private static Quality MapQuality(int opcDaQuality)
        {
            if (Enum.IsDefined(typeof(Quality), (byte)opcDaQuality))
                return (Quality)(byte)opcDaQuality;

            // Fallback: use category bits
            if (opcDaQuality >= 192) return Quality.Good;
            if (opcDaQuality >= 64) return Quality.Uncertain;
            return Quality.Bad;
        }
    }
}
