using MMoneyCoderSharp.Data;

namespace MBN_STOCK_WEBVIEW.LegacyApplication.Tests;

public sealed class LegacyTabActivationParityTests
{
    [Fact]
    public async Task ThemeEachActivation_LoadsOriginalEmptyQueryOncePerActivation()
    {
        // Original evidence: Control/UC4.cs UC4_Load starts timer1 and its first
        // timer1_Tick calls GetThemeByKeyword with the initially empty txts value.
        var service = new CountingThemeService();
        var controller = new LegacyOperatorController(
            Lookup(),
            themeWorkflow: new LegacyThemeWorkflow(service));

        var first = await controller.SelectTabAsync(LegacyOperatorTabId.Theme);
        Assert.Equal(string.Empty, service.Queries.Single());
        var firstResultId = Assert.Single(first.Theme!.SearchResults).ResultId;
        controller.SetThemeSort("GAIN_DESC");
        var selected = await controller.SelectThemeAsync(firstResultId);
        Assert.Equal(LegacyThemeSort.GainRateDescending, selected.Theme!.Sort);
        Assert.NotNull(selected.Theme.SelectedTheme);

        await controller.SelectTabAsync(LegacyOperatorTabId.Theme);
        Assert.Single(service.Queries);

        await controller.SelectTabAsync(LegacyOperatorTabId.Index);
        var second = await controller.SelectTabAsync(LegacyOperatorTabId.Theme);

        Assert.Collection(
            service.Queries,
            query => Assert.Equal(string.Empty, query),
            query => Assert.Equal(string.Empty, query));
        Assert.Single(second.Theme!.SearchResults);
        Assert.Equal(LegacyThemeSort.InputOrder, second.Theme.Sort);
        Assert.Null(second.Theme.SelectedResultId);
        Assert.Null(second.Theme.SelectedTheme);
        Assert.Empty(second.Theme.PreviewItems);
    }

    [Fact]
    public async Task ExpertEachActivation_LoadsOriginalFullListOncePerActivation()
    {
        // Original evidence: Control/UC6.cs UC6_Load calls Data_Load, whose query
        // reads the complete EXPERT_LIST without a filter.
        var service = new CountingExpertService();
        var controller = new LegacyOperatorController(
            Lookup(),
            expertWorkflow: new LegacyExpertWorkflowController(service));

        var first = await controller.SelectTabAsync(LegacyOperatorTabId.Expert);
        Assert.Equal(string.Empty, service.Queries.Single());
        Assert.Single(first.Expert!.Search.Results);

        await controller.SelectTabAsync(LegacyOperatorTabId.Expert);
        Assert.Single(service.Queries);

        await controller.SelectTabAsync(LegacyOperatorTabId.Index);
        var second = await controller.SelectTabAsync(LegacyOperatorTabId.Expert);

        Assert.Collection(
            service.Queries,
            query => Assert.Equal(string.Empty, query),
            query => Assert.Equal(string.Empty, query));
        Assert.Single(second.Expert!.Search.Results);
    }

    [Fact]
    public async Task DragSwapThatActivatesTheme_UsesTheSameNativeInitialLoad()
    {
        var service = new CountingThemeService();
        var controller = new LegacyOperatorController(
            Lookup(),
            themeWorkflow: new LegacyThemeWorkflow(service));

        var state = await controller.SwapTabsAsync(
            LegacyOperatorTabId.Theme,
            LegacyOperatorTabId.Exchange);

        Assert.Equal(string.Empty, service.Queries.Single());
        Assert.Single(state.Theme!.SearchResults);
        Assert.Equal(
            LegacyOperatorTabId.Theme,
            Assert.Single(state.Tabs!, tab => tab.IsActive).Id);
    }

