namespace MBN_STOCK_WEBVIEW.LegacyWeb.Tests;

public sealed class LegacyGateAAppIntegrationContractTests
{
    private static readonly string RepositoryRoot = FindRepositoryRoot();
    private static readonly string AppRoot = Path.Combine(
        RepositoryRoot,
        "src",
        "MBN_STOCK_WEBVIEW.LegacyParityApp");
    private static readonly string Window = Read(AppRoot, "MainWindow.xaml.cs");
    private static readonly string Playout = Read(AppRoot, "MainWindow.Playout.cs");
    private static readonly string IntentParser = Read(
        RepositoryRoot,
        "src",
        "MBN_STOCK_WEBVIEW.LegacyBridge",
        "LegacyPlayoutUiIntents.cs");
    private static readonly string Factory = Read(
        RepositoryRoot,
        "src",
        "MBN_STOCK_WEBVIEW.Playout",
        "PlayoutEngineFactory.cs");
    private static readonly string LaunchAuthorization = Read(
        RepositoryRoot,
        "src",
        "MBN_STOCK_WEBVIEW.Playout",
        "Safety",
        "PlayoutLaunchAuthorization.cs");
    private static readonly string InfrastructureRoot = Path.Combine(
        RepositoryRoot,
        "src",
        "MBN_STOCK_WEBVIEW.Infrastructure",
        "Execution");

    [Fact]
    public void Launch_authorization_is_captured_before_WebView_creation_and_forwarded_unchanged()
    {
        var constructor = Slice(
            Window,
            "    public MainWindow()",
            "    private async void OnRootLoaded(");
        var capture = RequiredIndex(
            constructor,
            "PlayoutLaunchAuthorization.CaptureFromEnvironment()");
        var initializeComponent = RequiredIndex(constructor, "InitializeComponent();");

        Assert.True(capture < initializeComponent);
        Assert.Contains(
            "_playoutLaunchAuthorization =\n" +
            "            PlayoutLaunchAuthorization.CaptureFromEnvironment();",
            Normalize(constructor),
            StringComparison.Ordinal);

        var initialization = Normalize(Slice(
            Playout,
            "    private void InitializePlayoutRuntime()",
            "    private async Task ConnectPlayoutAsync("));
        Assert.Contains(
            "_playoutEngine = PlayoutEngineFactory.Create(\n" +
            "                _playoutOptions,\n" +
            "                _playoutLaunchAuthorization);",
            initialization,
            StringComparison.Ordinal);

        var factoryCreate = Slice(
            Normalize(Factory),
            "    public static IPlayoutEngine Create(\n" +
            "        PlayoutOptions options,\n" +
            "        PlayoutLaunchAuthorization launchAuthorization)",
            "    internal static void ValidateGateAOptions(");
        Assert.Contains("dispatcher,\n            launchAuthorization,", factoryCreate,
            StringComparison.Ordinal);
        Assert.Contains("launchAuthorization: launchAuthorization", factoryCreate,
            StringComparison.Ordinal);
    }

