using MMoneyCoderSharp.Data;
using MBN_STOCK_WEBVIEW.Core.Playout;
using MBN_STOCK_WEBVIEW.Core.Playout.Scenes;

namespace MBN_STOCK_WEBVIEW.LegacyApplication.Tests;

public sealed class LegacyOperatorPlayoutRequestTests
{
    [Fact]
    public void EmptyPlaylistCannotCreatePlayoutRequest()
    {
        var controller = new LegacyOperatorController(new StaticLookup());

        Assert.Throws<InvalidOperationException>(controller.CreatePlayoutRequest);
    }

    [Fact]
    public async Task RequestPreservesNativePlaylistIdentityEnabledStateAndSelection()
    {
        var controller = new LegacyOperatorController(new StaticLookup());
        await controller.SearchStocksAsync("삼성", LegacySearchTrigger.Button);
        controller.SelectStock(0);
        AddCut(controller, physicalIndex: 0, timestamp: 1_000);
        AddCut(controller, physicalIndex: 1, timestamp: 2_000);
        var rows = controller.Current.Playlist;
        controller.SetPlaylistRowEnabled(rows[0].RowId, enabled: false);
        controller.SelectPlaylistRow(rows[1].RowId, control: false, shift: false);

        var request = controller.CreatePlayoutRequest();

        Assert.Equal(1, request.SelectedIndexZeroBased);
        Assert.Equal(2, request.Playlist.Count);
        Assert.Equal(rows.Select(row => row.RowId),
            request.Playlist.Select(entry => entry.EntryId));
        Assert.False(request.Playlist[0].IsEnabled);
        Assert.True(request.Playlist[1].IsEnabled);
        Assert.All(request.Playlist, entry =>
            Assert.Equal("005930", entry.Selection?.DataCode));
    }

    [Theory]
    [InlineData(false, false, "")]
    [InlineData(true, false, "MA5")]
    [InlineData(false, true, "MA20")]
    [InlineData(true, true, "MA5,MA20")]
    public async Task CandleAddedBeforeToggleSamplesCurrentMovingAveragesAtF8Request(
        bool ma5,
        bool ma20,
        string expectedGraphicType)
    {
        var controller = new LegacyOperatorController(new StaticLookup());
        await controller.SearchStocksAsync("stock", LegacySearchTrigger.Button);
        controller.SelectStock(0);
        var candle = LegacyStockCutCatalog.Rows.First(row =>
            !row.IsSeparator &&
            LegacyStockCutCatalog.ResolveCutAlias(row.RawLabel, isNxt: false) == "8051");
        AddCut(controller, candle.PhysicalIndex, 1_000);
        var rawBeforeToggle = Assert.Single(controller.Current.Playlist);
        var rawGraphicType = rawBeforeToggle.ToPlayoutEntry().Selection!.GraphicType;

        controller.SetMovingAverages(ma5, ma20);
        var entry = Assert.Single(controller.CreatePlayoutRequest().Playlist);

        Assert.Equal(expectedGraphicType, entry.Selection!.GraphicType);
        Assert.Equal(rawGraphicType,
            Assert.Single(controller.Current.Playlist).ToPlayoutEntry().Selection!.GraphicType);
        Assert.Equal(
            new LegacySceneMovingAverageSelection(ma5, ma20),
            entry.MovingAverageSelectionSource!.Current);
    }

    [Fact]
    public async Task MovingAverageOverlayDoesNotAlterNonCandleRequest()
    {
        var controller = new LegacyOperatorController(new StaticLookup());
        await controller.SearchStocksAsync("stock", LegacySearchTrigger.Button);
        controller.SelectStock(0);
        var current = LegacyStockCutCatalog.Rows.First(row =>
            !row.IsSeparator &&
            LegacyStockCutCatalog.ResolveCutAlias(row.RawLabel, isNxt: false) == "5001");
        AddCut(controller, current.PhysicalIndex, 1_000);
        var raw = Assert.Single(controller.Current.Playlist).ToPlayoutEntry();

        controller.SetMovingAverages(ma5: false, ma20: false);
        var requested = Assert.Single(controller.CreatePlayoutRequest().Playlist);

        Assert.Equal(raw.Selection, requested.Selection);
        Assert.Null(requested.MovingAverageSelectionSource);
    }

