using System.Data;
using MMoneyCoderSharp.Data;

namespace MBN_STOCK_WEBVIEW.Core.Tests;

public sealed class LegacyStockMasterIdentityValidationServiceTests
{
    [Fact]
    public async Task ThemeItems_ResolveExactPrefixMarketWithBoundFreshQueries()
    {
        var executor = new CatalogRecordingQueryExecutor(call => call.TableName switch
        {
            "STOCK_MASTER_VERIFY_KOSPI" => StockTable(("삼성전자", "005930")),
            "STOCK_MASTER_VERIFY_KOSDAQ" => StockTable(("카카오", "035720")),
            _ => throw new InvalidOperationException(call.TableName)
        });
        var service = new LegacyStockMasterIdentityValidationService(executor);

        var result = await service.ValidateThemeItemsAsync(
        [
            new ThemeCatalogItem(0, "P005930", "삼성전자"),
            new ThemeCatalogItem(1, "D035720", "카카오")
        ]);

        Assert.Equal(
        [
            new StockMasterIdentity(
                StockMarket.Kospi,
                DataSourceKind.Oracle,
                "삼성전자",
                "005930"),
            new StockMasterIdentity(
                StockMarket.Kosdaq,
                DataSourceKind.Oracle,
                "카카오",
                "035720")
        ], result);
        Assert.Equal(2, executor.Calls.Count);
        Assert.All(executor.Calls, call =>
        {
            call.Spec.ValidateFor(call.Source);
            Assert.Equal(2, call.Spec.Parameters.Count);
            Assert.DoesNotContain("LIKE", call.Spec.Sql, StringComparison.OrdinalIgnoreCase);
        });
    }

    [Fact]
    public async Task NxtThemeItem_ValidatesStoredNameWithoutDisplaySuffix()
    {
        var executor = new CatalogRecordingQueryExecutor(call =>
        {
            Assert.Equal("STOCK_MASTER_VERIFY_NXT_KOSPI", call.TableName);
            Assert.DoesNotContain("CONCAT", call.Spec.Sql, StringComparison.OrdinalIgnoreCase);
            Assert.Equal("삼성전자", call.Spec.Parameters[1].Value);
            return StockTable(("삼성전자", "005930"));
        });
        var service = new LegacyStockMasterIdentityValidationService(executor);

        var result = await service.ValidateThemeItemsAsync(
            [new ThemeCatalogItem(0, "X005930", "삼성전자")]);

        var identity = Assert.Single(result);
        Assert.Equal(StockMarket.NxtKospi, identity.Market);
        Assert.Equal(DataSourceKind.MariaDb, identity.Source);
        Assert.Equal("삼성전자", identity.Name);
    }

    [Fact]
    public async Task ExpertRecommendation_UsesNxtDisplaySuffixAndRequiresOneMarket()
    {
        var executor = new CatalogRecordingQueryExecutor(call => call.TableName switch
        {
            "STOCK_MASTER_VERIFY_NXT_KOSPI" => StockTable(("삼성전자(NXT)", "005930")),
            _ => StockTable()
        });
        var service = new LegacyStockMasterIdentityValidationService(executor);

        var result = await service.ValidateExpertRecommendationsAsync(
            [new ExpertCatalogRecommendation(0, "005930", "삼성전자(NXT)", 70_000)]);

        var identity = Assert.Single(result);
        Assert.Equal(StockMarket.NxtKospi, identity.Market);
        Assert.Equal("삼성전자(NXT)", identity.Name);
        Assert.Equal(4, executor.Calls.Count);
        var nxt = Assert.Single(
            executor.Calls,
            call => call.TableName == "STOCK_MASTER_VERIFY_NXT_KOSPI");
        Assert.Contains("CONCAT", nxt.Spec.Sql, StringComparison.OrdinalIgnoreCase);
    }

    [Fact]
    public async Task ExpertRecommendation_RejectsSameExactIdentityAcrossMarkets()
    {
        var executor = new CatalogRecordingQueryExecutor(call => call.TableName switch
        {
            "STOCK_MASTER_VERIFY_KOSPI" or "STOCK_MASTER_VERIFY_KOSDAQ" =>
                StockTable(("동명이인", "000001")),
            _ => StockTable()
        });
        var service = new LegacyStockMasterIdentityValidationService(executor);

        await Assert.ThrowsAsync<StockMasterIdentityDataException>(() =>
            service.ValidateExpertRecommendationsAsync(
                [new ExpertCatalogRecommendation(0, "000001", "동명이인", 1)]));
    }

    [Theory]
    [InlineData("missing")]
    [InlineData("duplicate")]
    [InlineData("extra-column")]
    public async Task ThemeItem_RejectsMissingDuplicateOrExpandedResult(string mode)
    {
        var executor = new CatalogRecordingQueryExecutor(_ => mode switch
        {
            "missing" => StockTable(),
            "duplicate" => StockTable(("삼성전자", "005930"), ("삼성전자", "005930")),
            "extra-column" => StockTableWithExtraColumn(("삼성전자", "005930")),
            _ => throw new InvalidOperationException(mode)
        });
        var service = new LegacyStockMasterIdentityValidationService(executor);

        await Assert.ThrowsAsync<StockMasterIdentityDataException>(() =>
            service.ValidateThemeItemsAsync(
                [new ThemeCatalogItem(0, "P005930", "삼성전자")]));
    }

    [Fact]
    public async Task PreCanceledValidation_DoesNotTouchTheDatabase()
    {
        var executor = new CatalogRecordingQueryExecutor(_ => StockTable());
        var service = new LegacyStockMasterIdentityValidationService(executor);
        using var cancellation = new CancellationTokenSource();
        cancellation.Cancel();

        await Assert.ThrowsAnyAsync<OperationCanceledException>(() =>
            service.ValidateThemeItemsAsync(
                [new ThemeCatalogItem(0, "P005930", "삼성전자")],
                cancellation.Token));

        Assert.Empty(executor.Calls);
    }

    private static DataTable StockTable(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 static DataTable StockTableWithExtraColumn(params (string Name, string Code)[] rows)
    {
        var table = StockTable(rows);
        table.Columns.Add("UNEXPECTED", typeof(string));
        return table;
    }
}
