using System.Data;
using MBN_STOCK_WEBVIEW.LegacyApplication;
using MMoneyCoderSharp.Data;

namespace MBN_STOCK_WEBVIEW.LegacyApplication.Tests;

public sealed class LegacyParityStockSearchServiceTests
{
    [Fact]
    public async Task Search_PreservesProviderAndRowOrderWithoutLimitOrSort()
    {
        var executor = new RecordingExecutor(new Dictionary<string, DataTable>
        {
            ["LEGACY_PARITY_KOSPI_MASTER"] = Table(("중복", "000001")),
            ["LEGACY_PARITY_KOSDAQ_MASTER"] = Table(("중복", "100001")),
            ["LEGACY_PARITY_NXT_KOSPI_MASTER"] = Table(("중복(NXT)", "200001")),
            ["LEGACY_PARITY_NXT_KOSDAQ_MASTER"] = Table(("중복", "300001")),
            ["LEGACY_PARITY_KOSPI"] = Table(("가-2", "000002"), ("가-1", "000001")),
            ["LEGACY_PARITY_KOSDAQ"] = Table(("나", "100001")),
            ["LEGACY_PARITY_NXT_KOSPI"] = Table(("다(NXT)", "200001")),
            ["LEGACY_PARITY_NXT_KOSDAQ"] = Table(("라(NXT)", "300001"))
        });
        var service = new LegacyParityStockSearchService(executor);

        var result = await service.SearchAsync(" a%_ ");

        Assert.Equal(["가-2", "가-1", "나", "다(NXT)", "라(NXT)"], result.DisplayNames);
        Assert.Equal("300001", result.ResolveLastByDisplayName("중복")?.StockCode);
        Assert.Equal(LegacyDomesticStockMarket.NxtKosdaq,
            result.ResolveLastByDisplayName("중복")?.Market);
        Assert.Equal(8, executor.Calls.Count);

        foreach (var call in executor.Calls.Skip(4))
        {
            Assert.DoesNotContain("ORDER BY", call.Spec.Sql, StringComparison.OrdinalIgnoreCase);
            Assert.DoesNotContain("LIMIT", call.Spec.Sql, StringComparison.OrdinalIgnoreCase);
            Assert.DoesNotContain("ROWNUM", call.Spec.Sql, StringComparison.OrdinalIgnoreCase);
            Assert.Single(call.Spec.Parameters);
            Assert.Equal(" A%_ ", call.Spec.Parameters[0].Value);
        }
    }

    [Fact]
    public async Task EmptySearch_IsAllowedAndMasterSnapshotIsCached()
    {
        var tables = new Dictionary<string, DataTable>();
        foreach (var name in new[]
        {
            "LEGACY_PARITY_KOSPI", "LEGACY_PARITY_KOSDAQ",
            "LEGACY_PARITY_NXT_KOSPI", "LEGACY_PARITY_NXT_KOSDAQ"
        })
        {
            tables[name] = Table();
            tables[$"{name}_MASTER"] = Table();
        }

        var executor = new RecordingExecutor(tables);
        var service = new LegacyParityStockSearchService(executor);

        await service.SearchAsync(string.Empty);
        await service.SearchAsync(string.Empty);

        Assert.Equal(12, executor.Calls.Count);
        Assert.Equal(4, executor.Calls.Count(call => call.TableName.EndsWith("_MASTER", StringComparison.Ordinal)));
        Assert.All(executor.Calls.Where(call => !call.TableName.EndsWith("_MASTER", StringComparison.Ordinal)),
            call => Assert.Equal(string.Empty, call.Spec.Parameters[0].Value));
    }

    [Fact]
    public async Task InitialConsonantSearch_FiltersOnlyLegacySearchCandidatesInProviderOrder()
    {
        var executor = new RecordingExecutor(new Dictionary<string, DataTable>
        {
            ["LEGACY_PARITY_KOSPI_MASTER"] = Table(
                ("삼성전자", "005930")),
            ["LEGACY_PARITY_KOSDAQ_MASTER"] = Table(
                ("삼성중공업", "010140")),
            ["LEGACY_PARITY_NXT_KOSPI_MASTER"] = Table(
                ("삼성전자(NXT)", "005930"),
                ("삼성화재(NXT)", "000810")),
            ["LEGACY_PARITY_NXT_KOSDAQ_MASTER"] = Table(
                ("카카오게임즈(NXT)", "293490")),
            ["LEGACY_PARITY_KOSPI"] = Table(
                ("삼성전자", "005930"),
                ("서울반도체", "046890")),
            ["LEGACY_PARITY_KOSDAQ"] = Table(
                ("삼성중공업", "010140")),
            ["LEGACY_PARITY_NXT_KOSPI"] = Table(
                ("삼성전자(NXT)", "005930")),
            ["LEGACY_PARITY_NXT_KOSDAQ"] = Table()
        });
        var service = new LegacyParityStockSearchService(executor);

        var result = await service.SearchAsync("ㅅㅅ");

        Assert.Equal(
            ["삼성전자", "삼성중공업", "삼성전자(NXT)"],
            result.DisplayNames);
        Assert.DoesNotContain("삼성화재(NXT)", result.DisplayNames);
        Assert.NotNull(result.ResolveLastByDisplayName("삼성화재(NXT)"));
        Assert.Equal(8, executor.Calls.Count);
        Assert.All(
            executor.Calls.Where(call => !call.TableName.EndsWith("_MASTER", StringComparison.Ordinal)),
            call => Assert.Equal(string.Empty, call.Spec.Parameters[0].Value));
        Assert.Contains(
            executor.Calls,
            call => call.TableName == "LEGACY_PARITY_NXT_KOSPI" &&
                call.Spec.Sql.Contains("N_ONLINE", StringComparison.Ordinal));
    }

    private static DataTable Table(params (string Name, string Code)[] rows)
    {
        var table = new DataTable();
        table.Columns.Add("STOCK_NAME", typeof(string));
        table.Columns.Add("STOCK_CODE", typeof(string));
        foreach (var row in rows)
        {
            table.Rows.Add(row.Name, row.Code);
        }

        return table;
    }

    private sealed class RecordingExecutor : IDataQueryExecutor
    {
        private readonly IReadOnlyDictionary<string, DataTable> _tables;

        public RecordingExecutor(IReadOnlyDictionary<string, DataTable> tables)
        {
            _tables = tables;
        }

        public List<Call> Calls { get; } = [];

        public DataTable Execute(DataSourceKind source, string tableName, string query) =>
            throw new NotSupportedException();

        public Task<DataTable> ExecuteAsync(
            DataSourceKind source,
            string tableName,
            string query,
            CancellationToken cancellationToken = default) =>
            throw new NotSupportedException();

        public Task<DataTable> ExecuteAsync(
            DataSourceKind source,
            string tableName,
            DataQuerySpec query,
            CancellationToken cancellationToken = default)
        {
            Calls.Add(new Call(source, tableName, query));
            return Task.FromResult(_tables[tableName].Copy());
        }
    }

    private sealed record Call(DataSourceKind Source, string TableName, DataQuerySpec Spec);
}