    [Fact]
    public async Task SupersededThemeActivation_DoesNotCommitAndRetriesWhenNeeded()
    {
        var service = new DelayedThemeService();
        var controller = new LegacyOperatorController(
            Lookup(),
            themeWorkflow: new LegacyThemeWorkflow(service));

        var firstActivation = controller.SelectTabAsync(LegacyOperatorTabId.Theme);
        await service.FirstRequestStarted.Task.WaitAsync(TimeSpan.FromSeconds(5));

        controller.SelectTab(LegacyOperatorTabId.Index);
        service.ReleaseFirstRequest.TrySetResult();
        var superseded = await firstActivation;

        Assert.Equal(
            LegacyOperatorTabId.Index,
            Assert.Single(superseded.Tabs!, tab => tab.IsActive).Id);

        var retried = await controller.SelectTabAsync(LegacyOperatorTabId.Theme);
        Assert.Equal(2, service.CallCount);
        Assert.Single(retried.Theme!.SearchResults);
    }

    [Theory]
    [InlineData(LegacyOperatorTabId.KospiIndustry, IndustryMarket.Kospi)]
    [InlineData(LegacyOperatorTabId.KosdaqIndustry, IndustryMarket.Kosdaq)]
    public async Task IndustryEachActivation_ReloadsAndClearsUc2Selections(
        LegacyOperatorTabId tabId,
        IndustryMarket market)
    {
        var service = new CountingIndustryService();
        var controller = new LegacyOperatorController(
            Lookup(),
            industryWorkflow: new LegacyIndustrySelectionWorkflow(service));

        await controller.SelectTabAsync(tabId);
        controller.SelectIndustry(0, doubleClick: true);
        var selected = controller.SelectIndustry(1, doubleClick: true);
        Assert.NotNull(selected.Industry!.SelectedIndustry);
        Assert.NotNull(selected.Industry.FirstIndustry);
        Assert.NotNull(selected.Industry.SecondIndustry);

        await controller.SelectTabAsync(tabId);
        Assert.Single(service.Markets);

        await controller.SelectTabAsync(LegacyOperatorTabId.Index);
        var reentered = await controller.SelectTabAsync(tabId);

        Assert.Equal([market, market], service.Markets);
        Assert.Equal(2, reentered.Industry!.Industries.Count);
        Assert.Null(reentered.Industry.SelectedIndex);
        Assert.Null(reentered.Industry.SelectedIndustry);
        Assert.Null(reentered.Industry.FirstIndustry);
        Assert.Null(reentered.Industry.SecondIndustry);
    }

    [Fact]
    public async Task ComparisonEachActivation_ReloadsPairsAndClearsUc3DraftAndSearch()
    {
        var store = new CountingComparisonPairStore();
        var controller = new LegacyOperatorController(
            Lookup(),
            comparisonWorkflow: new LegacyComparisonWorkflowController(
                new StaticComparisonDataService(),
                store));

        await controller.SelectTabAsync(LegacyOperatorTabId.Comparison);
        var searched = await controller.SearchComparisonDomesticAsync("semiconductor");
        var results = searched.Comparison!.DomesticSearch.Results;
        controller.ChooseComparisonDomesticResult(results[0].SelectionId);
        var drafted = controller.ChooseComparisonDomesticResult(results[1].SelectionId);
        Assert.NotNull(drafted.Comparison!.FirstTarget);
        Assert.NotNull(drafted.Comparison.SecondTarget);

        await controller.SelectTabAsync(LegacyOperatorTabId.Comparison);
        Assert.Equal(1, store.ReadCount);

        await controller.SelectTabAsync(LegacyOperatorTabId.Index);
        var reentered = await controller.SelectTabAsync(LegacyOperatorTabId.Comparison);

        Assert.Equal(2, store.ReadCount);
        Assert.Equal(LegacyComparisonSearchStatus.Idle, reentered.Comparison!.DomesticSearch.Status);
        Assert.Empty(reentered.Comparison.DomesticSearch.Results);
        Assert.Equal(string.Empty, reentered.Comparison.DomesticSearch.Query);
        Assert.Null(reentered.Comparison.FirstTarget);
        Assert.Null(reentered.Comparison.SecondTarget);
        Assert.Empty(reentered.Comparison.SavedPairs);
    }

