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;

    public MxAccessAlarmEventSink()
        : this(new MxAccessEventQueue(), new MxAccessEventMapper())
    {
    }

    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>
    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)
    {
        try
        {
            MxEvent mxEvent = eventMapper.CreateOnAlarmTransition(
                sessionId,
                alarmFullReference,
                sourceObjectReference,
                alarmTypeName,
                transitionKind,
                severity,
                originalRaiseTimestampUtc,
                transitionTimestampUtc,
                operatorUser,
                operatorComment,
                category,
                description,
                statuses: null);
            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.",
                },
            });
        }
    }
}
