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

namespace MBN_STOCK_WEBVIEW.LegacyBridge.Tests;

public sealed class LegacyOperatorCatalogBridgeTests
{
    [Theory]
    [InlineData("theme", LegacyOperatorCatalogEntity.Theme)]
    [InlineData("expert", LegacyOperatorCatalogEntity.Expert)]
    public void Open_accepts_only_the_closed_ThemeA_or_EList_identity(
        string entity,
        LegacyOperatorCatalogEntity expected)
    {
        Assert.True(Parse(
            JsonSerializer.Serialize(new
            {
                type = "open-operator-catalog",
                payload = new { entity }
            }),
            out var parsed));
        Assert.Equal(expected, Assert.IsType<LegacyOpenOperatorCatalogIntent>(parsed).Entity);

        Assert.False(Parse(
            """
            {"type":"open-operator-catalog","payload":{"entity":"SB_LIST"}}
            """,
            out _));
    }

    [Fact]
    public void Stock_search_accepts_explicit_blank_show_all_but_rejects_unsafe_or_overlong_input()
    {
        var blank = Assert.IsType<LegacySearchOperatorCatalogStocksIntent>(ParseRequired(
            "search-operator-catalog-stocks",
            """{"query":""}"""));
        Assert.Equal(string.Empty, blank.Query);

        foreach (var query in new[]
                 {
                     "bad\nvalue",
                     "bad\0value",
                     new string('A', LegacyStockSearchService.MaximumQueryLength + 1)
                 })
        {
            Assert.False(Parse(
                JsonSerializer.Serialize(new
                {
                    type = "search-operator-catalog-stocks",
                    payload = new { query }
                }),
                out _));
        }
    }

    [Fact]
    public void Catalog_selection_and_draft_changes_accept_only_opaque_ids()
    {
        Assert.IsType<LegacySelectOperatorCatalogResultIntent>(ParseRequired(
            "select-operator-catalog-result",
            """{"resultId":"catalog-result-0123456789abcdef"}"""));
        Assert.IsType<LegacySelectOperatorCatalogStockIntent>(ParseRequired(
            "select-operator-catalog-stock",
            """{"resultId":"catalog-stock-0123456789abcdef"}"""));
        Assert.IsType<LegacyMoveOperatorCatalogDraftRowIntent>(ParseRequired(
            "move-operator-catalog-draft-row",
            """{"rowId":"catalog-draft-0123456789abcdef","direction":"up"}"""));
        var reorder = Assert.IsType<LegacyReorderOperatorCatalogDraftRowIntent>(ParseRequired(
            "reorder-operator-catalog-draft-row",
            """{"sourceRowId":"catalog-draft-0123456789abcdef","targetRowId":"catalog-draft-fedcba9876543210","position":"after"}"""));
        Assert.Equal(LegacyDragDropPosition.After, reorder.Position);
        Assert.IsType<LegacyRemoveOperatorCatalogDraftRowIntent>(ParseRequired(
            "remove-operator-catalog-draft-row",
            """{"rowId":"catalog-draft-0123456789abcdef"}"""));
        Assert.IsType<LegacySelectOperatorCatalogDraftRowIntent>(ParseRequired(
            "select-operator-catalog-draft-row",
            """{"rowId":"catalog-draft-0123456789abcdef"}"""));
        Assert.IsType<LegacyRemoveActiveOperatorCatalogDraftRowIntent>(ParseRequired(
            "remove-active-operator-catalog-draft-row",
            "{}"));
        Assert.IsType<LegacyClearOperatorCatalogDraftRowsIntent>(ParseRequired(
            "clear-operator-catalog-draft-rows",
            "{}"));

        Assert.False(Parse(
            """
            {"type":"select-operator-catalog-result","payload":{"resultId":"catalog-result-0123456789abcdef","themeCode":"00000001"}}
            """,
            out _));
        Assert.False(Parse(
            """
            {"type":"select-operator-catalog-stock","payload":{"stockCode":"005930"}}
            """,
            out _));
        Assert.False(Parse(
            """
            {"type":"reorder-operator-catalog-draft-row","payload":{"sourceRowId":"catalog-draft-0123456789abcdef","targetRowId":"catalog-draft-fedcba9876543210","position":"after","playIndex":1}}
            """,
            out _));
        Assert.False(Parse(
            """
            {"type":"select-operator-catalog-draft-row","payload":{"rowId":"catalog-draft-0123456789abcdef","stockCode":"005930"}}
            """,
            out _));
        Assert.False(Parse(
            """
            {"type":"remove-active-operator-catalog-draft-row","payload":{"rowId":"catalog-draft-0123456789abcdef"}}
            """,
            out _));
        Assert.False(Parse(
            """
            {"type":"clear-operator-catalog-draft-rows","payload":{"themeCode":"00000001"}}
            """,
            out _));
    }

