namespace MBN_STOCK_WEBVIEW.LegacyWeb.Tests;

public sealed class LegacyPlayoutWebContractTests
{
    private static readonly string RepositoryRoot = FindRepositoryRoot();
    private static readonly string WebRoot = Path.Combine(
        RepositoryRoot,
        "src",
        "MBN_STOCK_WEBVIEW.LegacyParityApp",
        "Web");
    private static readonly string Markup = File.ReadAllText(
        Path.Combine(WebRoot, "index.html"));
    private static readonly string Script = File.ReadAllText(
        Path.Combine(WebRoot, "playout-ui.js"));

    [Fact]
    public void ShellExposesBackgroundControlsButKeepsOriginalHiddenFadeStateInert()
    {
        foreach (var id in new[]
                 {
                     "playout-connection-state",
                     "playout-background-none",
                     "playout-background-file",
                     "playout-background-name",
                     "playout-fade-duration",
                     "playout-prepare",
                     "playout-take-in",
                     "playout-next",
                     "playout-take-out",
                     "playout-status"
                 })
        {
            Assert.Contains($"id=\"{id}\"", Markup, StringComparison.Ordinal);
        }

        Assert.Contains("PREPARE", Markup, StringComparison.Ordinal);
        Assert.Contains("TAKE IN", Markup, StringComparison.Ordinal);
        Assert.Contains("NEXT", Markup, StringComparison.Ordinal);
        Assert.Contains("TAKE OUT", Markup, StringComparison.Ordinal);
        var fadeMarkup = ExtractElement(Markup, "playout-fade-duration", "</select>");
        Assert.Equal(20, CountOccurrences(fadeMarkup, "<option value="));
        Assert.Contains(
            "class=\"fade-control\" for=\"playout-fade-duration\" hidden",
            Markup,
            StringComparison.Ordinal);
        Assert.Contains("hidden aria-hidden=\"true\" tabindex=\"-1\"", fadeMarkup,
            StringComparison.Ordinal);
        for (var index = 0; index < 20; index++)
        {
            Assert.Contains(
                $"<option value=\"{index}\">{index + 1}</option>",
                fadeMarkup,
                StringComparison.Ordinal);
        }
        Assert.Contains("<script src=\"playout-ui.js\"></script>", Markup,
            StringComparison.Ordinal);
    }

    [Fact]
    public void PlayoutWebLayerSendsCommandIntentOnly()
    {
        foreach (var intent in new[]
                 {
                     "prepare-playout",
                     "take-in",
                     "next-playout",
                     "take-out",
                     "choose-background",
                     "toggle-background"
                 })
        {
            Assert.Contains($"\"{intent}\"", Script, StringComparison.Ordinal);
        }

        Assert.DoesNotContain("fetch(", Script, StringComparison.Ordinal);
        Assert.DoesNotContain("localStorage", Script, StringComparison.Ordinal);
        Assert.DoesNotContain("sessionStorage", Script, StringComparison.Ordinal);
        Assert.DoesNotContain("filePath", Script, StringComparison.Ordinal);
        Assert.DoesNotContain("sceneCode", Script, StringComparison.Ordinal);
        Assert.Contains("post(\"prepare-playout\", {})", Script,
            StringComparison.Ordinal);
        Assert.Contains("post(\"take-in\", {})", Script, StringComparison.Ordinal);
        Assert.Contains("post(\"next-playout\", {})", Script, StringComparison.Ordinal);
        Assert.Contains("post(\"take-out\", {})", Script, StringComparison.Ordinal);
        Assert.DoesNotContain("post(\"set-fade-duration\"", Script,
            StringComparison.Ordinal);
        Assert.DoesNotContain("fadeDuration.addEventListener", Script,
            StringComparison.Ordinal);
        Assert.Contains("fadeDuration.disabled = true;", Script,
            StringComparison.Ordinal);
        Assert.Contains("fadeDuration.value = String(playout.fadeDuration);", Script,
            StringComparison.Ordinal);
    }

    [Fact]
    public void OriginalGlobalShortcutsAreIsolatedFromOpenDialogs()
    {
        Assert.Contains("hasOpenDialog()", Script, StringComparison.Ordinal);
        Assert.Contains("event.key === \"F2\"", Script, StringComparison.Ordinal);
        Assert.Contains("event.key === \"F3\"", Script, StringComparison.Ordinal);
        Assert.Contains("event.key === \"F8\"", Script, StringComparison.Ordinal);
        Assert.Contains("event.key === \"Escape\"", Script, StringComparison.Ordinal);
        Assert.Contains("if (event.defaultPrevented) return;", Script,
            StringComparison.Ordinal);
        Assert.Contains("if (hasOpenDialog())", Script,
            StringComparison.Ordinal);
        Assert.Contains("if (legacyShortcut) event.preventDefault();", Script,
            StringComparison.Ordinal);
    }

