using MMoneyCoderSharp.Data;

namespace MBN_STOCK_WEBVIEW.LegacyApplication.Tests;

public sealed class LegacyTradingHaltWorkflowTests
{
    private static readonly TradingHaltSelection KospiAlpha =
        new(TradingHaltMarket.Kospi, "005930", "가온전자");

    private static readonly TradingHaltSelection KospiBeta =
        new(TradingHaltMarket.Kospi, "000660", "한빛반도체");

    private static readonly TradingHaltSelection KosdaqAlpha =
        new(TradingHaltMarket.Kosdaq, "035720", "다온테크");

    [Fact]
    public async Task Search_AutomaticallyActivatesFirstFarPointRowAndActions()
    {
        var service = new DelegateTradingHaltService
        {
            Search = (query, _, _) => Task.FromResult(
                SearchResult(query, KospiAlpha, KospiBeta, KosdaqAlpha))
        };
        var controller = new LegacyTradingHaltWorkflowController(service);

        var searched = await controller.SearchAsync(string.Empty);
        var current = controller.MaterializeSelectedAction(
            LegacyTradingHaltWorkflowController.CurrentActionId);

        Assert.Equal(searched.Results[0].SelectionId, searched.Selected?.SelectionId);
        Assert.True(searched.Results[0].IsSelected);
        Assert.All(searched.Actions, action => Assert.True(action.IsAvailable));
        Assert.Equal("005930", current.StockCode);
        Assert.Equal("가온전자", current.DisplayName);
    }

    [Fact]
    public async Task Search_AcceptsConservativeTruncationAfterInitialFiltering()
    {
        var service = new DelegateTradingHaltService
        {
            Search = (query, _, _) => Task.FromResult(
                SearchResult(query, KospiAlpha) with { IsTruncated = true })
        };
        var controller = new LegacyTradingHaltWorkflowController(service);

        var searched = await controller.SearchAsync("ㄱㅈ");

        Assert.Equal(KospiAlpha.DisplayName, Assert.Single(searched.Results).DisplayName);
        Assert.True(searched.IsTruncated);
    }

    [Fact]
    public async Task Search_StillRejectsImpossiblePartialTruncationForOrdinaryText()
    {
        var service = new DelegateTradingHaltService
        {
            Search = (query, _, _) => Task.FromResult(
                SearchResult(query, KospiAlpha) with { IsTruncated = true })
        };
        var controller = new LegacyTradingHaltWorkflowController(service);

        await Assert.ThrowsAsync<TradingHaltSelectionDataException>(
            () => controller.SearchAsync("Alpha"));
    }