    [Fact]
    public void Gate_A_has_a_dedicated_capability_intent_and_exact_Samsung_5001_prepare_handler()
    {
        Assert.Contains(
            "public sealed record LegacyGateAPrepareIntent(string Capability)",
            IntentParser,
            StringComparison.Ordinal);
        Assert.Contains("\"gate-a-prepare\" => ParseGateAPrepare(payload)", IntentParser,
            StringComparison.Ordinal);
        var parser = Slice(
            IntentParser,
            "    private static LegacyUiIntent? ParseGateAPrepare(JsonElement payload)",
            "    private static LegacyUiIntent? ParseFadeDuration(JsonElement payload)");
        Assert.Contains("payload.EnumerateObject().Count() != 1", parser,
            StringComparison.Ordinal);
        Assert.Contains("capability.Length != 64", parser, StringComparison.Ordinal);
        Assert.Contains("not (>= 'A' and <= 'F')", parser, StringComparison.Ordinal);

        var dispatch = Normalize(Slice(
            Window,
            "    private async Task<LegacyOperatorSnapshot> HandleIntentAsync(",
            "    private async Task<LegacyOperatorSnapshot> RefreshNamedPlaylistsForDispatchAsync("));
        Assert.Contains(
            "LegacyGateAPrepareIntent prepare =>\n" +
            "                await ExecuteGateAPrepareAsync(\n" +
            "                    prepare.Capability,",
            dispatch,
            StringComparison.Ordinal);

        var handler = Slice(
            Playout,
            "    private async Task<LegacyOperatorSnapshot> ExecuteGateAPrepareAsync(",
            "    private bool TryCreateExactGateAPrepareRequest(");
        Assert.Contains("TryClaimGateAPrepare(capability)", handler,
            StringComparison.Ordinal);
        Assert.Contains("TryCreateExactGateAPrepareRequest(out var request)", handler,
            StringComparison.Ordinal);
        Assert.Contains("gateAPrepareClaimed: true", handler, StringComparison.Ordinal);
        Assert.Contains("gateAPrepareRequest: request", handler, StringComparison.Ordinal);
        Assert.Contains("CompleteGateAPrepare()", handler, StringComparison.Ordinal);

        var exact = Slice(
            Playout,
            "    private bool TryCreateExactGateAPrepareRequest(",
            "    private async Task<LegacyOperatorSnapshot> SetFadeDurationAsync(");
        foreach (var required in new[]
                 {
                     "snapshot.Playlist.Count != 1",
                     "composition.FadeDuration != 6",
                     "composition.BackgroundKind != LegacySceneBackgroundKind.None",
                     "row.MarketText, \"코스피\"",
                     "row.StockName, \"삼성전자\"",
                     "row.PageText, \"1/1\"",
                     "row.StockCode, \"005930\"",
                     "_controller.CreatePlayoutRequest(6)",
                     "candidate.SelectedIndexZeroBased != 0",
                     "entry.CutCode, \"5001\"",
                     "entry.FadeDuration != 6",
                     "entry.PageNavigation?.IsEnabled == true",
                     "selection.GroupCode, \"코스피\"",
                     "selection.Subject, \"삼성전자\"",
                     "selection.DataCode, \"005930\""
                 })
        {
            Assert.Contains(required, exact, StringComparison.Ordinal);
        }
    }

    [Fact]
    public void Ordinary_playout_intents_are_rejected_in_Gate_A_before_workflow_access()
    {
        var execute = Slice(
            Playout,
            "    private async Task<LegacyOperatorSnapshot> ExecuteOperatorPlayoutAsync(",
            "    private async Task<LegacyOperatorSnapshot> ExecuteGateAPrepareAsync(");
        var workflowRead = RequiredIndex(execute, "var workflow = _playoutWorkflow;");
        var preWorkflowGuard = execute[..workflowRead];

        Assert.Contains("_playoutLaunchAuthorization.IsGateA", preWorkflowGuard,
            StringComparison.Ordinal);
        Assert.Contains("!gateAPrepareClaimed", preWorkflowGuard, StringComparison.Ordinal);
        Assert.Contains("command != LegacyOperatorPlayoutCommand.Prepare", preWorkflowGuard,
            StringComparison.Ordinal);
        Assert.Contains("gateAPrepareRequest is null", preWorkflowGuard,
            StringComparison.Ordinal);
        Assert.Contains("return _controller.ReportError(", preWorkflowGuard,
            StringComparison.Ordinal);
        Assert.DoesNotContain("_playoutWorkflow", preWorkflowGuard,
            StringComparison.Ordinal);
        Assert.DoesNotContain("CreatePlayoutRequest", preWorkflowGuard,
            StringComparison.Ordinal);
        Assert.DoesNotContain("_playoutCommandGate.WaitAsync", preWorkflowGuard,
            StringComparison.Ordinal);

        var dispatch = Slice(
            Window,
            "    private async Task<LegacyOperatorSnapshot> HandleIntentAsync(",
            "    private async Task<LegacyOperatorSnapshot> RefreshNamedPlaylistsForDispatchAsync(");
        Assert.Contains("LegacyExecutePlayoutIntent playout =>", dispatch,
            StringComparison.Ordinal);
        Assert.Contains("ExecuteOperatorPlayoutAsync(\n                    playout.Command,",
            Normalize(dispatch), StringComparison.Ordinal);
    }