    [Fact]
    public void ButtonsFollowNativePhaseCallbackAndOutcomeState()
    {
        Assert.Contains("playout.outcomeUnknown !== true", Script,
            StringComparison.Ordinal);
        Assert.Contains("const canPrepareToggle = playout.phase === \"idle\"", Script,
            StringComparison.Ordinal);
        Assert.Contains("canTakeInFromState(playout, hasPlaylist)", Script,
            StringComparison.Ordinal);
        Assert.Contains("playout.phase === \"idle\" || playout.phase === \"program\"", Script,
            StringComparison.Ordinal);
        Assert.Contains("canAdvanceFromState(playout)", Script,
            StringComparison.Ordinal);
        Assert.Contains("playout.isPlayCompletionPending === true", Script,
            StringComparison.Ordinal);
        Assert.Contains("playout.isTakeOutCompletionPending !== true", Script,
            StringComparison.Ordinal);
        Assert.Contains("playout.nextKind === \"none\"", Script,
            StringComparison.Ordinal);
        Assert.Contains("playout.nextKind === \"endOfPlaylist\"", Script,
            StringComparison.Ordinal);
        Assert.Contains("takeOut.disabled = busy || !commandAvailable;", Script,
            StringComparison.Ordinal);
        Assert.Contains("마지막 항목", Script, StringComparison.Ordinal);
    }

    [Fact]
    public void StatusStateUsesFailSafePriorityAndIsReassignedOnEveryRender()
    {
        var resolver = ExtractFunction(Script, "function statusState(playout, busy)");

        foreach (var state in new[]
                 {
                     "outcome-unknown",
                     "busy",
                     "error",
                     "program",
                     "prepared",
                     "idle"
                 })
        {
            Assert.Contains($"\"{state}\"", resolver, StringComparison.Ordinal);
        }

        Assert.True(
            resolver.IndexOf("playout.outcomeUnknown === true", StringComparison.Ordinal) <
            resolver.IndexOf("if (busy)", StringComparison.Ordinal),
            "OutcomeUnknown must remain visually dominant while a command is busy.");
        Assert.True(
            resolver.IndexOf("if (busy)", StringComparison.Ordinal) <
            resolver.IndexOf("playout.mode === \"disabled\"", StringComparison.Ordinal),
            "Busy must win over an unavailable engine after OutcomeUnknown is excluded.");
        Assert.True(
            resolver.IndexOf("playout.mode === \"disabled\"", StringComparison.Ordinal) <
            resolver.IndexOf("playout.phase === \"program\"", StringComparison.Ordinal),
            "Engine errors must win over ordinary phase styling.");
        Assert.Contains(
            "status.dataset.state = statusState(playout, busy);",
            Script,
            StringComparison.Ordinal);
        Assert.Contains(
            "status.dataset.state = \"error\";",
            Script,
            StringComparison.Ordinal);
    }

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

        return count;
    }

    private static string ExtractElement(string source, string id, string closingTag)
    {
        var start = source.IndexOf($"id=\"{id}\"", StringComparison.Ordinal);
        Assert.True(start >= 0, $"Element '{id}' was not found.");
        var end = source.IndexOf(closingTag, start, StringComparison.Ordinal);
        Assert.True(end >= 0, $"Closing tag for '{id}' was not found.");
        return source[start..(end + closingTag.Length)];
    }

    private static string ExtractFunction(string source, string signature)
    {
        var start = source.IndexOf(signature, StringComparison.Ordinal);
        Assert.True(start >= 0, $"Function '{signature}' was not found.");
        var bodyStart = source.IndexOf('{', start);
        Assert.True(bodyStart >= 0, $"Function '{signature}' has no body.");
        var depth = 0;
        for (var index = bodyStart; index < source.Length; index++)
        {
            if (source[index] == '{')
            {
                depth++;
            }
            else if (source[index] == '}' && --depth == 0)
            {
                return source[start..(index + 1)];
            }
        }

        throw new InvalidDataException($"Function '{signature}' is incomplete.");
    }

    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.");
    }
}