    [Fact]
    public async Task SearchSelect_MaterializesClosedCurrentAndCandleDraftsFromActionIdOnly()
    {
        var service = new DelegateTradingHaltService
        {
            Search = (query, _, _) => Task.FromResult(
                SearchResult(query, KospiAlpha, KospiBeta, KosdaqAlpha))
        };
        var controller = new LegacyTradingHaltWorkflowController(service);

        var search = await controller.SearchAsync("  거래  ");
        var selected = controller.SelectResult(search.Results[2].SelectionId);
        controller.SetMovingAverages(ma5: true, ma20: true);
        var current = controller.MaterializeSelectedAction(
            LegacyTradingHaltWorkflowController.CurrentActionId);
        var candle = controller.MaterializeSelectedAction(
            LegacyTradingHaltWorkflowController.CandleActionId);

        Assert.Equal("거래", service.SearchQuery);
        Assert.Equal(500, service.SearchMaximum);
        Assert.Equal("다온테크", selected.Selected?.DisplayName);
        Assert.Collection(
            selected.Actions,
            action =>
            {
                Assert.Equal("current", action.ActionId);
                Assert.True(action.IsAvailable);
            },
            action =>
            {
                Assert.Equal("candle-120d", action.ActionId);
                Assert.True(action.IsAvailable);
            });

        Assert.Equal("s5001", current.BuilderKey);
        Assert.Equal("5001", current.CutCode);
        Assert.Equal(new[] { "5001" }, current.Aliases);
        Assert.Equal("거래정지-현재가", current.Detail);
        Assert.Equal("stock", current.Category);
        Assert.Equal("kosdaq", current.Market);
        Assert.Equal("KOSDAQ", current.Selection.GroupCode);
        Assert.Equal("다온테크", current.Selection.Subject);
        Assert.Equal("1열판기본", current.Selection.GraphicType);
        Assert.Equal("HALTED", current.Selection.Subtype);
        Assert.Equal("035720", current.Selection.DataCode);
        Assert.False(current.Ma5);
        Assert.False(current.Ma20);
        Assert.Null(current.CandlePeriodDays);

        Assert.Equal("s8010", candle.BuilderKey);
        Assert.Equal("8051", candle.CutCode);
        Assert.Equal(new[] { "8051" }, candle.Aliases);
        Assert.Equal("캔들그래프-거래정지 · 120일", candle.Detail);
        Assert.Equal("chart", candle.Category);
        Assert.Equal("kosdaq", candle.Market);
        Assert.Equal("KOSDAQ_STOCK", candle.Selection.GroupCode);
        Assert.Equal("다온테크", candle.Selection.Subject);
        Assert.Equal("MA5,MA20", candle.Selection.GraphicType);
        Assert.Equal("TRADING_HALT", candle.Selection.Subtype);
        Assert.Equal("035720", candle.Selection.DataCode);
        Assert.True(candle.Ma5);
        Assert.True(candle.Ma20);
        Assert.Equal(120, candle.CandlePeriodDays);
        Assert.Equal("1/1", candle.PageText);
        Assert.True(candle.IsEnabled);
        Assert.Equal(6, candle.FadeDuration);
        Assert.Equal("legacy-trading-halt-workflow", candle.Source);
        Assert.Equal(candle.DraftId, candle.ToLegacyPlaylistEntry().EntryId);

        var method = typeof(LegacyTradingHaltWorkflowController).GetMethod(
            nameof(LegacyTradingHaltWorkflowController.MaterializeSelectedAction));
        Assert.NotNull(method);
        Assert.Collection(
            method.GetParameters(),
            parameter => Assert.Equal("actionId", parameter.Name));
        Assert.Throws<ArgumentOutOfRangeException>(
            () => controller.MaterializeSelectedAction("8051-caller-override"));
    }

    [Fact]
    public async Task NameSort_FirstClickIsDescendingThenAscending_AndKeepsSelection()
    {
        var service = new DelegateTradingHaltService
        {
            Search = (query, _, _) => Task.FromResult(
                SearchResult(query, KospiAlpha, KospiBeta, KosdaqAlpha))
        };
        var controller = new LegacyTradingHaltWorkflowController(service);
        var source = await controller.SearchAsync(string.Empty);
        controller.SelectResult(source.Results[0].SelectionId);

        var descending = controller.ToggleNameSort();
        var ascending = controller.ToggleNameSort();

        Assert.Equal(
            new[] { "가온전자", "한빛반도체", "다온테크" },
            source.Results.Select(row => row.DisplayName));
        Assert.Equal(LegacyTradingHaltNameSort.Descending, descending.NameSort);
        Assert.Equal(
            new[] { "한빛반도체", "다온테크", "가온전자" },
            descending.Results.Select(row => row.DisplayName));
        Assert.Equal(LegacyTradingHaltNameSort.Ascending, ascending.NameSort);
        Assert.Equal(
            new[] { "가온전자", "다온테크", "한빛반도체" },
            ascending.Results.Select(row => row.DisplayName));
        Assert.Equal("005930", ascending.Selected?.StockCode);
        Assert.True(Assert.Single(ascending.Results, row => row.IsSelected).IsSelected);
    }