    [Fact]
    public async Task ComparisonReentry_InvalidatesPairLoadFromDiscardedUc3()
    {
        var store = new DelayedComparisonPairStore();
        var controller = new LegacyOperatorController(
            Lookup(),
            comparisonWorkflow: new LegacyComparisonWorkflowController(
                new StaticComparisonDataService(),
                store));

        var firstActivation = controller.SelectTabAsync(LegacyOperatorTabId.Comparison);
        await store.FirstReadStarted.Task.WaitAsync(TimeSpan.FromSeconds(5));
        controller.SelectTab(LegacyOperatorTabId.Index);
        controller.SelectTab(LegacyOperatorTabId.Comparison);
        store.ReleaseFirstRead.TrySetResult();
        await firstActivation;

        Assert.Equal(LegacyOperatorStatusKind.Neutral, controller.Current.Comparison!.StatusKind);
        Assert.Equal(string.Empty, controller.Current.Comparison.StatusMessage);
        Assert.Empty(controller.Current.Comparison.SavedPairs);

        var reloaded = await controller.SelectTabAsync(LegacyOperatorTabId.Comparison);
        Assert.Equal(2, store.ReadCount);
        Assert.Equal(LegacyOperatorStatusKind.Information, reloaded.Comparison!.StatusKind);
    }

    [Fact]
    public async Task OverseasStocksReentry_ResetsUc5StateAndKeepsMainMovingAverages()
    {
        var workflow = new LegacyOverseasSelectionWorkflow(
            new StaticOverseasIndustryService(),
            new StaticOverseasStockService());
        var controller = new LegacyOperatorController(Lookup(), overseasWorkflow: workflow);
        controller.SelectTab(LegacyOperatorTabId.OverseasStocks);
        controller.SelectOverseasFixedIndex("nasdaq");
        var industries = await controller.SearchOverseasIndustriesAsync("industry");
        controller.SelectOverseasIndustry(
            Assert.Single(industries.Overseas!.Industry.Results).SelectionId);
        var stocks = await controller.SearchOverseasStocksAsync("stock");
        controller.SelectOverseasStock(
            Assert.Single(stocks.Overseas!.Stock.Results).SelectionId);
        controller.SetMovingAverages(ma5: false, ma20: true);

        controller.SelectTab(LegacyOperatorTabId.Index);
        var reentered = controller.SelectTab(LegacyOperatorTabId.OverseasStocks);

        Assert.Equal("dow-jones", reentered.Overseas!.SelectedFixedIndex?.TargetId);
        Assert.Equal(LegacyOverseasSearchStatus.Idle, reentered.Overseas.Industry.Status);
        Assert.Equal(string.Empty, reentered.Overseas.Industry.Query);
        Assert.Empty(reentered.Overseas.Industry.Results);
        Assert.Null(reentered.Overseas.Industry.Selected);
        Assert.Equal(LegacyOverseasSearchStatus.Idle, reentered.Overseas.Stock.Status);
        Assert.Equal(string.Empty, reentered.Overseas.Stock.Query);
        Assert.Empty(reentered.Overseas.Stock.Results);
        Assert.Null(reentered.Overseas.Stock.Selected);
        Assert.Equal(
            new LegacyOverseasMovingAverageSelection(false, true),
            reentered.Overseas.MovingAverages);
    }

