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

namespace MBN_STOCK_WEBVIEW.LegacyBridge.Tests;

public sealed class LegacyOverseasBridgeTests
{
    [Theory]
    [InlineData(
        "{\"type\":\"select-overseas-fixed-index\",\"payload\":{\"targetId\":\"nasdaq\"}}",
        typeof(LegacySelectOverseasFixedIndexIntent))]
    [InlineData(
        "{\"type\":\"search-overseas-industries\",\"payload\":{\"query\":\"반도체\"}}",
        typeof(LegacySearchOverseasIndustriesIntent))]
    [InlineData(
        "{\"type\":\"search-overseas-stocks\",\"payload\":{\"query\":\"Apple\"}}",
        typeof(LegacySearchOverseasStocksIntent))]
    [InlineData(
        "{\"type\":\"select-overseas-industry\",\"payload\":{\"selectionId\":\"overseas-industry-00000001-0001\"}}",
        typeof(LegacySelectOverseasIndustryIntent))]
    [InlineData(
        "{\"type\":\"select-overseas-stock\",\"payload\":{\"selectionId\":\"overseas-stock-00000001-0001\"}}",
        typeof(LegacySelectOverseasStockIntent))]
    [InlineData(
        "{\"type\":\"activate-overseas-action\",\"payload\":{\"actionId\":\"stock-candle-20d\"}}",
        typeof(LegacyActivateOverseasActionIntent))]
    public void ParsesStrictOverseasIntents(string json, Type expectedType)
    {
        Assert.True(LegacyBridgeProtocol.TryParseIntent(json, out var intent, out var error));
        Assert.Null(error);
        Assert.IsType(expectedType, intent);
    }

    [Theory]
    [InlineData("{\"type\":\"select-overseas-fixed-index\",\"payload\":{\"targetId\":\"not-registered\"}}")]
    [InlineData("{\"type\":\"activate-overseas-action\",\"payload\":{\"actionId\":\"8061\"}}")]
    [InlineData("{\"type\":\"select-overseas-stock\",\"payload\":{\"selectionId\":\"../../stock\"}}")]
    [InlineData("{\"type\":\"search-overseas-industries\",\"payload\":{\"query\":\"bad\\u0001query\"}}")]
    [InlineData("{\"type\":\"search-overseas-stocks\",\"payload\":{\"query\":\"Apple\",\"symbol\":\"NAS@AAPL\"}}")]
    [InlineData("{\"type\":\"activate-overseas-action\",\"payload\":{\"actionId\":12}}")]
    public void RejectsUnknownMalformedAndAuthorityBearingOverseasIntents(string json)
    {
        Assert.False(LegacyBridgeProtocol.TryParseIntent(json, out var intent, out var error));
        Assert.Null(intent);
        Assert.NotNull(error);
    }

    [Fact]
    public async Task ProjectionExposesDisplayStateAndOpaqueIdsButNoSceneAuthority()
    {
        var workflow = new LegacyOverseasSelectionWorkflow(
            new StaticIndustryService(),
            new StaticStockService());
        workflow.SelectFixedIndex("nasdaq");
        var industries = await workflow.SearchIndustriesAsync("반도체");
        workflow.SelectIndustry(industries.Industry.Results[0].SelectionId);
        var stocks = await workflow.SearchStocksAsync("Apple");
        workflow.SelectStock(stocks.Stock.Results[0].SelectionId);
        var snapshot = new LegacyOperatorSnapshot(
            7,
            string.Empty,
            [],
            null,
            [],
            [],
            null,
            string.Empty,
            LegacyOperatorStatusKind.Neutral,
            Overseas: workflow.Current);

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

        Assert.Equal(10, overseas.GetProperty("fixedIndexTargets").GetArrayLength());
        Assert.Equal("nasdaq", overseas.GetProperty("selectedFixedIndexId").GetString());
        Assert.Equal("나스닥", overseas.GetProperty("selectedFixedIndexName").GetString());
        Assert.Equal(
            "overseas-industry-00000001-0001",
            overseas.GetProperty("industry").GetProperty("selectedId").GetString());
        Assert.Equal(
            "overseas-stock-00000001-0001",
            overseas.GetProperty("stock").GetProperty("selectedId").GetString());
        Assert.Equal(
            0,
            overseas.GetProperty("stock").GetProperty("isolatedInvalidRowCount").GetInt32());
        Assert.Equal(12, overseas.GetProperty("actions").GetArrayLength());
        Assert.All(
            overseas.GetProperty("actions").EnumerateArray(),
            action => Assert.True(action.GetProperty("isAvailable").GetBoolean()));
        Assert.DoesNotContain("builderKey", json, StringComparison.OrdinalIgnoreCase);
        Assert.DoesNotContain("sceneAlias", json, StringComparison.OrdinalIgnoreCase);
        Assert.DoesNotContain("dataCode", json, StringComparison.OrdinalIgnoreCase);
        Assert.DoesNotContain("nationCode", json, StringComparison.OrdinalIgnoreCase);
        Assert.DoesNotContain("NAS@AAPL", json, StringComparison.Ordinal);
        Assert.DoesNotContain("NAS@SOX", json, StringComparison.Ordinal);
    }