    [Fact]
    public void All_database_mutation_executors_receive_the_same_launch_denial_policy()
    {
        var constructor = Slice(
            Window,
            "    public MainWindow()",
            "    private async void OnRootLoaded(");
        Assert.Contains(
            "new LaunchDatabaseMutationAuthorization(_playoutLaunchAuthorization)",
            constructor,
            StringComparison.Ordinal);
        Assert.True(
            CountOccurrences(
                constructor,
                "mutationAuthorization: databaseMutationAuthorization") == 3,
            "The same launch authorization must be injected into all three DB mutation executors.");

        var adapter = Slice(
            Window,
            "    private sealed class LaunchDatabaseMutationAuthorization",
            "    private void PostState(");
        Assert.Contains(": IDatabaseMutationAuthorization", adapter,
            StringComparison.Ordinal);
        Assert.Contains("return _authorization.AllowsDatabaseWrites;", adapter,
            StringComparison.Ordinal);
        Assert.Contains("_authorization = authorization ??", adapter,
            StringComparison.Ordinal);

        foreach (var executorFile in new[]
                 {
                     "OracleManualFinancialMutationExecutor.cs",
                     "OracleNamedPlaylistMutationExecutor.cs",
                     "OperatorCatalogMutationExecutor.cs"
                 })
        {
            var executor = File.ReadAllText(Path.Combine(InfrastructureRoot, executorFile));
            var demand = RequiredIndex(executor, "DatabaseMutationAuthorizationGuard.Demand(");
            var open = RequiredIndex(executor, "await connection.OpenAsync(");
            Assert.True(demand < open, $"{executorFile} must deny before opening a DB connection.");
            Assert.Contains("_mutationAuthorization", executor, StringComparison.Ordinal);
            Assert.Contains("IDatabaseMutationAuthorization mutationAuthorization", executor,
                StringComparison.Ordinal);
        }
    }

    [Fact]
    public void Gate_A_blocks_persistent_write_intents_before_dispatch()
    {
        var process = Normalize(Slice(
            Window,
            "    private async Task ProcessIntentAsync(LegacyUiIntent intent)",
            "    private static bool IsNamedPlaylistModalIntent("));
        var guard = RequiredIndex(
            process,
            "_playoutLaunchAuthorization.IsGateA &&\n                IsPersistentWriteIntent(intent)");
        var dispatchStarted = RequiredIndex(process, "dispatchStarted = true;");
        var dispatch = RequiredIndex(process, "HandleIntentAsync(intent");
        Assert.True(guard < dispatchStarted);
        Assert.True(guard < dispatch);

        var guardEnd = process.IndexOf("return;", guard, StringComparison.Ordinal);
        Assert.True(guardEnd > guard);
        Assert.Contains("ReportIntentFailure(", process[guard..guardEnd],
            StringComparison.Ordinal);

        var persistent = Slice(
            Playout,
            "    private static bool IsPersistentWriteIntent(LegacyUiIntent intent)",
            "    private static bool IsFixedSectionBatchMutationIntent(");
        foreach (var intent in new[]
                 {
                     "LegacyDeleteUc4SelectedThemeIntent",
                     "LegacyDeleteUc6SelectedExpertIntent",
                     "LegacySaveOperatorCatalogIntent",
                     "LegacyConfirmNativeDialogIntent",
                     "LegacyAddComparisonPairIntent",
                     "LegacySaveManualFinancialIntent",
                     "LegacySaveManualNetSellIntent",
                     "LegacySaveManualViIntent",
                     "LegacyImportManualListsIntent",
                     "LegacyCreateNamedPlaylistIntent",
                     "LegacySaveCurrentNamedPlaylistIntent",
                     "LegacyDeleteSelectedNamedPlaylistIntent"
                 })
        {
            Assert.Contains(intent, persistent, StringComparison.Ordinal);
        }
    }

