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

namespace MBN_STOCK_WEBVIEW.LegacyApplication.Tests;

public sealed class LegacyOperatorComparisonIntegrationTests
{
    [Fact]
    public async Task ComparisonTabBuildsPairAndAddsAuthoritativeSceneRow()
    {
        var data = new StaticComparisonDataService();
        var store = new MemoryComparisonPairStore();
        var controller = new LegacyOperatorController(
            new EmptyStockLookup(),
            comparisonWorkflow: new LegacyComparisonWorkflowController(data, store));

        var selected = await controller.SelectTabAsync(LegacyOperatorTabId.Comparison);
        Assert.NotNull(selected.Comparison);
        Assert.Empty(selected.Comparison.SavedPairs);

        var searched = await controller.SearchComparisonDomesticAsync("반도체");
        var results = searched.Comparison!.DomesticSearch.Results;
        controller.ChooseComparisonDomesticResult(results[0].SelectionId);
        controller.ChooseComparisonDomesticResult(results[1].SelectionId);
        var saved = await controller.AddComparisonPairAsync();
        Assert.Single(saved.Comparison!.SavedPairs);
        Assert.Single(store.Writes);

        var added = controller.ActivateComparisonAction("comparison-candle");
        var row = Assert.Single(added.Playlist);
        Assert.Equal("5026", row.ToPlayoutEntry().CutCode);
        Assert.Equal(
            "KOSPI:000660|KOSPI:005930",
            Assert.IsType<LegacySceneSelection>(row.ToPlayoutEntry().Selection).DataCode);
        Assert.Equal("비교", row.MarketText);
    }

    [Fact]
    public async Task ComparisonParentDoubleClickAddsOnlyDirectChildrenInOriginalOrder()
    {
        var controller = new LegacyOperatorController(
            new EmptyStockLookup(),
            comparisonWorkflow: new LegacyComparisonWorkflowController(
                new StaticComparisonDataService(),
                new MemoryComparisonPairStore()));
        await controller.SelectTabAsync(LegacyOperatorTabId.Comparison);
        var searched = await controller.SearchComparisonDomesticAsync("반도체");
        controller.ChooseComparisonDomesticResult(
            searched.Comparison!.DomesticSearch.Results[0].SelectionId);
        controller.ChooseComparisonDomesticResult(
            searched.Comparison.DomesticSearch.Results[1].SelectionId);
        await controller.AddComparisonPairAsync();

        var added = controller.ActivateComparisonSection(1);

        Assert.Collection(
            added.Playlist,
            row => Assert.Equal("5026", row.SceneAlias),
            row => Assert.Equal("5087", row.SceneAlias));
    }

    [Fact]
    public async Task DeleteAllComparisonPairs_NoPreservesRowsAndPerformsNoWrite()
    {
        var (controller, store) = await CreateControllerWithSavedPairAsync();
        var writesBeforeRequest = store.Writes.Count;

        var requested = await controller.DeleteComparisonPairAsync(all: true);

        Assert.Equal("모두 삭제하겠습니까?", requested.Dialog?.Message);
        Assert.Equal("MmoneyCoder", requested.Dialog?.Caption);
        Assert.True(requested.Dialog?.IsConfirmation);
        Assert.Single(requested.Comparison!.SavedPairs);
        Assert.Equal(writesBeforeRequest, store.Writes.Count);

        var dismissed = controller.DismissDialog();
        Assert.True(dismissed.Dialog?.IsConfirmation);
        Assert.Equal(writesBeforeRequest, store.Writes.Count);

        var canceled = controller.CancelDialog();

        Assert.Null(canceled.Dialog);
        Assert.Single(canceled.Comparison!.SavedPairs);
        Assert.Equal(writesBeforeRequest, store.Writes.Count);

        await controller.ConfirmDialogAsync();
        Assert.Equal(writesBeforeRequest, store.Writes.Count);
    }

    [Fact]
    public async Task DeleteAllComparisonPairs_YesConsumesConfirmationBeforeSingleWrite()
    {
        var (controller, store) = await CreateControllerWithSavedPairAsync();
        var writesBeforeRequest = store.Writes.Count;
        await controller.DeleteComparisonPairAsync(all: true);

        var confirmed = await controller.ConfirmDialogAsync();

        Assert.Null(confirmed.Dialog);
        Assert.Empty(confirmed.Comparison!.SavedPairs);
        Assert.Equal(writesBeforeRequest + 1, store.Writes.Count);
        Assert.Empty(store.Writes[^1]);

        await controller.ConfirmDialogAsync();
        Assert.Equal(writesBeforeRequest + 1, store.Writes.Count);
    }

