using System.Text.Json;
using Akka.Actor;
using Akka.Event;
using ZB.MOM.WW.ScadaBridge.Commons.Messages.RemoteQuery;

namespace ZB.MOM.WW.ScadaBridge.StoreAndForward;

/// <summary>
/// Akka actor bridge for <see cref="StoreAndForwardService"/> parked-message operations.
/// Receives Query/Retry/Discard requests from the SiteCommunicationActor and replies
/// with the matching response records.
/// </summary>
public class ParkedMessageHandlerActor : ReceiveActor
{
    private readonly ILoggingAdapter _log = Context.GetLogger();
    private readonly StoreAndForwardService _service;
    private readonly string _siteId;

    /// <summary>
    /// Initializes the actor and registers message handlers for query, retry, and discard operations.
    /// </summary>
    /// <param name="service">The store-and-forward service used to execute parked-message operations.</param>
    /// <param name="siteId">The site identifier this actor manages parked messages for.</param>
    public ParkedMessageHandlerActor(StoreAndForwardService service, string siteId)
    {
        _service = service;
        _siteId = siteId;

        Receive<ParkedMessageQueryRequest>(HandleQuery);
        Receive<ParkedMessageRetryRequest>(HandleRetry);
        Receive<ParkedMessageDiscardRequest>(HandleDiscard);

        // Central→site Retry/Discard relay for parked cached
        // operations. The cached call's S&F buffer message id is the
        // TrackedOperationId, so these reuse the same parked-message primitive
        // as HandleRetry/HandleDiscard, keyed off the tracked id.
        Receive<RetryParkedOperation>(HandleRetryParkedOperation);
        Receive<DiscardParkedOperation>(HandleDiscardParkedOperation);
    }

    private void HandleQuery(ParkedMessageQueryRequest msg)
    {
        var sender = Sender;
        var siteId = _siteId;

        _service.GetParkedMessagesAsync(category: null, msg.PageNumber, msg.PageSize)
            .PipeTo(
                sender,
                success: result =>
                {
                    var entries = result.Messages
                        .Select(m => new ParkedMessageEntry(
                            MessageId: m.Id,
                            TargetSystem: m.Target,
                            MethodName: ExtractMethodName(m.PayloadJson, m.Category),
                            ErrorMessage: m.LastError ?? string.Empty,
                            AttemptCount: m.RetryCount,
                            OriginalTimestamp: m.CreatedAt,
                            LastAttemptTimestamp: m.LastAttemptAt ?? m.CreatedAt,
                            MaxAttempts: m.MaxRetries,
                            Category: m.Category,
                            OriginInstance: m.OriginInstanceName))
                        .ToList();

                    return new ParkedMessageQueryResponse(
                        msg.CorrelationId, siteId, entries, result.TotalCount,
                        msg.PageNumber, msg.PageSize, true, null, DateTimeOffset.UtcNow);
                },
                failure: ex => new ParkedMessageQueryResponse(
                    msg.CorrelationId, siteId, [], 0, msg.PageNumber, msg.PageSize,
                    false, ex.GetBaseException().Message, DateTimeOffset.UtcNow));
    }

    private void HandleRetry(ParkedMessageRetryRequest msg)
    {
        var sender = Sender;

        _service.RetryParkedMessageAsync(msg.MessageId)
            .PipeTo(
                sender,
                success: retried => new ParkedMessageRetryResponse(
                    msg.CorrelationId, retried,
                    retried ? null : "Message not found or no longer parked."),
                failure: ex => new ParkedMessageRetryResponse(
                    msg.CorrelationId, false, ex.GetBaseException().Message));
    }

    private void HandleDiscard(ParkedMessageDiscardRequest msg)
    {
        var sender = Sender;

        _service.DiscardParkedMessageAsync(msg.MessageId)
            .PipeTo(
                sender,
                success: discarded => new ParkedMessageDiscardResponse(
                    msg.CorrelationId, discarded,
                    discarded ? null : "Message not found or no longer parked."),
                failure: ex => new ParkedMessageDiscardResponse(
                    msg.CorrelationId, false, ex.GetBaseException().Message));
    }

    /// <summary>
    /// Executes a central-relayed Retry of a parked cached call.
    /// The tracked id is the S&amp;F buffer message id, so this reuses
    /// <see cref="StoreAndForwardService.RetryParkedMessageAsync"/> — which only
    /// touches rows that are actually <c>Parked</c> (a non-parked or unknown
    /// operation yields <c>false</c>, a safe no-op). Central never mutates the
    /// central <c>SiteCalls</c> mirror; the reset row's corrected state flows
    /// back via the normal cached-call telemetry path.
    /// </summary>
    private void HandleRetryParkedOperation(RetryParkedOperation msg)
    {
        var sender = Sender;

        _service.RetryParkedMessageAsync(msg.TrackedOperationId.ToString())
            .PipeTo(
                sender,
                success: applied => new ParkedOperationActionAck(
                    msg.CorrelationId, applied, ErrorMessage: null),
                failure: ex => new ParkedOperationActionAck(
                    msg.CorrelationId, Applied: false, ex.GetBaseException().Message));
    }

    /// <summary>
    /// Executes a central-relayed Discard of a parked cached call.
    /// Mirrors <see cref="HandleRetryParkedOperation"/>; Discard removes the
    /// parked S&amp;F buffer row (only when it is actually <c>Parked</c>).
    /// </summary>
    private void HandleDiscardParkedOperation(DiscardParkedOperation msg)
    {
        var sender = Sender;

        _service.DiscardParkedMessageAsync(msg.TrackedOperationId.ToString())
            .PipeTo(
                sender,
                success: applied => new ParkedOperationActionAck(
                    msg.CorrelationId, applied, ErrorMessage: null),
                failure: ex => new ParkedOperationActionAck(
                    msg.CorrelationId, Applied: false, ex.GetBaseException().Message));
    }

    private static string ExtractMethodName(string payloadJson, Commons.Types.Enums.StoreAndForwardCategory category)
    {
        if (string.IsNullOrEmpty(payloadJson))
            return category.ToString();

        try
        {
            using var doc = JsonDocument.Parse(payloadJson);
            var root = doc.RootElement;
            if (root.TryGetProperty("MethodName", out var method) && method.ValueKind == JsonValueKind.String)
                return method.GetString() ?? category.ToString();
            if (root.TryGetProperty("Subject", out var subject) && subject.ValueKind == JsonValueKind.String)
                return subject.GetString() ?? category.ToString();
        }
        catch (JsonException)
        {
        }

        return category.ToString();
    }
}