    [Fact]
    public void Create_save_and_add_are_closed_and_typed_while_child_delete_is_removed()
    {
        Assert.IsType<LegacyBeginCreateThemeCatalogIntent>(ParseRequired(
            "begin-create-theme-catalog",
            """{"market":"nxt"}"""));
        var marketChange = Assert.IsType<LegacyChangeCreateThemeMarketIntent>(ParseRequired(
            "change-create-theme-market",
            """{"market":"krx","editorName":"사용자 초안"}"""));
        Assert.Equal(ThemeMarket.Krx, marketChange.Market);
        Assert.Equal("사용자 초안", marketChange.EditorName);
        Assert.Equal(string.Empty, Assert.IsType<LegacyChangeCreateThemeMarketIntent>(
            ParseRequired(
                "change-create-theme-market",
                """{"market":"nxt","editorName":""}""")).EditorName);
        Assert.IsType<LegacyBeginCreateExpertCatalogIntent>(ParseRequired(
            "begin-create-expert-catalog",
            "{}"));
        Assert.Null(Assert.IsType<LegacyAddOperatorCatalogStockIntent>(ParseRequired(
            "add-operator-catalog-stock",
            """{"buyAmount":null}""")).BuyAmount);
        Assert.Equal(70_000, Assert.IsType<LegacyAddOperatorCatalogStockIntent>(ParseRequired(
            "add-operator-catalog-stock",
            """{"buyAmount":70000}""")).BuyAmount);
        Assert.Equal("AI 반도체", Assert.IsType<LegacyCheckOperatorCatalogThemeNameIntent>(
            ParseRequired(
                "check-operator-catalog-theme-name",
                """{"name":"AI 반도체"}""")).Name);
        Assert.Equal("김전문", Assert.IsType<LegacySaveOperatorCatalogIntent>(ParseRequired(
            "save-operator-catalog",
            """{"name":"김전문"}""")).Name);
        Assert.Equal(string.Empty, Assert.IsType<LegacySaveOperatorCatalogIntent>(ParseRequired(
            "save-operator-catalog",
            """{"name":""}""")).Name);
        Assert.False(Parse(
            """
            {"type":"delete-operator-catalog","payload":{}}
            """,
            out _));

        Assert.False(Parse(
            """
            {"type":"save-operator-catalog","payload":{"name":"김전문","expertCode":"0001"}}
            """,
            out _));
        Assert.False(Parse(
            """
            {"type":"check-operator-catalog-theme-name","payload":{"name":"AI 반도체","market":"krx"}}
            """,
            out _));
        Assert.False(Parse(
            """
            {"type":"check-operator-catalog-theme-name","payload":{"name":" AI 반도체"}}
            """,
            out _));
        Assert.False(Parse(
            """
            {"type":"add-operator-catalog-stock","payload":{"buyAmount":1.5}}
            """,
            out _));

        foreach (var payload in new[]
                 {
                     """{"market":"future","editorName":"초안"}""",
                     """{"market":"nxt","editorName":" 초안"}""",
                     """{"market":"nxt","editorName":"bad\nvalue"}""",
                     """{"market":"nxt"}""",
                     """{"market":"nxt","editorName":"초안","themeCode":"00000001"}"""
                 })
        {
            Assert.False(Parse(
                "{\"type\":\"change-create-theme-market\",\"payload\":" + payload + "}",
                out _));
        }

        Assert.False(Parse(
            JsonSerializer.Serialize(new
            {
                type = "change-create-theme-market",
                payload = new
                {
                    market = "krx",
                    editorName = new string('A', 129)
                }
            }),
            out _));
    }

    [Fact]
    public void Confirmation_commands_accept_only_an_empty_payload_and_never_accept_trusted_identity()
    {
        Assert.IsType<LegacyConfirmNativeDialogIntent>(ParseRequired(
            "confirm-native-dialog",
            "{}"));
        Assert.IsType<LegacyCancelNativeDialogIntent>(ParseRequired(
            "cancel-native-dialog",
            "{}"));
        Assert.False(Parse(
            """{"type":"confirm-operator-catalog-confirmation","payload":{}}""",
            out _));
        Assert.False(Parse(
            """{"type":"cancel-operator-catalog-confirmation","payload":{}}""",
            out _));

        foreach (var type in new[]
                 {
                     "confirm-native-dialog",
                     "cancel-native-dialog"
                 })
        {
            foreach (var payload in new[]
                     {
                         """{"themeCode":"00000001"}""",
                         """{"expertCode":"0001"}""",
                         """{"rowId":"catalog-draft-0123456789abcdef"}""",
                         """{"comparisonPairId":"comparison-pair-00000001"}""",
                         """{"expectedIdentity":{"code":"00000001"}}"""
                     })
            {
                Assert.False(Parse(
                    "{\"type\":\"" + type + "\",\"payload\":" + payload + "}",
                    out _));
            }
        }
    }