    [Fact]
    public async Task DeleteAllComparisonPairs_ChangedListInvalidatesPendingConfirmation()
    {
        var (controller, store) = await CreateControllerWithSavedPairAsync();
        var writesBeforeRequest = store.Writes.Count;
        var requested = await controller.DeleteComparisonPairAsync(all: true);
        var rowId = Assert.Single(requested.Comparison!.SavedPairs).RowId;
        controller.SelectComparisonPair(rowId);
        await controller.DeleteComparisonPairAsync(all: true);

        var rejected = await controller.ConfirmDialogAsync();

        Assert.Single(rejected.Comparison!.SavedPairs);
        Assert.Equal(writesBeforeRequest, store.Writes.Count);
        Assert.Equal(LegacyOperatorStatusKind.Error, rejected.StatusKind);
    }

    [Fact]
    public async Task DeleteAllComparisonPairs_EmptyListStillPromptsAndYesDoesNotWrite()
    {
        var store = new MemoryComparisonPairStore();
        var controller = new LegacyOperatorController(
            new EmptyStockLookup(),
            comparisonWorkflow: new LegacyComparisonWorkflowController(
                new StaticComparisonDataService(),
                store));
        await controller.SelectTabAsync(LegacyOperatorTabId.Comparison);

        var requested = await controller.DeleteComparisonPairAsync(all: true);

        Assert.Equal("모두 삭제하겠습니까?", requested.Dialog?.Message);
        Assert.True(requested.Dialog?.IsConfirmation);
        var confirmed = await controller.ConfirmDialogAsync();
        Assert.Empty(confirmed.Comparison!.SavedPairs);
        Assert.Empty(store.Writes);
    }

    [Fact]
    public async Task LegacyComparisonImport_requires_explicit_confirmation_and_consumes_yes_once()
    {
        var store = new MemoryComparisonPairStore();
        var importer = new StaticComparisonImporter();
        var controller = new LegacyOperatorController(
            new EmptyStockLookup(),
            comparisonWorkflow: new LegacyComparisonWorkflowController(
                new StaticComparisonDataService(),
                store,
                pairImporter: importer));
        await controller.SelectTabAsync(LegacyOperatorTabId.Comparison);

        var requested = controller.RequestLegacyComparisonPairImport();

        Assert.True(requested.Dialog?.IsConfirmation);
        Assert.Contains("없는 항목만", requested.Dialog?.Message, StringComparison.Ordinal);
        Assert.Empty(store.Writes);
        Assert.Equal(0, importer.Calls);

        var confirmed = await controller.ConfirmDialogAsync();

        Assert.Null(confirmed.Dialog);
        Assert.Single(confirmed.Comparison!.SavedPairs);
        Assert.Equal(1, confirmed.Comparison.LastImport?.AddedPairCount);
        Assert.Single(store.Writes);
        Assert.Equal(1, importer.Calls);

        await controller.ConfirmDialogAsync();
        Assert.Single(store.Writes);
        Assert.Equal(1, importer.Calls);
    }

    private static async Task<(LegacyOperatorController Controller,
        MemoryComparisonPairStore Store)> CreateControllerWithSavedPairAsync()
    {
        var store = new MemoryComparisonPairStore();
        var controller = new LegacyOperatorController(
            new EmptyStockLookup(),
            comparisonWorkflow: new LegacyComparisonWorkflowController(
                new StaticComparisonDataService(),
                store));
        await controller.SelectTabAsync(LegacyOperatorTabId.Comparison);
        var searched = await controller.SearchComparisonDomesticAsync("반도체");
        controller.ChooseComparisonDomesticResult(
            searched.Comparison!.DomesticSearch.Results[0].SelectionId);
        controller.ChooseComparisonDomesticResult(
            searched.Comparison.DomesticSearch.Results[1].SelectionId);
        await controller.AddComparisonPairAsync();
        return (controller, store);
    }

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

