using MMoneyCoderSharp.Data;

namespace MBN_STOCK_WEBVIEW.Core.Tests;

public sealed class LegacyPListReadAuditParserTests
{
    [Fact]
    public void Parse_PreservesEveryOriginalFieldAndOnlyNormalizesDisplayPageSpaces()
    {
        const string text = "1^테마^반도체 장비^5단 표그래프^테마-현재가(입력순)^ 1 / 12 ^00123";

        var result = LegacyPListReadAuditParser.Parse(
            "00000001",
            "아침 방송",
            "0",
            text);

        Assert.True(result.IsSuccess);
        var row = Assert.IsType<LegacyPListAuditRow>(result.Row);
        Assert.Equal(text, row.RawListText);
        Assert.Equal(text, string.Join('^', row.Fields));
        Assert.True(row.IsLossless);
        Assert.True(row.IsEnabled);
        Assert.Equal("테마", row.GroupCode);
        Assert.Equal("반도체 장비", row.Subject);
        Assert.Equal(" 1 / 12 ", row.RawPageText);
        Assert.Equal("1/12", row.PageTextForDisplay);
    }

    [Theory]
    [InlineData("1^테마^반도체^5단^현재가^1/1", LegacyPListAuditParseFailure.InvalidFieldCount)]
    [InlineData("2^테마^반도체^5단^현재가^1/1^1", LegacyPListAuditParseFailure.InvalidEnabledField)]
    [InlineData("1^테마^반도체^5단^현재가^1/1^1^extra", LegacyPListAuditParseFailure.InvalidFieldCount)]
    public void Parse_RejectsAnythingOutsideTheOriginalSevenFieldGrammar(
        string text,
        LegacyPListAuditParseFailure expected)
    {
        var result = LegacyPListReadAuditParser.Parse("00000001", "방송", "0", text);

        Assert.False(result.IsSuccess);
        Assert.Equal(expected, result.Failure);
        Assert.Null(result.Row);
    }

    [Fact]
    public void Parse_RejectsReplacementAndKnownCodepageDamageWithoutEchoingText()
    {
        var replacement = LegacyPListReadAuditParser.Parse(
            "00000001", "방송", "0", "1^테마^반도체�^5단^현재가^1/1^1");
        var mojibake = LegacyPListReadAuditParser.Parse(
            "00000001", "방송", "0", "1^테마^Ã¬Â Â^5단^현재가^1/1^1");

        Assert.Equal(LegacyPListAuditParseFailure.ReplacementCharacter, replacement.Failure);
        Assert.Equal(LegacyPListAuditParseFailure.MojibakeSignal, mojibake.Failure);
    }

    [Fact]
    public void Parse_RejectsUnpairedUtf16SurrogatesButPreservesValidScalarPairs()
    {
        var unpaired = LegacyPListReadAuditParser.Parse(
            "00000001", "방송", "0", "1^테마^반도체\uD800^5단^현재가^1/1^1");
        var validPair = LegacyPListReadAuditParser.Parse(
            "00000001", "방송 📈", "0", "1^테마^반도체 📈^5단^현재가^1/1^1");

        Assert.Equal(LegacyPListAuditParseFailure.UnsafeText, unpaired.Failure);
        Assert.True(validPair.IsSuccess);
        Assert.True(validPair.Row!.IsLossless);
    }

    [Theory]
    [InlineData("0000001")]
    [InlineData("0000000A")]
    [InlineData(" 00000001")]
    public void Parse_RequiresTheOriginalEightDigitProgramIdentity(string code)
    {
        var result = LegacyPListReadAuditParser.Parse(
            code, "방송", "0", "1^테마^반도체^5단^현재가^1/1^1");

        Assert.Equal(LegacyPListAuditParseFailure.InvalidProgramCode, result.Failure);
    }

    [Theory]
    [InlineData("00")]
    [InlineData("-1")]
    [InlineData("1000")]
    [InlineData(" 0")]
    public void Parse_RequiresCanonicalBoundedItemIndex(string itemIndex)
    {
        var result = LegacyPListReadAuditParser.Parse(
            "00000001", "방송", itemIndex, "1^테마^반도체^5단^현재가^1/1^1");

        Assert.Equal(LegacyPListAuditParseFailure.InvalidItemIndex, result.Failure);
    }

    [Fact]
    public void Parse_AllowsEmptyLegacySelectionFieldsAndLiteralQuestionMarks()
    {
        const string text = "0^^확인?^^^1/1^";

        var result = LegacyPListReadAuditParser.Parse("00000001", "질문?", "0", text);

        Assert.True(result.IsSuccess);
        Assert.False(result.Row!.IsEnabled);
        Assert.Equal(string.Empty, result.Row.GroupCode);
        Assert.Equal(string.Empty, result.Row.DataCode);
    }