    [Fact]
    public async Task TradingHaltReentry_ResetsUc7QuerySelectionAndSort()
    {
        var controller = new LegacyOperatorController(
            Lookup(),
            tradingHaltWorkflow: new LegacyTradingHaltWorkflowController(
                new StaticTradingHaltService()));
        controller.SelectTab(LegacyOperatorTabId.TradingHalt);
        var searched = await controller.SearchTradingHaltsAsync("halted");
        controller.SelectTradingHalt(
            Assert.Single(searched.TradingHalt!.Results).SelectionId);
        controller.ToggleTradingHaltSort();
        controller.SetMovingAverages(ma5: false, ma20: true);

        controller.SelectTab(LegacyOperatorTabId.Index);
        var reentered = controller.SelectTab(LegacyOperatorTabId.TradingHalt);

        Assert.Equal(LegacyTradingHaltRequestStatus.Idle, reentered.TradingHalt!.Status);
        Assert.Equal(string.Empty, reentered.TradingHalt.Query);
        Assert.Empty(reentered.TradingHalt.Results);
        Assert.Null(reentered.TradingHalt.Selected);
        Assert.Equal(LegacyTradingHaltNameSort.None, reentered.TradingHalt.NameSort);
        Assert.False(reentered.TradingHalt.Ma5);
        Assert.True(reentered.TradingHalt.Ma20);
    }

    [Fact]
    public async Task OverseasReentry_InvalidatesSearchCompletionFromDiscardedUc5()
    {
        var service = new DelayedOverseasIndustryService();
        var controller = new LegacyOperatorController(
            Lookup(),
            overseasWorkflow: new LegacyOverseasSelectionWorkflow(
                service,
                new StaticOverseasStockService()));
        controller.SelectTab(LegacyOperatorTabId.OverseasStocks);

        var pending = controller.SearchOverseasIndustriesAsync("old");
        await service.RequestStarted.Task.WaitAsync(TimeSpan.FromSeconds(5));
        controller.SelectTab(LegacyOperatorTabId.Index);
        controller.SelectTab(LegacyOperatorTabId.OverseasStocks);
        service.ReleaseRequest.TrySetResult();
        await pending;

        Assert.Equal(LegacyOverseasSearchStatus.Idle, controller.Current.Overseas!.Industry.Status);
        Assert.Equal(string.Empty, controller.Current.Overseas.Industry.Query);
        Assert.Empty(controller.Current.Overseas.Industry.Results);
    }

    private static ILegacyStockLookup Lookup() =>
        new StaticLookup(new LegacyStockSearchData(string.Empty, [], []));

    private sealed class StaticLookup : ILegacyStockLookup
    {
        private readonly LegacyStockSearchData _data;

        public StaticLookup(LegacyStockSearchData data)
        {
            _data = data;
        }

        public Task<LegacyStockSearchData> SearchAsync(
            string text,
            CancellationToken cancellationToken = default) => Task.FromResult(_data);
    }

    private sealed class CountingThemeService : IThemeSelectionService
    {
        private static readonly ThemeSelectionIdentity Identity = new(
            ThemeMarket.Krx,
            ThemeSession.NotApplicable,
            "00000001",
            "Theme A");

        public List<string> Queries { get; } = [];

        public Task<ThemeSearchResult> SearchAsync(
            string query,
            ThemeSession nxtSession,
            int maximumResults = LegacyThemeSelectionService.DefaultMaximumResults,
            CancellationToken cancellationToken = default)
        {
            Queries.Add(query);
            return Task.FromResult(CreateThemeResult(query, nxtSession));
        }

        public Task<ThemePreviewResult> PreviewAsync(
            ThemeSelectionIdentity identity,
            int maximumItems = LegacyThemeSelectionService.DefaultMaximumPreviewItems,
            CancellationToken cancellationToken = default) => Task.FromResult(
                new ThemePreviewResult(identity, DateTimeOffset.UtcNow, [], false));

        public static ThemeSearchResult CreateThemeResult(
            string query,
            ThemeSession nxtSession) => new(
                query,
                nxtSession,
                DateTimeOffset.UtcNow,
                [new ThemeSelectorItem(Identity, DataSourceKind.Oracle)],
                false);
    }

    private sealed class DelayedThemeService : IThemeSelectionService
    {
        private int _callCount;

        public TaskCompletionSource FirstRequestStarted { get; } = new(
            TaskCreationOptions.RunContinuationsAsynchronously);

