using ZB.MOM.WW.HistorianGateway.Contracts.Grpc;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;

namespace ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway.Mapping;

/// <summary>
/// Maps gateway wire samples (<see cref="HistorianSample"/> / <see cref="HistorianAggregateSample"/>)
/// onto the driver-agnostic <see cref="DataValueSnapshot"/>, mirroring the legacy Wonderware client's
/// <c>ToSnapshots</c> / <c>ToAggregateSnapshots</c> conventions.
/// </summary>
internal static class SampleMapper
{
    private const uint StatusGood = HistorianStatusCodes.Good;
    private const uint StatusBadNoData = HistorianStatusCodes.BadNoData;

    /// <summary>OPC DA "Good" family floor — a quality byte at/above this carries usable data.</summary>
    private const byte GoodQualityFloor = 192;

    /// <summary>Maps a single raw sample to a value snapshot.</summary>
    /// <param name="sample">The gateway raw sample.</param>
    /// <returns>The driver-agnostic snapshot.</returns>
    public static DataValueSnapshot ToSnapshot(HistorianSample sample)
    {
        // proto3 explicit presence: prefer the numeric value, else the string value, else null.
        object? value;
        if (sample.HasNumericValue)
            value = sample.NumericValue;          // boxes as System.Double
        else if (sample.HasStringValue)
            value = sample.StringValue;
        else
            value = null;

        // Prefer the OPC DA quality byte (opc_quality); the gateway populates it directly from the
        // SDK's OpcQuality, so it is the authoritative byte for GatewayQualityMapper. Fall back to
        // the historian quality field only when opc_quality is unset (0).
        byte qualityByte = sample.OpcQuality != 0 ? (byte)sample.OpcQuality : (byte)sample.Quality;

        return new DataValueSnapshot(
            Value: value,
            StatusCode: GatewayQualityMapper.Map(qualityByte),
            SourceTimestampUtc: sample.Timestamp?.ToDateTime(),  // Utc kind
            ServerTimestampUtc: DateTime.UtcNow);
    }

    /// <summary>Maps a batch of raw samples to value snapshots, in order.</summary>
    /// <param name="samples">The gateway raw samples.</param>
    /// <returns>The driver-agnostic snapshots.</returns>
    public static IReadOnlyList<DataValueSnapshot> ToSnapshots(IEnumerable<HistorianSample> samples)
    {
        var result = new List<DataValueSnapshot>();
        foreach (var sample in samples)
            result.Add(ToSnapshot(sample));
        return result;
    }

    /// <summary>Maps a single aggregate bucket to a value snapshot.</summary>
    /// <param name="aggregate">The gateway aggregate sample.</param>
    /// <returns>The driver-agnostic snapshot.</returns>
    /// <remarks>
    /// Unlike the legacy Wonderware DTO (a nullable <c>double?</c>), the gateway proto carries a
    /// non-optional <c>double value</c>, so an unavailable (no-data) bucket cannot be signalled by a
    /// null value. Instead it is signalled by a non-Good <c>opc_quality</c>: a Good bucket
    /// (<c>opc_quality &gt;= 192</c>) yields its value with a Good status, anything else maps to
    /// <c>BadNoData</c> with a null value — preserving the Wonderware aggregate contract (binary
    /// Good-with-value / BadNoData-null).
    /// </remarks>
    public static DataValueSnapshot ToAggregateSnapshot(HistorianAggregateSample aggregate)
    {
        bool hasData = aggregate.OpcQuality >= GoodQualityFloor;
        return new DataValueSnapshot(
            Value: hasData ? aggregate.Value : null,                 // boxes as System.Double when present
            StatusCode: hasData ? StatusGood : StatusBadNoData,
            SourceTimestampUtc: (aggregate.EndTime ?? aggregate.StartTime)?.ToDateTime(),  // bucket timestamp
            ServerTimestampUtc: DateTime.UtcNow);
    }

    /// <summary>Maps a batch of aggregate buckets to value snapshots, in order.</summary>
    /// <param name="aggregates">The gateway aggregate samples.</param>
    /// <returns>The driver-agnostic snapshots.</returns>
    public static IReadOnlyList<DataValueSnapshot> ToAggregateSnapshots(IEnumerable<HistorianAggregateSample> aggregates)
    {
        var result = new List<DataValueSnapshot>();
        foreach (var aggregate in aggregates)
            result.Add(ToAggregateSnapshot(aggregate));
        return result;
    }
}
