using MBN_STOCK_WEBVIEW.LegacyApplication;
using MMoneyCoderSharp.Data;

namespace MBN_STOCK_WEBVIEW.LegacyApplication.Tests;

public sealed class LegacyOverseasOperatorControllerTests
{
    private static readonly DateTimeOffset RetrievedAt =
        new(2026, 7, 15, 10, 0, 0, TimeSpan.FromHours(9));

    [Fact]
    public void OverseasSnapshotAndFixedIndexActionRemainControllerAuthoritative()
    {
        var controller = CreateController();

        var selectedTab = controller.SelectTab(LegacyOperatorTabId.OverseasStocks);

        Assert.NotNull(selectedTab.Overseas);
        Assert.Equal(10, selectedTab.Overseas.FixedIndexTargets.Count);
        Assert.Equal(12, selectedTab.Overseas.Actions.Count);
        Assert.Equal(new LegacyMovingAverageSelection(true, true), selectedTab.MovingAverages);
        Assert.Equal(
            new LegacyOverseasMovingAverageSelection(true, true),
            selectedTab.Overseas.MovingAverages);

        controller.SelectOverseasFixedIndex("nasdaq");
        controller.SetMovingAverages(ma5: false, ma20: true);
        var state = controller.ActivateOverseasAction("index-candle-20d");
        var row = Assert.Single(state.Playlist);
        var entry = row.ToPlayoutEntry();
        var selection = Assert.IsType<MBN_STOCK_WEBVIEW.Core.Playout.Scenes.LegacySceneSelection>(
            entry.Selection);

        Assert.Equal("legacy-overseas-00000001", row.RowId);
        Assert.Equal("해외지수", row.MarketText);
        Assert.Equal("나스닥", row.StockName);
        Assert.Equal("캔들그래프", row.GraphicType);
        Assert.Equal("20일", row.Subtype);
        Assert.Equal("1/1", row.PageText);
        Assert.Equal("8040", entry.CutCode);
        Assert.Equal("FOREIGN_INDEX", selection.GroupCode);
        Assert.Equal("나스닥", selection.Subject);
        Assert.Equal("NAS@IXIC|US|0", selection.DataCode);
        Assert.Equal(
            "MA20",
            Assert.Single(controller.CreatePlayoutRequest().Playlist).Selection!.GraphicType);
    }

    [Fact]
    public async Task OverseasIndustryAndStockSearchSelectionsMaterializeExactPlaylistRows()
    {
        var controller = CreateController(
            industries: [new("반도체", "US Semiconductor", "NAS@SOX")],
            stocks: [new("Apple", "NAS@AAPL", "US")]);
        controller.SelectTab(LegacyOperatorTabId.OverseasStocks);

        var industries = await controller.SearchOverseasIndustriesAsync("반도체");
        var industryId = Assert.Single(industries.Overseas!.Industry.Results).SelectionId;
        controller.SelectOverseasIndustry(industryId);
        var industryState = controller.ActivateOverseasAction("industry-current");

        var industryRow = Assert.Single(industryState.Playlist);
        Assert.Equal("5001", industryRow.ToPlayoutEntry().CutCode);
        Assert.Equal("FOREIGN_INDUSTRY", industryRow.PlayoutSelection!.GroupCode);
        Assert.Equal("반도체", industryRow.PlayoutSelection.Subject);
        Assert.Equal("1열판기본", industryRow.PlayoutSelection.GraphicType);
        Assert.Equal("CURRENT", industryRow.PlayoutSelection.Subtype);
        Assert.Equal("US Semiconductor|NAS@SOX|US|0", industryRow.PlayoutSelection.DataCode);

        var stocks = await controller.SearchOverseasStocksAsync("apple");
        var stockId = Assert.Single(stocks.Overseas!.Stock.Results).SelectionId;
        controller.SelectOverseasStock(stockId);
        var stockState = controller.ActivateOverseasAction("stock-candle-5d");

        Assert.Equal(2, stockState.Playlist.Count);
        var stockRow = stockState.Playlist[1];
        Assert.Equal("8061", stockRow.ToPlayoutEntry().CutCode);
        Assert.Equal("캔들그래프", stockRow.GraphicType);
        Assert.Equal("FOREIGN_STOCK", stockRow.PlayoutSelection!.GroupCode);
        Assert.Equal("Apple", stockRow.PlayoutSelection.Subject);
        Assert.Equal(string.Empty, stockRow.PlayoutSelection.GraphicType);
        Assert.Equal("PRICE", stockRow.PlayoutSelection.Subtype);
        Assert.Equal("NAS@AAPL|US|1", stockRow.PlayoutSelection.DataCode);
        Assert.Equal(
            "MA5,MA20",
            controller.CreatePlayoutRequest().Playlist[1].Selection!.GraphicType);
    }