    [Fact]
    public void Evidence_RequiresContiguousIndexesAndExactPersistenceFields()
    {
        var first = Parse("0", "1^업종_코스피^코스피^네모그래프^^1/1^");
        var second = Parse("1", "0^업종_코스피^코스피^섹터지수^^^");
        var stored = new NamedPlaylistStoredItem(
            0,
            true,
            new MBN_STOCK_WEBVIEW.Core.Playout.Scenes.LegacySceneSelection(
                "업종_코스피", "코스피", "네모그래프", "", ""),
            new NamedPlaylistPageState(1, 1));

        Assert.True(LegacyPListReadAuditEvidence.HasContiguousIndexes([first, second]));
        Assert.False(LegacyPListReadAuditEvidence.HasContiguousIndexes([
            first,
            second with { ItemIndex = 2 }
        ]));
        Assert.True(LegacyPListReadAuditEvidence.MatchesPersistenceResult(first, stored));
        Assert.False(LegacyPListReadAuditEvidence.MatchesPersistenceResult(
            first with { RawListText = first.RawListText.Replace("코스피", "코스닥") },
            stored with { IsEnabled = false }));
    }

    [Theory]
    [InlineData("1^업종_코스피^코스피^네모그래프^^1/1^", LegacyPListAuditRoute.IndustryFixedClosedMapper)]
    [InlineData("1^해외지수^다우존스^캔들그래프^5일^1/1^", LegacyPListAuditRoute.ForeignIndexCandleFreshIdentity)]
    [InlineData("1^테마_NXT^반도체(NXT)^5단 표그래프^테마-현재가(입력순)^1/1^123", LegacyPListAuditRoute.NxtThemeFreshIdentity)]
    [InlineData("1^테마^반도체^5단 표그래프^테마-현재가(입력순)^1/1^123", LegacyPListAuditRoute.KrxThemeFreshIdentity)]
    [InlineData("1^해외종목^APPLE^캔들그래프^5일^1/1^", LegacyPListAuditRoute.ForeignStockFreshIdentity)]
    [InlineData("1^FOREIGN_STOCK^NVIDIA^1열판기본^CURRENT^1/1^NAS@NVDA|US|1", LegacyPListAuditRoute.ExistingClosedMapper)]
    [InlineData("1^FOREIGN_STOCK^NVIDIA^1열판기본^CURRENT^1/1^NAS@𐐀|US|1", LegacyPListAuditRoute.ExistingClosedMapper)]
    [InlineData("1^FOREIGN_STOCK^NVIDIA^MA5^PRICE^1/1^NAS@NVDA|US|1", LegacyPListAuditRoute.Unmapped)]
    [InlineData("1^코스피^삼성전자^성장성 지표^^1/1^", LegacyPListAuditRoute.ManualFreshMaterialization)]
    [InlineData("1^KOSPI^삼성전자^GROWTH_METRICS^^1/1^", LegacyPListAuditRoute.ManualFreshMaterialization)]
    [InlineData("1^INDIVIDUAL^INDEX^MANUAL_NET_SELL^MANUAL_NET_SELL^1/1^", LegacyPListAuditRoute.ManualFreshMaterialization)]
    [InlineData("1^PAGED_VI^@MBN_VI_SNAPSHOT_V1^INPUT_ORDER^CURRENT^1/1^aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", LegacyPListAuditRoute.ManualFreshMaterialization)]
    [InlineData("1^코스피^삼성전자^캔들그래프^20일^1/1^005930", LegacyPListAuditRoute.ExistingClosedMapper)]
    [InlineData("1^ALL^INDEX^PROGRAM_TRADING^TABLE_GRAPH^1/1^", LegacyPListAuditRoute.ExistingClosedMapper)]
    [InlineData("1^알수없는그룹^알수없는대상^비슷한그래프^^1/1^", LegacyPListAuditRoute.Unmapped)]
    public void Evidence_RoutesSpecialRowsWithoutGuessing(
        string text,
        LegacyPListAuditRoute expected)
    {
        Assert.Equal(expected, LegacyPListReadAuditEvidence.ClassifyRoute(Parse("0", text)));
    }

    [Fact]
    public void Evidence_DoesNotTreatNearestStockLabelAsAClosedMapperMatch()
    {
        var exact = Parse("0", "1^코스피^삼성전자^수익률그래프^20일^1/1^005930");
        var nearest = Parse("0", "1^코스피^삼성전자^수익률 그래프^20일^1/1^005930");

        Assert.Equal(
            LegacyPListAuditRoute.ExistingClosedMapper,
            LegacyPListReadAuditEvidence.ClassifyRoute(exact));
        Assert.Equal(
            LegacyPListAuditRoute.Unmapped,
            LegacyPListReadAuditEvidence.ClassifyRoute(nearest));
    }

    private static LegacyPListAuditRow Parse(string itemIndex, string text) =>
        Assert.IsType<LegacyPListAuditRow>(LegacyPListReadAuditParser.Parse(
            "00000001", "방송", itemIndex, text).Row);
}