    [Fact]
    public async Task ProjectionKeepsNonUsTwRowOpaqueAndReportsItAvailable()
    {
        var workflow = new LegacyOverseasSelectionWorkflow(
            new StaticIndustryService(),
            new StaticStockService([new WorldStockSearchItem("London Stock", "LNS@LON", "GB")]));
        var searched = await workflow.SearchStocksAsync("London");
        workflow.SelectStock(Assert.Single(searched.Stock.Results).SelectionId);
        var snapshot = new LegacyOperatorSnapshot(
            7, string.Empty, [], null, [], [], null, string.Empty,
            LegacyOperatorStatusKind.Neutral, Overseas: workflow.Current);

        using var document = JsonDocument.Parse(LegacyBridgeProtocol.SerializeState(snapshot));
        var overseas = document.RootElement.GetProperty("payload").GetProperty("overseas");
        var result = Assert.Single(overseas.GetProperty("stock").GetProperty("results").EnumerateArray());
        Assert.Equal("London Stock", result.GetProperty("displayName").GetString());
        Assert.False(result.TryGetProperty("nationCode", out _));
        var action = overseas.GetProperty("actions").EnumerateArray().Single(value =>
            value.GetProperty("actionId").GetString() == "stock-current");
        Assert.True(action.GetProperty("isAvailable").GetBoolean());
        Assert.Equal(JsonValueKind.Null, action.GetProperty("reason").ValueKind);
        Assert.DoesNotContain("LNS@LON", document.RootElement.GetRawText(), StringComparison.Ordinal);
    }

    [Fact]
    public async Task ProjectionKeepsThreeLetterNationOpaqueAndFailsClosed()
    {
        var workflow = new LegacyOverseasSelectionWorkflow(
            new StaticIndustryService(),
            new StaticStockService([new WorldStockSearchItem("Unsupported", "LNS@UNSUPPORTED", "GBR")]));
        var searched = await workflow.SearchStocksAsync("unsupported");
        workflow.SelectStock(Assert.Single(searched.Stock.Results).SelectionId);
        var snapshot = new LegacyOperatorSnapshot(
            7, string.Empty, [], null, [], [], null, string.Empty,
            LegacyOperatorStatusKind.Neutral, Overseas: workflow.Current);

        using var document = JsonDocument.Parse(LegacyBridgeProtocol.SerializeState(snapshot));
        var overseas = document.RootElement.GetProperty("payload").GetProperty("overseas");
        var action = overseas.GetProperty("actions").EnumerateArray().Single(value =>
            value.GetProperty("actionId").GetString() == "stock-current");

        Assert.False(action.GetProperty("isAvailable").GetBoolean());
        Assert.Equal("선택한 해외종목의 국가 코드는 두 글자여야 합니다.",
            action.GetProperty("reason").GetString());
        Assert.DoesNotContain("GBR", document.RootElement.GetRawText(), StringComparison.Ordinal);
        Assert.DoesNotContain("LNS@UNSUPPORTED", document.RootElement.GetRawText(), StringComparison.Ordinal);
    }

    [Fact]
    public async Task ProjectionReportsRowsIsolatedByTheReadBoundary()
    {
        var workflow = new LegacyOverseasSelectionWorkflow(
            new StaticIndustryService(),
            new StaticStockService(isolatedInvalidRowCount: 16));
        await workflow.SearchStocksAsync(string.Empty);
        var snapshot = new LegacyOperatorSnapshot(
            7, string.Empty, [], null, [], [], null, string.Empty,
            LegacyOperatorStatusKind.Neutral, Overseas: workflow.Current);

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

        Assert.Equal(16, stock.GetProperty("isolatedInvalidRowCount").GetInt32());
        Assert.True(stock.GetProperty("isTruncated").GetBoolean());
        Assert.Single(stock.GetProperty("results").EnumerateArray());
    }

    private sealed class StaticIndustryService : IOverseasIndustryIndexSearchService
    {
        public Task<OverseasIndustryIndexSearchResult> SearchAsync(
            string query,
            int maximumResults = LegacyOverseasIndustryIndexSearchService.DefaultMaximumResults,
            CancellationToken cancellationToken = default) =>
            Task.FromResult(new OverseasIndustryIndexSearchResult(
                query,
                DateTimeOffset.Parse("2026-07-15T10:00:00+09:00"),
                [new OverseasIndustryIndexItem("반도체", "US Semiconductor", "NAS@SOX")],
                IsTruncated: false));
    }

    private sealed class StaticStockService(
        IReadOnlyList<WorldStockSearchItem>? items = null,
        int isolatedInvalidRowCount = 0) : IOverseasStockSearchService
    {
        public Task<WorldStockSearchResult> SearchAsync(
            string query,
            int maximumResults = LegacyOverseasStockSearchService.DefaultMaximumResults,
            CancellationToken cancellationToken = default) =>
            Task.FromResult(new WorldStockSearchResult(
                query,
                DateTimeOffset.Parse("2026-07-15T10:00:00+09:00"),
                items ?? [new WorldStockSearchItem("Apple", "NAS@AAPL", "US")],
                IsTruncated: isolatedInvalidRowCount > 0,
                IsolatedInvalidRowCount: isolatedInvalidRowCount));
    }
}