        public TaskCompletionSource ReleaseFirstRequest { get; } = new(
            TaskCreationOptions.RunContinuationsAsynchronously);

        public int CallCount => Volatile.Read(ref _callCount);

        public async Task<ThemeSearchResult> SearchAsync(
            string query,
            ThemeSession nxtSession,
            int maximumResults = LegacyThemeSelectionService.DefaultMaximumResults,
            CancellationToken cancellationToken = default)
        {
            if (Interlocked.Increment(ref _callCount) == 1)
            {
                FirstRequestStarted.TrySetResult();
                await ReleaseFirstRequest.Task.WaitAsync(cancellationToken);
            }

            return CountingThemeService.CreateThemeResult(query, nxtSession);
        }

        public Task<ThemePreviewResult> PreviewAsync(
            ThemeSelectionIdentity identity,
            int maximumItems = LegacyThemeSelectionService.DefaultMaximumPreviewItems,
            CancellationToken cancellationToken = default) => Task.FromResult(
                new ThemePreviewResult(identity, DateTimeOffset.UtcNow, [], false));
    }

    private sealed class CountingExpertService : IExpertSelectionService
    {
        private static readonly ExpertSelectionIdentity Identity = new("0001", "Expert A");

        public List<string> Queries { get; } = [];

        public Task<ExpertSearchResult> SearchAsync(
            string query = "",
            int maximumResults = LegacyExpertSelectionService.DefaultMaximumResults,
            CancellationToken cancellationToken = default)
        {
            Queries.Add(query);
            return Task.FromResult(new ExpertSearchResult(
                query,
                DateTimeOffset.UtcNow,
                [new ExpertSelectorItem(Identity, DataSourceKind.Oracle)],
                false));
        }

        public Task<ExpertPreviewResult> PreviewAsync(
            ExpertSelectionIdentity identity,
            int maximumItems = LegacyExpertSelectionService.DefaultMaximumPreviewItems,
            CancellationToken cancellationToken = default) => Task.FromResult(
                new ExpertPreviewResult(identity, DateTimeOffset.UtcNow, [], false));
    }

    private sealed class CountingIndustryService : IIndustrySelectionService
    {
        public List<IndustryMarket> Markets { get; } = [];

        public Task<IReadOnlyList<IndustrySelection>> GetAsync(
            IndustryMarket market,
            CancellationToken cancellationToken = default)
        {
            Markets.Add(market);
            return Task.FromResult<IReadOnlyList<IndustrySelection>>(
            [
                new(market, "Industry 1", "001"),
                new(market, "Industry 2", "002")
            ]);
        }
    }

    private sealed class StaticComparisonDataService : ILegacyComparisonDataService
    {
        private static readonly IReadOnlyList<LegacyComparisonTarget> Domestic =
        [
            LegacyComparisonTarget.CreateKrxStock(
                LegacyComparisonDomesticMarket.Kospi,
                "Samsung Electronics",
                "005930"),
            LegacyComparisonTarget.CreateKrxStock(
                LegacyComparisonDomesticMarket.Kospi,
                "SK Hynix",
                "000660")
        ];

        public Task<IReadOnlyList<LegacyComparisonTarget>> SearchDomesticAsync(
            string query,
            int maximumResults,
            CancellationToken cancellationToken = default) => Task.FromResult(Domestic);

        public Task<IReadOnlyList<LegacyComparisonTarget>> SearchWorldAsync(
            string query,
            int maximumResults,
            CancellationToken cancellationToken = default) => Task.FromResult<
                IReadOnlyList<LegacyComparisonTarget>>([]);

        public Task<LegacyComparisonTarget?> ResolveStoredTargetAsync(
            LegacyComparisonStoredTargetReference target,
            CancellationToken cancellationToken = default) => Task.FromResult<
                LegacyComparisonTarget?>(null);
    }

    private sealed class CountingComparisonPairStore : ILegacyComparisonPairStore
    {
        public int ReadCount { get; private set; }

