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

namespace ZB.MOM.WW.LmxProxy.Host.MxAccess
{
    public sealed partial class MxAccessClient
    {
        /// <summary>
        ///     Callback invoked by the SubscriptionManager when it needs to deliver
        ///     data change events. Set by the SubscriptionManager during initialization.
        /// </summary>
        public Action<string, Vtq>? OnTagValueChanged { get; set; }

        /// <summary>
        ///     COM event handler for MxAccess OnDataChange events.
        ///     Signature matches the ArchestrA.MxAccess ILMXProxyServerEvents interface.
        /// </summary>
        private void OnDataChange(
            int hLMXServerHandle,
            int phItemHandle,
            object pvItemValue,
            int pwItemQuality,
            object pftItemTimeStamp,
            ref MXSTATUS_PROXY[] ItemStatus)
        {
            try
            {
                var quality = MapQuality(pwItemQuality);
                var timestamp = ConvertTimestamp(pftItemTimeStamp);

                // Check MXSTATUS_PROXY — if success is false, override quality
                // with a more specific code derived from the MxAccess status fields
                if (ItemStatus != null && ItemStatus.Length > 0 && ItemStatus[0].success == 0)
                {
                    var status = ItemStatus[0];
                    quality = MxStatusMapper.CategoryToQuality((int)status.category, status.detail);
                    Log.Debug("OnDataChange status failure for handle {Handle}: {Status}",
                        phItemHandle, MxStatusMapper.FormatStatus(status.detail, (int)status.category, (int)status.detectedBy));
                }

                var vtq = new Vtq(pvItemValue, timestamp, quality);

                // Resolve address from handle map
                string address;
                lock (_lock)
                {
                    if (!_handleToAddress.TryGetValue(phItemHandle, out address))
                    {
                        Log.Debug("OnDataChange for unknown handle {Handle}, ignoring", phItemHandle);
                        return;
                    }
                }

                // Invoke the stored subscription callback
                Action<string, Vtq> callback;
                lock (_lock)
                {
                    if (!_storedSubscriptions.TryGetValue(address, out callback))
                    {
                        Log.Debug("OnDataChange for {Address} but no callback registered", address);
                        return;
                    }
                }

                // Update probe timestamp if this is the probe tag
                if (_probeTestTagAddress != null &&
                    string.Equals(address, _probeTestTagAddress, StringComparison.OrdinalIgnoreCase))
                {
                    OnProbeDataChange(address, vtq);
                }

                callback.Invoke(address, vtq);

                // Also route to the SubscriptionManager's global handler
                OnTagValueChanged?.Invoke(address, vtq);
            }
            catch (Exception ex)
            {
                Log.Error(ex, "Error processing OnDataChange event for handle {Handle}", phItemHandle);
            }
        }

        /// <summary>
        ///     COM event handler for MxAccess OnWriteComplete events.
        ///     Resolves the pending TaskCompletionSource so the caller gets
        ///     confirmation (or error) from the OnWriteComplete callback.
        /// </summary>
        private void OnWriteComplete(
            int hLMXServerHandle,
            int phItemHandle,
            ref MXSTATUS_PROXY[] ItemStatus)
        {
            try
            {
                TaskCompletionSource<bool> tcs;
                bool hasPending;
                lock (_lock)
                {
                    hasPending = _pendingWrites.TryGetValue(phItemHandle, out tcs);
                }

                if (ItemStatus != null && ItemStatus.Length > 0)
                {
                    var status = ItemStatus[0];
                    if (status.success == 0)
                    {
                        string errorMsg = MxStatusMapper.FormatStatus(status.detail, (int)status.category, (int)status.detectedBy);
                        Log.Warning("OnWriteComplete: write failed for handle {Handle}: {Status}", phItemHandle, errorMsg);
                        if (hasPending) tcs.TrySetException(new InvalidOperationException("Write failed: " + errorMsg));
                    }
                    else
                    {
                        Log.Debug("OnWriteComplete: write succeeded for handle {Handle}", phItemHandle);
                        if (hasPending) tcs.TrySetResult(true);
                    }
                }
                else
                {
                    Log.Debug("OnWriteComplete: no status for handle {Handle}", phItemHandle);
                    tcs?.TrySetResult(true);
                }
            }
            catch (Exception ex)
            {
                Log.Error(ex, "Error processing OnWriteComplete event for handle {Handle}", phItemHandle);
            }
        }

        /// <summary>
        ///     Converts a timestamp object to DateTime in UTC.
        /// </summary>
        private static DateTime ConvertTimestamp(object timestamp)
        {
            if (timestamp is DateTime dt)
            {
                return dt.Kind == DateTimeKind.Utc ? dt : dt.ToUniversalTime();
            }

            return DateTime.UtcNow;
        }
    }
}