    [Fact]
    public async Task DuplicateIndustryDisplayNamesKeepChosenTupleInRuntimePlaylist()
    {
        var controller = CreateController(industries:
        [
            new("Shared", "First Input", "NAS@ONE"),
            new("Shared", "Second Input", "NAS@TWO")
        ]);
        controller.SelectTab(LegacyOperatorTabId.OverseasStocks);
        var searched = await controller.SearchOverseasIndustriesAsync(string.Empty);
        controller.SelectOverseasIndustry(searched.Overseas!.Industry.Results[1].SelectionId);

        controller.ActivateOverseasAction("industry-current");

        var runtimeSelection = Assert.Single(
            controller.CreatePlayoutRequest().Playlist).Selection!;
        Assert.Equal("FOREIGN_INDUSTRY", runtimeSelection.GroupCode);
        Assert.Equal("Shared", runtimeSelection.Subject);
        Assert.Equal("Second Input|NAS@TWO|US|0", runtimeSelection.DataCode);
    }

    [Fact]
    public async Task NonUsStockKeepsOneExactIdentityThroughBothActualUc5SceneAliases()
    {
        var controller = CreateController(
            stocks: [new("London Stock", "LNS@LON", "GB")]);
        controller.SelectTab(LegacyOperatorTabId.OverseasStocks);

        // UC5's first inserted result is immediately the active RowMode row.
        var searched = await controller.SearchOverseasStocksAsync("london");
        Assert.Equal("London Stock", searched.Overseas!.Stock.Selected?.InputName);

        var oneColumn = controller.ActivateOverseasAction("stock-current");
        var candle = controller.ActivateOverseasAction("stock-candle-5d");
        var rows = candle.Playlist;

        Assert.Equal("5001", rows[0].ToPlayoutEntry().CutCode);
        Assert.Equal("8061", rows[1].ToPlayoutEntry().CutCode);
        Assert.All(rows, row =>
        {
            Assert.Equal("FOREIGN_STOCK", row.PlayoutSelection?.GroupCode);
            Assert.Equal("London Stock", row.PlayoutSelection?.Subject);
            Assert.Equal("LNS@LON|GB|1", row.PlayoutSelection?.DataCode);
        });
        Assert.Single(oneColumn.Playlist);
    }

    [Fact]
    public async Task OverseasActionsRejectInactiveTabAndStaleSearchIds()
    {
        var controller = CreateController(
            industries: [new("반도체", "US Semiconductor", "NAS@SOX")]);

        var inactive = controller.SelectOverseasFixedIndex("nasdaq");
        Assert.Equal(LegacyOperatorStatusKind.Error, inactive.StatusKind);
        Assert.Null(inactive.Overseas);

        controller.SelectTab(LegacyOperatorTabId.OverseasStocks);
        var first = await controller.SearchOverseasIndustriesAsync("");
        var staleId = Assert.Single(first.Overseas!.Industry.Results).SelectionId;
        await controller.SearchOverseasIndustriesAsync("new generation");

        var stale = controller.SelectOverseasIndustry(staleId);
        Assert.Equal(LegacyOperatorStatusKind.Error, stale.StatusKind);
        Assert.Equal("반도체", stale.Overseas!.Industry.Selected?.KoreanName);
    }

    private static LegacyOperatorController CreateController(
        IReadOnlyList<OverseasIndustryIndexItem>? industries = null,
        IReadOnlyList<WorldStockSearchItem>? stocks = null)
    {
        var workflow = new LegacyOverseasSelectionWorkflow(
            new StaticIndustryService(industries ?? []),
            new StaticStockService(stocks ?? []));
        return new LegacyOperatorController(
            new EmptyStockLookup(),
            overseasWorkflow: workflow);
    }

    private sealed class EmptyStockLookup : ILegacyStockLookup
    {
        public Task<LegacyStockSearchData> SearchAsync(
            string query,
            CancellationToken cancellationToken = default)
        {
            cancellationToken.ThrowIfCancellationRequested();
            return Task.FromResult(new LegacyStockSearchData(query, [], []));
        }
    }

    private sealed class StaticIndustryService(
        IReadOnlyList<OverseasIndustryIndexItem> items) : IOverseasIndustryIndexSearchService
    {
        public Task<OverseasIndustryIndexSearchResult> SearchAsync(
            string query,
            int maximumResults = LegacyOverseasIndustryIndexSearchService.DefaultMaximumResults,
            CancellationToken cancellationToken = default)
        {
            cancellationToken.ThrowIfCancellationRequested();
            return Task.FromResult(new OverseasIndustryIndexSearchResult(
                query.Trim(),
                RetrievedAt,
                items,
                IsTruncated: false));
        }
    }

    private sealed class StaticStockService(
        IReadOnlyList<WorldStockSearchItem> items) : IOverseasStockSearchService
    {
        public Task<WorldStockSearchResult> SearchAsync(
            string query,
            int maximumResults = LegacyOverseasStockSearchService.DefaultMaximumResults,
            CancellationToken cancellationToken = default)
        {
            cancellationToken.ThrowIfCancellationRequested();
            return Task.FromResult(new WorldStockSearchResult(
                query.Trim(),
                RetrievedAt,
                items,
                IsTruncated: false));
        }
    }
}
