using System.Text.Json;
using MBN_STOCK_WEBVIEW.Core.Playout;
using MBN_STOCK_WEBVIEW.Core.Playout.Scenes;
using MBN_STOCK_WEBVIEW.Infrastructure;

namespace MBN_STOCK_WEBVIEW;

public sealed partial class MainWindow
{
    private async Task HandlePlaylistPagePlansRequestAsync(JsonElement payload)
    {
        var parsed = PlayoutBridgeProtocol.ParsePagePlanRequest(payload);
        var request = parsed.Request;
        if (!parsed.IsValid)
        {
            PostPlaylistPagePlansError(
                request.RequestId,
                "INVALID_REQUEST",
                parsed.Error,
                retryable: false);
            return;
        }

        var workflow = _playoutWorkflow;
        var engine = _playoutEngine;
        if (workflow is null || engine is null)
        {
            PostPlaylistPagePlansError(
                request.RequestId,
                "PLAYOUT_UNAVAILABLE",
                _playoutInitializationError ??
                    "The playout workflow is not available for page preflight.",
                retryable: false);
            return;
        }

        var requestCancellation = CancellationTokenSource.CreateLinkedTokenSource(
            _lifetimeCancellation.Token);
        var requestToken = requestCancellation.Token;
        var browserGeneration = Volatile.Read(ref _playlistPagePlanBrowserGeneration);
        var previousCancellation = Interlocked.Exchange(
            ref _playlistPagePlanCancellation,
            requestCancellation);
        CancelRequest(previousCancellation);

        var databaseActivityEntered = false;
        try
        {
            if (Volatile.Read(ref _playoutCommandInFlight) != 0)
            {
                PostPlaylistPagePlansErrorIfCurrent(
                    request.RequestId,
                    "PLAYOUT_PRIORITY",
                    "A playout command has priority over page preflight.",
                    retryable: true,
                    requestCancellation,
                    browserGeneration);
                return;
            }

            if (!IsPlaylistPagePlanIdle(workflow, engine))
            {
                PostPlaylistPagePlansErrorIfCurrent(
                    request.RequestId,
                    "PLAYOUT_NOT_IDLE",
                    "Page preflight is available only while no scene is prepared or on air.",
                    retryable: true,
                    requestCancellation,
                    browserGeneration);
                return;
            }

            await _databaseActivityGate.WaitAsync(requestToken);
            databaseActivityEntered = true;
            requestToken.ThrowIfCancellationRequested();

            if (Volatile.Read(ref _playoutCommandInFlight) != 0)
            {
                PostPlaylistPagePlansErrorIfCurrent(
                    request.RequestId,
                    "PLAYOUT_PRIORITY",
                    "A playout command cancelled page preflight before the database read.",
                    retryable: true,
                    requestCancellation,
                    browserGeneration);
                return;
            }

            if (!IsPlaylistPagePlanIdle(workflow, engine))
            {
                PostPlaylistPagePlansErrorIfCurrent(
                    request.RequestId,
                    "PLAYOUT_NOT_IDLE",
                    "Playout became active before page preflight could start.",
                    retryable: true,
                    requestCancellation,
                    browserGeneration);
                return;
            }

            var plans = await workflow.PreflightPagePlansAsync(
                request.Entries!,
                requestToken);
            requestToken.ThrowIfCancellationRequested();
            if (!IsCurrentPlaylistPagePlanRequest(requestCancellation, browserGeneration))
            {
                return;
            }

            PostMessage("playlist-page-plans-results", new
            {
                requestId = request.RequestId,
                calculatedAt = DateTimeOffset.UtcNow,
                plans = plans.Select(plan => new
                {
                    entryId = plan.EntryId,
                    itemCount = plan.ItemCount,
                    pageSize = plan.PageSize,
                    pageCount = plan.PageCount,
                    accessibleItemCount = plan.AccessibleItemCount,
                    isTruncated = plan.IsTruncated,
                    builderKey = plan.BuilderKey
                })
            });
        }
        catch (OperationCanceledException) when (requestToken.IsCancellationRequested)
        {
            if (Volatile.Read(ref _playoutCommandInFlight) != 0)
            {
                PostPlaylistPagePlansErrorIfCurrent(
                    request.RequestId,
                    "PLAYOUT_PRIORITY",
                    "A playout command cancelled the read-only page preflight.",
                    retryable: true,
                    requestCancellation,
                    browserGeneration);
            }
        }
        catch (DatabaseOperationException exception)
        {
            PostPlaylistPagePlansErrorIfCurrent(
                request.RequestId,
                "DATABASE_UNAVAILABLE",
                exception.Message,
                retryable: true,
                requestCancellation,
                browserGeneration);
        }
        catch (LegacySceneDataException exception)
        {
            PostPlaylistPagePlansErrorIfCurrent(
                request.RequestId,
                "SCENE_DATA_INVALID",
                exception.Message,
                retryable: false,
                requestCancellation,
                browserGeneration);
        }
        catch (InvalidOperationException exception)
        {
            PostPlaylistPagePlansErrorIfCurrent(
                request.RequestId,
                "PLAYOUT_NOT_IDLE",
                exception.Message,
                retryable: true,
                requestCancellation,
                browserGeneration);
        }
        catch (Exception exception)
        {
            PostPlaylistPagePlansErrorIfCurrent(
                request.RequestId,
                "PAGE_PREFLIGHT_FAILED",
                exception.Message,
                retryable: false,
                requestCancellation,
                browserGeneration);
        }
        finally
        {
            if (databaseActivityEntered)
            {
                _databaseActivityGate.Release();
            }

            Interlocked.CompareExchange(
                ref _playlistPagePlanCancellation,
                null,
                requestCancellation);
            requestCancellation.Dispose();
        }
    }

    private static bool IsPlaylistPagePlanIdle(
        LegacyPlayoutWorkflow workflow,
        IPlayoutEngine engine)
    {
        var workflowState = workflow.State;
        var engineStatus = engine.Status;
        return string.IsNullOrWhiteSpace(workflowState.PreparedCutCode) &&
            string.IsNullOrWhiteSpace(workflowState.OnAirCutCode) &&
            string.IsNullOrWhiteSpace(engineStatus.PreparedSceneName) &&
            string.IsNullOrWhiteSpace(engineStatus.OnAirSceneName);
    }

    private bool IsCurrentPlaylistPagePlanRequest(
        CancellationTokenSource requestCancellation,
        int browserGeneration) =>
        ReferenceEquals(
            Volatile.Read(ref _playlistPagePlanCancellation),
            requestCancellation) &&
        browserGeneration == Volatile.Read(ref _playlistPagePlanBrowserGeneration) &&
        !_lifetimeCancellation.IsCancellationRequested;

    private void PostPlaylistPagePlansErrorIfCurrent(
        string requestId,
        string code,
        string message,
        bool retryable,
        CancellationTokenSource requestCancellation,
        int browserGeneration)
    {
        if (!IsCurrentPlaylistPagePlanRequest(requestCancellation, browserGeneration))
        {
            return;
        }

        PostPlaylistPagePlansError(requestId, code, message, retryable);
    }

    private void PostPlaylistPagePlansError(
        string requestId,
        string code,
        string message,
        bool retryable)
    {
        PostMessage("playlist-page-plans-error", new
        {
            requestId,
            code,
            message,
            retryable
        });
    }

    private void InvalidatePlaylistPagePlanBrowserRequest()
    {
        Interlocked.Increment(ref _playlistPagePlanBrowserGeneration);
        CancelRequest(Interlocked.Exchange(ref _playlistPagePlanCancellation, null));
    }
}