    private sealed class StaticComparisonDataService : ILegacyComparisonDataService
    {
        private static readonly IReadOnlyList<LegacyComparisonTarget> Domestic =
        [
            LegacyComparisonTarget.CreateKrxStock(
                LegacyComparisonDomesticMarket.Kospi,
                "삼성전자",
                "005930"),
            LegacyComparisonTarget.CreateKrxStock(
                LegacyComparisonDomesticMarket.Kospi,
                "SK하이닉스",
                "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 MemoryComparisonPairStore : ILegacyComparisonPairStore
    {
        public List<IReadOnlyList<LegacyComparisonPairWriteRow>> Writes { get; } = [];

        public Task<byte[]?> ReadCp949Async(CancellationToken cancellationToken = default) =>
            Task.FromResult<byte[]?>(null);

        public Task WriteCp949Async(
            IReadOnlyList<LegacyComparisonPairWriteRow> rows,
            CancellationToken cancellationToken = default)
        {
            Writes.Add(rows.ToArray());
            return Task.CompletedTask;
        }
    }

    private sealed class StaticComparisonImporter : ILegacyComparisonPairImportService
    {
        public int Calls { get; private set; }

        public Task<LegacyComparisonImportResult> ImportAsync(
            CancellationToken cancellationToken = default)
        {
            cancellationToken.ThrowIfCancellationRequested();
            Calls++;
            return Task.FromResult(new LegacyComparisonImportResult(
                new string('A', 64),
                1,
                DateTimeOffset.Parse("2026-07-22T12:00:00+09:00"),
                [new LegacyComparisonImportPair(
                    1,
                    new LegacyComparisonImportTarget(
                        LegacyComparisonImportTargetKind.MarketTarget,
                        MarketTarget: "Kospi"),
                    new LegacyComparisonImportTarget(
                        LegacyComparisonImportTargetKind.MarketTarget,
                        MarketTarget: "Kosdaq"))]));
        }
    }
}

public sealed class LegacyComparisonProductionAdapterTests
{
    [Fact]
    public async Task DataAdapterMapsDomesticNxtAndWorldIdentities()
    {
        var lookup = new StaticStockLookup();
        var world = new StaticWorldLookup();
        var service = new LegacyComparisonDataService(lookup, world);

        var domestic = await service.SearchDomesticAsync("삼성", 10);
        Assert.Equal(LegacyComparisonTargetKind.KrxStock, domestic[0].Kind);
        Assert.Equal(LegacyComparisonTargetKind.NxtStock, domestic[1].Kind);
        Assert.Equal("삼성전자(NXT)", domestic[1].DisplayName);

        var overseas = Assert.Single(await service.SearchWorldAsync("APPLE", 10));
        Assert.Equal("world:US:AAPL", overseas.TargetId);
        var resolved = await service.ResolveStoredTargetAsync(
            new LegacyComparisonStoredTargetReference("APPLE", string.Empty));
        Assert.Equal(overseas, resolved);
    }

    [Fact]
    public async Task DataAdapter_preserves_world_row_isolation_diagnostics()
    {
        var world = new StaticWorldLookup
        {
            Result = new WorldStockSearchResult(
                string.Empty,
                DateTimeOffset.UtcNow,
                [new WorldStockSearchItem("APPLE", "AAPL", "US")],
                true,
                16)
        };
        var service = new LegacyComparisonDataService(new StaticStockLookup(), world);

        var result = await service.SearchWorldWithDiagnosticsAsync(string.Empty, 100);

        Assert.Single(result.Items);
        Assert.True(result.IsTruncated);
        Assert.Equal(16, result.IsolatedInvalidRowCount);
    }

    [Fact]
    public async Task DataAdapter_keeps_comma_name_visible_as_one_deferred_row()
    {
        var world = new StaticWorldLookup
        {
            Result = new WorldStockSearchResult(
                string.Empty,
                DateTimeOffset.UtcNow,
                [new WorldStockSearchItem("ACME, INC", "ACME", "US")],
                false)
        };
        var service = new LegacyComparisonDataService(new StaticStockLookup(), world);

        var result = await service.SearchWorldWithDiagnosticsAsync(string.Empty, 100);

        var target = Assert.Single(result.Items);
        Assert.False(target.CanSelect);
        Assert.Equal(1, result.DeferredUnsafeRowCount);
        Assert.Equal(0, result.IsolatedInvalidRowCount);
    }

    [Fact]
    public async Task FileStoreRoundTripsStrictCp949WithAtomicTarget()
    {
        var directory = Path.Combine(
            Path.GetTempPath(),
            $"mbn-comparison-{Guid.NewGuid():N}");
        var path = Path.Combine(directory, "종목비교.dat");
        try
        {
            var store = new LegacyComparisonPairFileStore(path);
            await store.WriteCp949Async(
            [
                new LegacyComparisonPairWriteRow(
                    1,
                    "삼성전자",
                    "코스피",
                    "SK하이닉스",
                    "코스피")
            ]);

            var bytes = Assert.IsType<byte[]>(await store.ReadCp949Async());
            var parsed = Assert.Single(LegacyComparisonFileParser.Parse(bytes));
            Assert.Equal("삼성전자", parsed.First.Name);
            Assert.Empty(Directory.EnumerateFiles(directory, "*.tmp"));
        }
        finally
        {
            if (Directory.Exists(directory))
            {
                Directory.Delete(directory, recursive: true);
            }
        }
    }

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

    private sealed class StaticWorldLookup : IWorldStockSearchService
    {
        public WorldStockSearchResult Result { get; init; } = new(
            string.Empty,
            DateTimeOffset.UtcNow,
            [new WorldStockSearchItem("APPLE", "AAPL", "US")],
            false);

        public Task<WorldStockSearchResult> SearchAsync(
            string query,
            int maximumResults = LegacyWorldStockSearchService.DefaultMaximumResults,
            CancellationToken cancellationToken = default) => Task.FromResult(
                Result with { Query = query.Trim() });
    }
}
