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

namespace MBN_STOCK_WEBVIEW.Core.Tests;

public sealed class LegacyNamedManualRestoreAuditTests
{
    [Theory]
    [InlineData("코스피", "주요매출 구성", ManualFinancialScreenKind.RevenueComposition, StockMarket.Kospi)]
    [InlineData("코스닥", "성장성 지표", ManualFinancialScreenKind.GrowthMetrics, StockMarket.Kosdaq)]
    [InlineData("코스피_NXT", "매출액", ManualFinancialScreenKind.Sales, StockMarket.NxtKospi)]
    [InlineData("코스닥_NXT", "영업이익", ManualFinancialScreenKind.OperatingProfit, StockMarket.NxtKosdaq)]
    public void ClassifyHistoricalFinancialGrammar(
        string group,
        string graphic,
        ManualFinancialScreenKind expectedScreen,
        StockMarket expectedMarket)
    {
        var candidate = LegacyNamedManualRestoreClassifier.Classify(
            Row(group, expectedMarket is StockMarket.NxtKospi or StockMarket.NxtKosdaq
                ? "테스트(NXT)"
                : "테스트", graphic, string.Empty, "1/1", string.Empty));

        Assert.NotNull(candidate);
        Assert.Equal(LegacyNamedManualRestoreKind.Financial, candidate.Kind);
        Assert.True(candidate.IsHistorical);
        Assert.Equal(expectedScreen, candidate.FinancialScreen);
        Assert.Equal(expectedMarket, candidate.Market);
        Assert.Equal("테스트" + (expectedMarket is StockMarket.NxtKospi or StockMarket.NxtKosdaq
            ? "(NXT)"
            : string.Empty), candidate.StockName);
        Assert.Equal("Named manual restore candidate (Financial)", candidate.ToString());
    }

    [Theory]
    [InlineData("KOSPI", "REVENUE_COMPOSITION", ManualFinancialScreenKind.RevenueComposition)]
    [InlineData("KOSDAQ", "GROWTH_METRICS", ManualFinancialScreenKind.GrowthMetrics)]
    [InlineData("NXT_KOSPI", "SALES", ManualFinancialScreenKind.Sales)]
    [InlineData("NXT_KOSDAQ", "OPERATING_PROFIT", ManualFinancialScreenKind.OperatingProfit)]
    public void ClassifyCanonicalFinancialGrammar(
        string group,
        string graphic,
        ManualFinancialScreenKind expectedScreen)
    {
        var candidate = LegacyNamedManualRestoreClassifier.Classify(
            Row(group, group.StartsWith("NXT_", StringComparison.Ordinal)
                ? "테스트(NXT)"
                : "테스트", graphic, string.Empty, "1/1", string.Empty));

        Assert.NotNull(candidate);
        Assert.False(candidate.IsHistorical);
        Assert.Equal(expectedScreen, candidate.FinancialScreen);
    }

    [Theory]
    [InlineData("개인 순매도 상위(수동)", S5025ManualAudience.Individual)]
    [InlineData("외국인 순매도 상위(수동)", S5025ManualAudience.Foreign)]
    [InlineData("기관 순매도 상위(수동)", S5025ManualAudience.Institution)]
    public void ClassifyHistoricalNetSellGrammar(
        string group,
        S5025ManualAudience expected)
    {
        var candidate = LegacyNamedManualRestoreClassifier.Classify(
            Row(group, "지수", "순매도 상위", "순매도 상위", "1/1", string.Empty));

        Assert.NotNull(candidate);
        Assert.Equal(LegacyNamedManualRestoreKind.NetSell, candidate.Kind);
        Assert.True(candidate.IsHistorical);
        Assert.Equal(expected, candidate.Audience);
    }

    [Theory]
    [InlineData("INDIVIDUAL", S5025ManualAudience.Individual)]
    [InlineData("FOREIGN", S5025ManualAudience.Foreign)]
    [InlineData("INSTITUTION", S5025ManualAudience.Institution)]
    public void ClassifyCanonicalNetSellGrammar(
        string group,
        S5025ManualAudience expected)
    {
        var candidate = LegacyNamedManualRestoreClassifier.Classify(
            Row(group, "INDEX", "MANUAL_NET_SELL", "MANUAL_NET_SELL", "1/1", string.Empty));

        Assert.NotNull(candidate);
        Assert.False(candidate.IsHistorical);
        Assert.Equal(expected, candidate.Audience);
    }

    [Fact]
    public void ClassifyHistoricalAndVersionedViGrammar()
    {
        var historical = LegacyNamedManualRestoreClassifier.Classify(
            Row(string.Empty, "가,나,다,라,마,바", "5단 표그래프", "VI 발동(수동)", "1/2", string.Empty));
        var version = new string('a', 64);
        var canonical = LegacyNamedManualRestoreClassifier.Classify(
            Row("PAGED_VI", TrustedViManualReference.SubjectSentinel, "INPUT_ORDER", "CURRENT", "1/2", version));

        Assert.NotNull(historical);
        Assert.True(historical.IsHistorical);
        Assert.Equal(6, historical.HistoricalViNames!.Count);
        Assert.Equal(2, historical.ExpectedPageCount);
        Assert.NotNull(canonical);
        Assert.False(canonical.IsHistorical);
        Assert.Equal(version, canonical.ExpectedViVersion);
        Assert.Equal(2, canonical.ExpectedPageCount);
    }

    [Theory]
    [InlineData("1/1", "가,나,다,라,마,바")]
    [InlineData("1/21", "가")]
    [InlineData("01/1", "가")]
    public void RejectViPageGrammarThatCannotMatchMaterialization(
        string page,
        string subject)
    {
        Assert.Null(LegacyNamedManualRestoreClassifier.Classify(
            Row(string.Empty, subject, "5단 표그래프", "VI 발동(수동)", page, string.Empty)));
    }