    [Theory]
    [InlineData(0)]
    [InlineData(6)]
    [InlineData(19)]
    public async Task RequestAppliesCurrentGlobalDissolveIndexToEveryExistingRow(
        int globalFadeDuration)
    {
        var controller = await CreateControllerWithCutsAsync(3);
        Assert.All(controller.Current.Playlist, row => Assert.Equal(6, row.FadeDuration));

        var request = controller.CreatePlayoutRequest(globalFadeDuration);

        Assert.All(request.Playlist, entry =>
            Assert.Equal(globalFadeDuration, entry.FadeDuration));
        Assert.All(controller.Current.Playlist, row => Assert.Equal(6, row.FadeDuration));
    }

    [Theory]
    [InlineData(-1)]
    [InlineData(20)]
    [InlineData(60)]
    public async Task RequestRejectsGlobalDissolveIndexOutsideOriginalComboRange(
        int globalFadeDuration)
    {
        var controller = await CreateControllerWithCutsAsync(1);

        Assert.Throws<ArgumentOutOfRangeException>(() =>
            controller.CreatePlayoutRequest(globalFadeDuration));
    }

    [Theory]
    [InlineData("5단 표그래프", ScenePageSize.Five)]
    [InlineData("6종목 현재가", ScenePageSize.Six)]
    [InlineData("12종목 현재가", ScenePageSize.Twelve)]
    public void ExactLegacyGraphicTypeFreezesTheOperatorPageSuffix(
        string graphicType,
        ScenePageSize expectedPageSize)
    {
        var entry = OperatorRow(graphicType, "2/3").ToPlayoutEntry();

        Assert.Equal(expectedPageSize, entry.PageNavigation?.PageSize);
        Assert.Equal(3, entry.PageNavigation?.FrozenPageCount);
        Assert.True(entry.PageNavigation?.IsEnabled);
    }

    [Theory]
    [InlineData("5단 표그래프 ")]
    [InlineData("5단표그래프")]
    [InlineData("five-row")]
    public void NonCanonicalGraphicTypeExplicitlyDisablesPageNext(string graphicType)
    {
        var entry = OperatorRow(graphicType, "1/9").ToPlayoutEntry();

        Assert.NotNull(entry.PageNavigation);
        Assert.False(entry.PageNavigation.IsEnabled);
        Assert.Null(entry.PageNavigation.PageSize);
        Assert.Null(entry.PageNavigation.FrozenPageCount);
    }

    [Theory]
    [InlineData("1/0")]
    [InlineData("1/21")]
    [InlineData("2/1")]
    [InlineData("1 /2")]
    public void CanonicalPagedGraphicDefersHistoricalInvalidPageText(string pageText)
    {
        var entry = OperatorRow("5단 표그래프", pageText).ToPlayoutEntry();

        Assert.Null(entry.PageNavigation);
    }

    [Fact]
    public async Task NextProgressUpdatesProgramPageWithoutReplacingOperatorSelection()
    {
        var controller = await CreateControllerWithCutsAsync(3);
        var rows = controller.Current.Playlist;
        controller.SelectPlaylistRow(rows[2].RowId, control: false, shift: false);

        var state = controller.ReflectSuccessfulPlayout(
            rows[0].RowId,
            pageIndexZeroBased: 1,
            pageCount: 3,
            synchronizeOperatorSelection: false);

        Assert.Equal("2/3", state.Playlist[0].PageText);
        Assert.True(state.Playlist[2].IsSelected);
        Assert.False(state.Playlist[0].IsSelected);
        Assert.Equal(2, controller.CreatePlayoutRequest().SelectedIndexZeroBased);
    }