    [Fact]
    public async Task LateSearchResponse_CannotReplaceNewerTradingHaltResult()
    {
        var first = new TaskCompletionSource<TradingHaltSelectionResult>(
            TaskCreationOptions.RunContinuationsAsynchronously);
        var second = new TaskCompletionSource<TradingHaltSelectionResult>(
            TaskCreationOptions.RunContinuationsAsynchronously);
        var service = new DelegateTradingHaltService
        {
            Search = (query, _, _) => query == "첫째" ? first.Task : second.Task
        };
        var controller = new LegacyTradingHaltWorkflowController(service);

        var firstRequest = controller.SearchAsync("첫째");
        var secondRequest = controller.SearchAsync("둘째");
        second.SetResult(SearchResult("둘째", KosdaqAlpha));
        var newest = await secondRequest;
        first.SetResult(SearchResult("첫째", KospiAlpha));
        var staleCompletion = await firstRequest;

        Assert.Equal("둘째", newest.Query);
        Assert.Equal("다온테크", Assert.Single(newest.Results).DisplayName);
        Assert.Equal("둘째", staleCompletion.Query);
        Assert.Equal("다온테크", Assert.Single(staleCompletion.Results).DisplayName);
        Assert.Equal("둘째", controller.Current.Query);
    }

    [Fact]
    public async Task InvalidIdentityOrSourceOrder_IsRejectedAndLeavesErrorSnapshot()
    {
        var duplicateName = new TradingHaltSelection(
            TradingHaltMarket.Kospi,
            "005931",
            KospiAlpha.DisplayName);
        var service = new DelegateTradingHaltService
        {
            Search = (query, _, _) => Task.FromResult(
                SearchResult(query, KospiAlpha, duplicateName))
        };
        var controller = new LegacyTradingHaltWorkflowController(service);

        await Assert.ThrowsAsync<TradingHaltSelectionDataException>(
            () => controller.SearchAsync(string.Empty));

        Assert.Equal(LegacyTradingHaltRequestStatus.Error, controller.Current.Status);
        Assert.Empty(controller.Current.Results);
        Assert.Equal(LegacyOperatorStatusKind.Error, controller.Current.StatusKind);
    }

    [Fact]
    public async Task TradingHaltSnapshots_AreImmutableAcrossLaterQueries()
    {
        var service = new DelegateTradingHaltService
        {
            Search = (query, _, _) => Task.FromResult(
                query == "A"
                    ? SearchResult(query, KospiAlpha)
                    : SearchResult(query, KosdaqAlpha))
        };
        var controller = new LegacyTradingHaltWorkflowController(service);
        var before = await controller.SearchAsync("A");

        var after = await controller.SearchAsync("B");

        Assert.Equal("가온전자", Assert.Single(before.Results).DisplayName);
        Assert.Equal("다온테크", Assert.Single(after.Results).DisplayName);
        var rows = Assert.IsAssignableFrom<ICollection<LegacyTradingHaltResultView>>(
            before.Results);
        Assert.Throws<NotSupportedException>(() => rows.Clear());
        var actions = Assert.IsAssignableFrom<ICollection<LegacyTradingHaltActionView>>(
            before.Actions);
        Assert.Throws<NotSupportedException>(() => actions.Add(before.Actions[0]));
    }

    private static TradingHaltSelectionResult SearchResult(
        string query,
        params TradingHaltSelection[] selections) =>
        new(
            query,
            DateTimeOffset.Parse("2026-07-15T00:00:00+09:00"),
            selections,
            false);

    private sealed class DelegateTradingHaltService : ITradingHaltSelectionService
    {
        public Func<string, int, CancellationToken, Task<TradingHaltSelectionResult>> Search
        {
            get;
            init;
        } = (query, _, _) => Task.FromResult(SearchResult(query));

        public string? SearchQuery { get; private set; }

        public int SearchMaximum { get; private set; }

        public Task<TradingHaltSelectionResult> SearchAsync(
            string query = "",
            int maximumResults = LegacyTradingHaltSelectionService.DefaultMaximumResults,
            CancellationToken cancellationToken = default)
        {
            SearchQuery = query;
            SearchMaximum = maximumResults;
            return Search(query, maximumResults, cancellationToken);
        }
    }
}