    [Fact]
    public void Confirmation_dialog_projection_exposes_caption_and_kind_without_trusted_identity()
    {
        var snapshot = new LegacyOperatorSnapshot(
            7,
            string.Empty,
            [],
            null,
            [],
            [],
            new LegacyDialogState(
                "선택한 테마를 삭제하겠습니까?",
                "MmoneyCoder",
                IsConfirmation: true),
            string.Empty,
            LegacyOperatorStatusKind.Neutral);

        using var document = JsonDocument.Parse(LegacyBridgeProtocol.SerializeState(snapshot));
        var dialog = document.RootElement.GetProperty("payload").GetProperty("dialog");

        Assert.Equal("선택한 테마를 삭제하겠습니까?", dialog.GetProperty("message").GetString());
        Assert.Equal("MmoneyCoder", dialog.GetProperty("caption").GetString());
        Assert.True(dialog.GetProperty("isConfirmation").GetBoolean());
        Assert.Equal(3, dialog.EnumerateObject().Count());
        foreach (var forbidden in new[]
                 {
                     "themeCode", "expertCode", "stockCode", "rowId", "expectedIdentity"
                 })
        {
            Assert.False(dialog.TryGetProperty(forbidden, out _));
        }
    }

    [Fact]
    public void Projection_exposes_display_data_and_opaque_ids_without_DB_identity_authority()
    {
        var catalog = new LegacyOperatorCatalogWorkflowSnapshot(
            4,
            true,
            LegacyOperatorCatalogEntity.Theme,
            LegacyOperatorCatalogMode.Edit,
            "AI",
            ThemeSession.PreMarket,
            ThemeMarket.Krx,
            [
                new LegacyOperatorCatalogResultView(
                    "catalog-result-0123456789abcdef",
                    "00000001",
                    "AI 반도체",
                    "KRX / Oracle",
                    true)
            ],
            false,
            "catalog-result-0123456789abcdef",
            "00000001",
            "AI 반도체",
            LegacyThemeNameAvailability.Duplicate,
            "AI 반도체",
            [
                new LegacyOperatorCatalogDraftRowView(
                    "catalog-draft-0123456789abcdef",
                    "삼성전자",
                    "KOSPI",
                    null,
                    0)
            ],
            "catalog-draft-0123456789abcdef",
            "삼성",
            [
                new LegacyOperatorCatalogStockResultView(
                    "catalog-stock-0123456789abcdef",
                    "삼성전자",
                    "KOSPI",
                    true,
                    true)
            ],
            false,
            "catalog-stock-0123456789abcdef",
            true,
            false,
            false,
            true,
            true,
            true,
            true,
            "편집 중",
            LegacyOperatorCatalogStatusKind.Information);
        var snapshot = new LegacyOperatorSnapshot(
            1,
            string.Empty,
            [],
            null,
            [],
            [],
            null,
            string.Empty,
            LegacyOperatorStatusKind.Neutral,
            OperatorCatalog: catalog);

        var json = LegacyBridgeProtocol.SerializeState(snapshot);

        Assert.Contains("catalog-result-0123456789abcdef", json, StringComparison.Ordinal);
        Assert.Contains("codeLabel", json, StringComparison.Ordinal);
        Assert.Contains("themeNameAvailability", json, StringComparison.Ordinal);
        Assert.Contains("checkedThemeName", json, StringComparison.Ordinal);
        Assert.Contains("canCheckThemeName", json, StringComparison.Ordinal);
        Assert.Contains("activeDraftRowId", json, StringComparison.Ordinal);
        Assert.Contains("duplicate", json, StringComparison.OrdinalIgnoreCase);
        Assert.DoesNotContain("themeCode", json, StringComparison.Ordinal);
        Assert.DoesNotContain("expertCode", json, StringComparison.Ordinal);
        Assert.DoesNotContain("stockCode", json, StringComparison.Ordinal);
        Assert.DoesNotContain("itemCode", json, StringComparison.Ordinal);
        Assert.DoesNotContain("expectedIdentity", json, StringComparison.Ordinal);
    }

    private static LegacyUiIntent ParseRequired(string type, string payload)
    {
        var json = "{\"type\":\"" + type + "\",\"payload\":" + payload + "}";
        Assert.True(Parse(json, out var intent));
        return Assert.IsAssignableFrom<LegacyUiIntent>(intent);
    }

    private static bool Parse(string json, out LegacyUiIntent? intent) =>
        LegacyBridgeProtocol.TryParseIntent(json, out intent, out _);
}