        public Task<byte[]?> ReadCp949Async(CancellationToken cancellationToken = default)
        {
            ReadCount++;
            return Task.FromResult<byte[]?>(null);
        }

        public Task WriteCp949Async(
            IReadOnlyList<LegacyComparisonPairWriteRow> rows,
            CancellationToken cancellationToken = default) => Task.CompletedTask;
    }

    private sealed class DelayedComparisonPairStore : ILegacyComparisonPairStore
    {
        public TaskCompletionSource FirstReadStarted { get; } = new(
            TaskCreationOptions.RunContinuationsAsynchronously);

        public TaskCompletionSource ReleaseFirstRead { get; } = new(
            TaskCreationOptions.RunContinuationsAsynchronously);

        public int ReadCount { get; private set; }

        public async Task<byte[]?> ReadCp949Async(
            CancellationToken cancellationToken = default)
        {
            ReadCount++;
            if (ReadCount == 1)
            {
                FirstReadStarted.TrySetResult();
                await ReleaseFirstRead.Task.WaitAsync(cancellationToken);
            }

            return null;
        }

        public Task WriteCp949Async(
            IReadOnlyList<LegacyComparisonPairWriteRow> rows,
            CancellationToken cancellationToken = default) => Task.CompletedTask;
    }

    private sealed class StaticOverseasIndustryService : IOverseasIndustryIndexSearchService
    {
        public Task<OverseasIndustryIndexSearchResult> SearchAsync(
            string query,
            int maximumResults = LegacyOverseasIndustryIndexSearchService.DefaultMaximumResults,
            CancellationToken cancellationToken = default) => Task.FromResult(
                new OverseasIndustryIndexSearchResult(
                    query.Trim(),
                    DateTimeOffset.UtcNow,
                    [new OverseasIndustryIndexItem("Semiconductor", "US Semiconductor", "NAS@SOX")],
                    false));
    }

    private sealed class StaticOverseasStockService : IOverseasStockSearchService
    {
        public Task<WorldStockSearchResult> SearchAsync(
            string query,
            int maximumResults = LegacyOverseasStockSearchService.DefaultMaximumResults,
            CancellationToken cancellationToken = default) => Task.FromResult(
                new WorldStockSearchResult(
                    query.Trim(),
                    DateTimeOffset.UtcNow,
                    [new WorldStockSearchItem("Apple", "NAS@AAPL", "US")],
                    false));
    }

    private sealed class DelayedOverseasIndustryService : IOverseasIndustryIndexSearchService
    {
        public TaskCompletionSource RequestStarted { get; } = new(
            TaskCreationOptions.RunContinuationsAsynchronously);

        public TaskCompletionSource ReleaseRequest { get; } = new(
            TaskCreationOptions.RunContinuationsAsynchronously);

        public async Task<OverseasIndustryIndexSearchResult> SearchAsync(
            string query,
            int maximumResults = LegacyOverseasIndustryIndexSearchService.DefaultMaximumResults,
            CancellationToken cancellationToken = default)
        {
            RequestStarted.TrySetResult();
            await ReleaseRequest.Task.WaitAsync(cancellationToken);
            return new OverseasIndustryIndexSearchResult(
                query.Trim(),
                DateTimeOffset.UtcNow,
                [new OverseasIndustryIndexItem("Old", "Old result", "NAS@OLD")],
                false);
        }
    }

    private sealed class StaticTradingHaltService : ITradingHaltSelectionService
    {
        public Task<TradingHaltSelectionResult> SearchAsync(
            string query = "",
            int maximumResults = LegacyTradingHaltSelectionService.DefaultMaximumResults,
            CancellationToken cancellationToken = default) => Task.FromResult(
                new TradingHaltSelectionResult(
                    query.Trim(),
                    DateTimeOffset.UtcNow,
                    [new TradingHaltSelection(
                        TradingHaltMarket.Kospi,
                        "005930",
                        "Samsung Electronics")],
                    false));
    }
}