    [Fact]
    public void FinancialFreshProofRequiresTwoStableReadsAndOneMasterIdentity()
    {
        var candidate = Assert.IsType<LegacyNamedManualRestoreCandidate>(
            LegacyNamedManualRestoreClassifier.Classify(
                Row("코스피", "테스트", "매출액", string.Empty, "1/1", string.Empty)));
        var first = SalesSnapshot("테스트", 'A');
        var second = SalesSnapshot("테스트", 'A');
        var search = Search(
            "테스트",
            new StockSearchItem(StockMarket.Kospi, DataSourceKind.Oracle, "테스트", "A0001"));

        Assert.Equal(
            LegacyNamedManualFreshProofFailure.None,
            LegacyNamedManualFreshProof.EvaluateFinancial(candidate, first, second, search));
        Assert.Equal(
            LegacyNamedManualFreshProofFailure.FinancialSnapshotChanged,
            LegacyNamedManualFreshProof.EvaluateFinancial(
                candidate,
                first,
                SalesSnapshot("테스트", 'B'),
                search));
        Assert.Equal(
            LegacyNamedManualFreshProofFailure.FinancialStockIdentityNotUnique,
            LegacyNamedManualFreshProof.EvaluateFinancial(
                candidate,
                first,
                second,
                Search(
                    "테스트",
                    new StockSearchItem(StockMarket.Kospi, DataSourceKind.Oracle, "테스트", "A0001"),
                    new StockSearchItem(StockMarket.Kosdaq, DataSourceKind.Oracle, "테스트", "A0002"))));
    }

    [Fact]
    public void FinancialFreshProofUsesOriginalNxtDisplayIdentityForSearch()
    {
        var candidate = Assert.IsType<LegacyNamedManualRestoreCandidate>(
            LegacyNamedManualRestoreClassifier.Classify(
                Row("코스피_NXT", "테스트(NXT)", "매출액", string.Empty, "1/1", string.Empty)));

        Assert.Equal("테스트", LegacyNamedManualFreshProof.FinancialStockSearchQuery(candidate));
        Assert.Equal(
            LegacyNamedManualFreshProofFailure.None,
            LegacyNamedManualFreshProof.EvaluateFinancial(
                candidate,
                SalesSnapshot("테스트(NXT)", 'A'),
                SalesSnapshot("테스트(NXT)", 'A'),
                Search(
                    "테스트",
                    new StockSearchItem(
                        StockMarket.NxtKospi,
                        DataSourceKind.MariaDb,
                        "테스트(NXT)",
                        "A0001"))));
    }

    [Fact]
    public void ManualFileFreshProofRejectsShapeOrIdentityDrift()
    {
        var netSell = Assert.IsType<LegacyNamedManualRestoreCandidate>(
            LegacyNamedManualRestoreClassifier.Classify(
                Row("INDIVIDUAL", "INDEX", "MANUAL_NET_SELL", "MANUAL_NET_SELL", "1/1", string.Empty)));
        var rows = Enumerable.Range(0, 5)
            .Select(_ => new S5025TrustedManualRow("", "", "", ""))
            .ToArray();
        Assert.Equal(
            LegacyNamedManualFreshProofFailure.None,
            LegacyNamedManualFreshProof.EvaluateNetSell(netSell, rows));
        Assert.Equal(
            LegacyNamedManualFreshProofFailure.NetSellSnapshotInvalid,
            LegacyNamedManualFreshProof.EvaluateNetSell(netSell, rows.Take(4).ToArray()));

        var vi = Assert.IsType<LegacyNamedManualRestoreCandidate>(
            LegacyNamedManualRestoreClassifier.Classify(
                Row(string.Empty, "가,나,다,라,마,바", "5단 표그래프", "VI 발동(수동)", "1/2", string.Empty)));
        var snapshot = new TrustedViStockNameSnapshot(
            ["가", "나", "다", "라", "마", "바"],
            new string('a', 64));
        Assert.Equal(
            LegacyNamedManualFreshProofFailure.None,
            LegacyNamedManualFreshProof.EvaluateVi(vi, snapshot));
        Assert.Equal(
            LegacyNamedManualFreshProofFailure.ViHistoricalNamesChanged,
            LegacyNamedManualFreshProof.EvaluateVi(
                vi,
                snapshot with { StockNames = ["가", "나", "다", "라", "마", "변경"] }));
    }

    private static LegacyPListAuditRow Row(
        string group,
        string subject,
        string graphic,
        string subtype,
        string page,
        string dataCode)
    {
        var fields = new[] { "1", group, subject, graphic, subtype, page, dataCode };
        return new LegacyPListAuditRow(
            "20260712",
            "감사",
            0,
            true,
            Array.AsReadOnly(fields),
            string.Join('^', fields),
            page.Replace(" ", string.Empty, StringComparison.Ordinal));
    }

    private static ManualFinancialSnapshot SalesSnapshot(string stockName, char version) =>
        new(
            new ManualSalesRecord(
                new ManualFinancialIdentity(ManualFinancialScreenKind.Sales, stockName),
                Enumerable.Range(1, 6)
                    .Select(index => new ManualQuarterValue($"Q{index}", index))
                    .ToArray()),
            new string(version, 64));

    private static StockSearchResult Search(string query, params StockSearchItem[] items) =>
        new(query, DateTimeOffset.UnixEpoch, Array.AsReadOnly(items), IsTruncated: false);
}