    [Fact]
    public async Task TakeSkipMovesOnlyTheF8CandidateAndPreservesDeleteSelection()
    {
        var controller = await CreateControllerWithCutsAsync(2);
        var rows = controller.Current.Playlist;
        controller.SelectPlaylistRow(rows[0].RowId, control: false, shift: false);
        controller.ReflectSuccessfulPlayout(
            rows[0].RowId,
            pageIndexZeroBased: 2,
            pageCount: 3,
            synchronizeOperatorSelection: false);

        var state = controller.ReflectSuccessfulPlayout(
            rows[1].RowId,
            pageIndexZeroBased: 0,
            pageCount: 2,
            synchronizeOperatorSelection: true);

        Assert.Equal("1/2", state.Playlist[1].PageText);
        Assert.True(state.Playlist[0].IsSelected);
        Assert.False(state.Playlist[1].IsSelected);
        Assert.True(state.Playlist[1].IsActive);
        Assert.Equal(1, controller.CreatePlayoutRequest().SelectedIndexZeroBased);
    }

    [Fact]
    public async Task SuccessfulF8DoesNotReplaceTheRowsAccumulatedForDelete()
    {
        var controller = await CreateControllerWithCutsAsync(3);
        var rows = controller.Current.Playlist;
        controller.SelectPlaylistRow(rows[0].RowId, control: false, shift: false);
        controller.SelectPlaylistRow(rows[2].RowId, control: true, shift: false);

        var reflected = controller.ReflectSuccessfulPlayout(
            rows[2].RowId,
            pageIndexZeroBased: 0,
            pageCount: 1,
            synchronizeOperatorSelection: true);

        Assert.Equal(
            [rows[0].RowId, rows[2].RowId],
            reflected.Playlist.Where(row => row.IsSelected).Select(row => row.RowId));
        Assert.True(reflected.Playlist[2].IsActive);

        var deleted = controller.DeleteSelectedPlaylistRows();
        Assert.Equal(rows[1].RowId, Assert.Single(deleted.Playlist).RowId);
    }

    private static async Task<LegacyOperatorController> CreateControllerWithCutsAsync(int count)
    {
        var controller = new LegacyOperatorController(new StaticLookup());
        await controller.SearchStocksAsync("?쇱꽦", LegacySearchTrigger.Button);
        controller.SelectStock(0);
        for (var index = 0; index < count; index++)
        {
            AddCut(controller, index, 1_000 + (index * 1_000));
        }

        return controller;
    }

    private static LegacyOperatorPlaylistRow OperatorRow(
        string graphicType,
        string pageText) =>
        new(
            "row-1",
            true,
            "테마",
            "반도체",
            graphicType,
            "현재가",
            pageText,
            "00123",
            "paged-cut",
            6,
            SceneAlias: "5074",
            PlayoutSelection: new(
                "테마",
                "반도체",
                graphicType,
                "현재가",
                "00123"));

    private static void AddCut(
        LegacyOperatorController controller,
        int physicalIndex,
        long timestamp)
    {
        controller.CutPointerDown(
            physicalIndex,
            control: false,
            shift: false,
            x: 1,
            y: physicalIndex * 23,
            timestampMilliseconds: timestamp);
        controller.DropSelectedCutsOnPlaylist();
    }

    private sealed class StaticLookup : ILegacyStockLookup
    {
        public Task<LegacyStockSearchData> SearchAsync(
            string query,
            CancellationToken cancellationToken = default) =>
            Task.FromResult(new LegacyStockSearchData(
                query,
                ["삼성전자"],
                [new LegacyStockIdentity(
                    LegacyDomesticStockMarket.Kospi,
                    "삼성전자",
                    "005930")]));
    }
}
