using ZB.MOM.WW.OtOpcUa.Core.AlarmHistorian;
using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Shared;
using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Shared.Contracts;

namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Proxy.Ipc;

/// <summary>
///     Phase 7 follow-up (task #247) — bridges <see cref="SqliteStoreAndForwardSink"/>'s
///     drain worker to <c>Driver.Galaxy.Host</c> over the existing <see cref="GalaxyIpcClient"/>
///     pipe. Translates <see cref="AlarmHistorianEvent"/> batches into the
///     <see cref="HistorianAlarmEventDto"/> wire format the Host expects + maps per-event
///     <see cref="HistorianAlarmEventOutcomeDto"/> responses back to
///     <see cref="HistorianWriteOutcome"/> so the SQLite queue knows what to ack /
///     dead-letter / retry.
/// </summary>
/// <remarks>
///     <para>
///         Reuses the IPC channel <see cref="GalaxyProxyDriver"/> already opens for the
///         Galaxy data plane — no second pipe to <c>Driver.Galaxy.Host</c>, no separate
///         auth handshake. The IPC client's call gate serializes historian batches with
///         driver Reads/Writes/Subscribes; historian batches are infrequent (every few
///         seconds at most under the SQLite sink's drain cadence) so the contention is
///         negligible compared to per-tag-read pressure.
///     </para>
///     <para>
///         Pipe-level transport faults (broken pipe, host crash) bubble up as
///         <see cref="GalaxyIpcException"/> which the SQLite sink's drain worker catches +
///         translates to a whole-batch RetryPlease per the
///         <see cref="SqliteStoreAndForwardSink"/> docstring — failed events stay queued
///         for the next drain tick after backoff.
///     </para>
/// </remarks>
public sealed class GalaxyHistorianWriter : IAlarmHistorianWriter
{
    private readonly GalaxyIpcClient _client;

    public GalaxyHistorianWriter(GalaxyIpcClient client)
    {
        _client = client ?? throw new ArgumentNullException(nameof(client));
    }

    public async Task<IReadOnlyList<HistorianWriteOutcome>> WriteBatchAsync(
        IReadOnlyList<AlarmHistorianEvent> batch, CancellationToken cancellationToken)
    {
        ArgumentNullException.ThrowIfNull(batch);
        if (batch.Count == 0) return [];

        var request = new HistorianAlarmEventRequest
        {
            Events = batch.Select(ToDto).ToArray(),
        };

        var response = await _client.CallAsync<HistorianAlarmEventRequest, HistorianAlarmEventResponse>(
            requestKind: MessageKind.HistorianAlarmEventRequest,
            request: request,
            expectedResponseKind: MessageKind.HistorianAlarmEventResponse,
            ct: cancellationToken).ConfigureAwait(false);

        if (response.Outcomes.Length != batch.Count)
            throw new InvalidOperationException(
                $"Galaxy.Host returned {response.Outcomes.Length} outcomes for a batch of {batch.Count} — protocol mismatch");

        var outcomes = new HistorianWriteOutcome[response.Outcomes.Length];
        for (var i = 0; i < response.Outcomes.Length; i++)
            outcomes[i] = MapOutcome(response.Outcomes[i]);
        return outcomes;
    }

    internal static HistorianAlarmEventDto ToDto(AlarmHistorianEvent e) => new()
    {
        AlarmId = e.AlarmId,
        EquipmentPath = e.EquipmentPath,
        AlarmName = e.AlarmName,
        AlarmTypeName = e.AlarmTypeName,
        Severity = (int)e.Severity,
        EventKind = e.EventKind,
        Message = e.Message,
        User = e.User,
        Comment = e.Comment,
        TimestampUtcUnixMs = new DateTimeOffset(e.TimestampUtc, TimeSpan.Zero).ToUnixTimeMilliseconds(),
    };

    internal static HistorianWriteOutcome MapOutcome(HistorianAlarmEventOutcomeDto wire) => wire switch
    {
        HistorianAlarmEventOutcomeDto.Ack => HistorianWriteOutcome.Ack,
        HistorianAlarmEventOutcomeDto.RetryPlease => HistorianWriteOutcome.RetryPlease,
        HistorianAlarmEventOutcomeDto.PermanentFail => HistorianWriteOutcome.PermanentFail,
        _ => throw new InvalidOperationException($"Unknown HistorianAlarmEventOutcomeDto byte {(byte)wire}"),
    };
}
