using System;
using ZB.MOM.WW.MxGateway.Contracts.Proto;

namespace ZB.MOM.WW.MxGateway.Worker.MxAccess;

/// <summary>
///     Sink for native MxAccess alarm transitions. Bridges
///     <see cref="WnWrapAlarmConsumer"/> to the worker's event queue,
///     producing <see cref="OnAlarmTransitionEvent"/> messages via
///     <see cref="MxAccessEventMapper.CreateOnAlarmTransition"/>.
/// </summary>
/// <remarks>
///     <para>
///         <see cref="AlarmDispatcher"/> owns the wire-up: it constructs the
///         consumer/sink pair, calls <see cref="Attach"/> to propagate the
///         session id, and subscribes the consumer's
///         <see cref="IMxAccessAlarmConsumer.AlarmTransitionEmitted"/> event
///         so each decoded transition reaches <see cref="EnqueueTransition"/>.
///         The <see cref="Attach"/> method here carries only the session id —
///         the alarm path needs no COM-event subscription of its own because
///         the consumer already polls and raises transition events. The
///         captured payload schema is described in
///         <c>docs/AlarmClientDiscovery.md</c> "Option A — captured".
///     </para>
/// </remarks>
public sealed class MxAccessAlarmEventSink : IMxAccessEventSink
{
    private readonly MxAccessEventMapper eventMapper;
    private readonly MxAccessEventQueue eventQueue;
    private string sessionId = string.Empty;
    private bool attached;

    /// <summary>Initializes a new instance of the MxAccessAlarmEventSink class with default dependencies.</summary>
    public MxAccessAlarmEventSink()
        : this(new MxAccessEventQueue(), new MxAccessEventMapper())
    {
    }

    /// <summary>Initializes a new instance of the MxAccessAlarmEventSink class.</summary>
    /// <param name="eventQueue">Queue for buffering converted alarm events.</param>
    /// <param name="eventMapper">Converter for alarm transitions to protobuf format.</param>
    public MxAccessAlarmEventSink(
        MxAccessEventQueue eventQueue,
        MxAccessEventMapper eventMapper)
    {
        this.eventQueue = eventQueue ?? throw new ArgumentNullException(nameof(eventQueue));
        this.eventMapper = eventMapper ?? throw new ArgumentNullException(nameof(eventMapper));
    }

    /// <inheritdoc />
    public void Attach(object mxAccessComObject, string sessionId)
    {
        if (mxAccessComObject is null) throw new ArgumentNullException(nameof(mxAccessComObject));
        this.sessionId = sessionId ?? string.Empty;

        // The alarm path needs no COM-event subscription here: the wnwrap
        // consumer is polled by the worker's STA and raises transition events
        // that AlarmDispatcher routes into EnqueueTransition. Attach only
        // records the session id stamped onto every emitted MxEvent.
        attached = true;
    }

    /// <inheritdoc />
    public void Detach()
    {
        if (!attached) return;
        attached = false;
        sessionId = string.Empty;
    }

    /// <summary>
    ///     Enqueues a decoded alarm transition. The COM-side delegate registered
    ///     in <see cref="Attach"/> calls this method once it pulls the alarm
    ///     fields out of the MxAccess event payload. Exposed internal so unit
    ///     tests can drive the proto build path without a real COM event
    ///     source.
    /// </summary>
    /// <param name="alarmFullReference">The fully qualified alarm object reference.</param>
    /// <param name="sourceObjectReference">The object that sourced the alarm.</param>
    /// <param name="alarmTypeName">The alarm type name.</param>
    /// <param name="transitionKind">The kind of alarm transition.</param>
    /// <param name="severity">The alarm severity level.</param>
    /// <param name="originalRaiseTimestampUtc">The original alarm raise timestamp in UTC, if available.</param>
    /// <param name="transitionTimestampUtc">The timestamp of this transition in UTC.</param>
    /// <param name="operatorUser">The user who performed the operation, if applicable.</param>
    /// <param name="operatorComment">The operator's comment, if any.</param>
    /// <param name="category">The alarm category.</param>
    /// <param name="description">The alarm description.</param>
    /// <param name="degraded">
    ///     <see langword="true"/> when the transition was synthesized by the
    ///     subtag-provider fallback rather than the native alarmmgr path.
    ///     Defaults to <see langword="false"/> so existing alarmmgr callers
    ///     compile unchanged and stay on the parity (alarmmgr) path.
    /// </param>
    internal void EnqueueTransition(
        string alarmFullReference,
        string sourceObjectReference,
        string alarmTypeName,
        AlarmTransitionKind transitionKind,
        int severity,
        DateTime? originalRaiseTimestampUtc,
        DateTime transitionTimestampUtc,
        string operatorUser,
        string operatorComment,
        string category,
        string description,
        bool degraded = false)
    {
        try
        {
            // Degraded transitions come from the subtag fallback; the native
            // alarmmgr (wnwrap) path stays degraded=false / ALARMMGR for parity.
            AlarmProviderMode sourceProvider = degraded
                ? AlarmProviderMode.Subtag
                : AlarmProviderMode.Alarmmgr;
            MxEvent mxEvent = eventMapper.CreateOnAlarmTransition(
                sessionId,
                alarmFullReference,
                sourceObjectReference,
                alarmTypeName,
                transitionKind,
                severity,
                originalRaiseTimestampUtc,
                transitionTimestampUtc,
                operatorUser,
                operatorComment,
                category,
                description,
                statuses: null,
                degraded: degraded,
                sourceProvider: sourceProvider);
            eventQueue.Enqueue(mxEvent);
        }
        catch (Exception exception)
        {
            eventQueue.RecordFault(new WorkerFault
            {
                Category = WorkerFaultCategory.MxaccessEventConversionFailed,
                ExceptionType = exception.GetType().FullName ?? string.Empty,
                DiagnosticMessage = $"{exception.GetType().FullName}: HRESULT 0x{unchecked((uint)exception.HResult):X8}",
                ProtocolStatus = new ProtocolStatus
                {
                    Code = ProtocolStatusCode.MxaccessFailure,
                    Message = "MXAccess alarm event conversion failed.",
                },
            });
        }
    }
}