    [Fact]
    public void Gate_A_blocks_fade_and_background_mutation_before_any_command_gate()
    {
        Assert.Contains("public bool AllowsCompositionMutation => !IsGateA;",
            LaunchAuthorization, StringComparison.Ordinal);

        var fade = Slice(
            Playout,
            "    private async Task<LegacyOperatorSnapshot> SetFadeDurationAsync(",
            "    private LegacyOperatorSnapshot SelectPlaylistBoundaryAndStopRefresh(");
        AssertCompositionMutationGuard(
            fade,
            "Gate A 검증 회차에서는 Fade 설정을 변경할 수 없습니다.");

        var toggle = Slice(
            Playout,
            "    private async Task<LegacyOperatorSnapshot> ToggleBackgroundAsync(",
            "    private async Task<LegacyOperatorSnapshot> ChooseBackgroundAsync(");
        AssertCompositionMutationGuard(
            toggle,
            "Gate A 검증 회차에서는 배경 설정을 변경할 수 없습니다.");

        var choose = Slice(
            Playout,
            "    private async Task<LegacyOperatorSnapshot> ChooseBackgroundAsync(",
            "    private bool CanChangeComposition()");
        AssertCompositionMutationGuard(
            choose,
            "Gate A 검증 회차에서는 배경 파일을 선택할 수 없습니다.");
    }

    private static void AssertCompositionMutationGuard(string method, string message)
    {
        var authorization = RequiredIndex(
            method,
            "if (!_playoutLaunchAuthorization.AllowsCompositionMutation)");
        var commandGate = RequiredIndex(method, "_playoutCommandGate.WaitAsync");
        Assert.True(authorization < commandGate);
        Assert.Contains(message, method[authorization..commandGate],
            StringComparison.Ordinal);
        Assert.Contains("return _controller.ReportError(", method[authorization..commandGate],
            StringComparison.Ordinal);
    }

    private static int CountOccurrences(string source, string marker)
    {
        var count = 0;
        var index = 0;
        while ((index = source.IndexOf(marker, index, StringComparison.Ordinal)) >= 0)
        {
            count++;
            index += marker.Length;
        }

        return count;
    }

    private static string Slice(string source, string startMarker, string endMarker)
    {
        var start = RequiredIndex(source, startMarker);
        var end = RequiredIndex(source, endMarker, start + startMarker.Length);
        return source[start..end];
    }

    private static int RequiredIndex(string source, string marker, int startIndex = 0)
    {
        var index = source.IndexOf(marker, startIndex, StringComparison.Ordinal);
        Assert.True(index >= 0, $"Required source marker was not found: {marker}");
        return index;
    }

    private static string Normalize(string source) =>
        source.Replace("\r\n", "\n", StringComparison.Ordinal);

    private static string Read(params string[] parts) =>
        File.ReadAllText(Path.Combine(parts));

    private static string FindRepositoryRoot()
    {
        var current = new DirectoryInfo(AppContext.BaseDirectory);
        while (current is not null)
        {
            if (File.Exists(Path.Combine(current.FullName, "MBN_STOCK_WEBVIEW.sln")))
            {
                return current.FullName;
            }

            current = current.Parent;
        }

        throw new DirectoryNotFoundException("Repository root was not found.");
    }
}
