using System.Threading.Channels;
using Akka.Actor;
using Akka.Event;
using Google.Protobuf.WellKnownTypes;
using ScadaLink.Commons.Messages.Streaming;
using ScadaLink.Commons.Types;
using ScadaLink.Communication.Grpc;
using AlarmState = ScadaLink.Commons.Types.Enums.AlarmState;
using AlarmLevel = ScadaLink.Commons.Types.Enums.AlarmLevel;

namespace ScadaLink.Communication.Actors;

/// <summary>
/// Lightweight relay actor that bridges Akka domain events (AttributeValueChanged,
/// AlarmStateChanged) to a System.Threading.Channels.Channel of protobuf SiteStreamEvent
/// messages. The gRPC server method reads from the channel's reader side.
/// </summary>
public class StreamRelayActor : ReceiveActor
{
    private readonly ILoggingAdapter _log = Context.GetLogger();
    private readonly string _correlationId;
    private readonly ChannelWriter<SiteStreamEvent> _channelWriter;

    public StreamRelayActor(string correlationId, ChannelWriter<SiteStreamEvent> channelWriter)
    {
        _correlationId = correlationId;
        _channelWriter = channelWriter;

        Receive<AttributeValueChanged>(HandleAttributeValueChanged);
        Receive<AlarmStateChanged>(HandleAlarmStateChanged);
    }

    private void HandleAttributeValueChanged(AttributeValueChanged msg)
    {
        var protoEvent = new SiteStreamEvent
        {
            CorrelationId = _correlationId,
            AttributeChanged = new AttributeValueUpdate
            {
                InstanceUniqueName = msg.InstanceUniqueName,
                AttributePath = msg.AttributePath,
                AttributeName = msg.AttributeName,
                Value = ValueFormatter.FormatDisplayValue(msg.Value),
                Quality = MapQuality(msg.Quality),
                Timestamp = Timestamp.FromDateTimeOffset(msg.Timestamp)
            }
        };

        WriteToChannel(protoEvent);
    }

    private void HandleAlarmStateChanged(AlarmStateChanged msg)
    {
        var protoEvent = new SiteStreamEvent
        {
            CorrelationId = _correlationId,
            AlarmChanged = new AlarmStateUpdate
            {
                InstanceUniqueName = msg.InstanceUniqueName,
                AlarmName = msg.AlarmName,
                State = MapAlarmState(msg.State),
                Priority = msg.Priority,
                Timestamp = Timestamp.FromDateTimeOffset(msg.Timestamp),
                Level = MapAlarmLevel(msg.Level),
                Message = msg.Message ?? string.Empty
            }
        };

        WriteToChannel(protoEvent);
    }

    private void WriteToChannel(SiteStreamEvent protoEvent)
    {
        if (!_channelWriter.TryWrite(protoEvent))
        {
            _log.Warning("Channel full, dropping event for correlation {0}", _correlationId);
        }
    }

    private static Quality MapQuality(string quality) => quality switch
    {
        "Good" => Quality.Good,
        "Uncertain" => Quality.Uncertain,
        "Bad" => Quality.Bad,
        _ => Quality.Unspecified
    };

    private static AlarmStateEnum MapAlarmState(AlarmState state) => state switch
    {
        AlarmState.Normal => AlarmStateEnum.AlarmStateNormal,
        AlarmState.Active => AlarmStateEnum.AlarmStateActive,
        _ => AlarmStateEnum.AlarmStateUnspecified
    };

    private static AlarmLevelEnum MapAlarmLevel(AlarmLevel level) => level switch
    {
        AlarmLevel.Low => AlarmLevelEnum.AlarmLevelLow,
        AlarmLevel.LowLow => AlarmLevelEnum.AlarmLevelLowLow,
        AlarmLevel.High => AlarmLevelEnum.AlarmLevelHigh,
        AlarmLevel.HighHigh => AlarmLevelEnum.AlarmLevelHighHigh,
        _ => AlarmLevelEnum.AlarmLevelNone
    };
}
