using System.Text.RegularExpressions;

namespace MBN_STOCK_WEBVIEW.LegacyWeb.Tests;

public sealed class LegacyOperatorSettingsWebContractTests
{
    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 Styles = File.ReadAllText(Path.Combine(WebRoot, "styles.css"));
    private static readonly string App = File.ReadAllText(Path.Combine(WebRoot, "app.js"));
    private static readonly string SettingsHost = File.ReadAllText(Path.Combine(
        RepositoryRoot,
        "src",
        "MBN_STOCK_WEBVIEW.LegacyParityApp",
        "MainWindow.Settings.cs"));

    [Fact]
    public void SettingsScreenKeepsThreeNativeFolderPickersWithConciseFeedback()
    {
        var expectedKinds = new[] { "design", "resource", "background" };
        var actualKinds = Regex.Matches(Markup, "data-operator-folder=\"([^\"]+)\"")
            .Select(match => match.Groups[1].Value)
            .ToArray();

        Assert.Equal(expectedKinds, actualKinds);
        foreach (var kind in expectedKinds)
        {
            Assert.Contains($"id=\"operator-{kind}-folder-name\"", Markup,
                StringComparison.Ordinal);
            Assert.Contains($"id=\"operator-{kind}-folder-status\"", Markup,
                StringComparison.Ordinal);
            Assert.Contains($"id=\"operator-{kind}-folder-select\"", Markup,
                StringComparison.Ordinal);
            Assert.Contains($"id=\"operator-{kind}-folder-reset\"", Markup,
                StringComparison.Ordinal);
            Assert.Matches(
                $"id=\"operator-{kind}-folder-source\"[^>]*hidden",
                Markup);
        }

        Assert.Matches(
            "id=\"operator-settings-message\"[^>]*role=\"status\"[^>]*aria-live=\"polite\"",
            Markup);
        Assert.Matches(
            "id=\"operator-settings-restart\"[^>]*role=\"status\"[^>]*aria-live=\"polite\"",
            Markup);
        Assert.Contains("<h3>폴더</h3>", Markup, StringComparison.Ordinal);
        Assert.Contains("<h3>메뉴</h3>", Markup, StringComparison.Ordinal);
        Assert.Contains("<h3>상태</h3>", Markup, StringComparison.Ordinal);
        Assert.Contains("재시작하면 적용됩니다.", Markup,
            StringComparison.Ordinal);
        Assert.Contains("id=\"operator-design-folder-name\">앱 기본값", Markup,
            StringComparison.Ordinal);
        Assert.Contains("id=\"operator-resource-folder-name\">앱 기본값", Markup,
            StringComparison.Ordinal);
        Assert.Contains("id=\"operator-background-folder-name\">자동", Markup,
            StringComparison.Ordinal);
        Assert.Contains("id=\"operator-navigation-expanded\"", Markup,
            StringComparison.Ordinal);
        Assert.Contains("메뉴 펼치기", Markup, StringComparison.Ordinal);
        Assert.Contains("settings-status-badge.valid", Styles, StringComparison.Ordinal);
        Assert.Contains("settings-status-badge.invalid", Styles, StringComparison.Ordinal);

        Assert.DoesNotContain("settings-intro", Markup, StringComparison.Ordinal);
        Assert.DoesNotContain("이 PC에서 사용할 디자인·설정·배경 폴더", Markup,
            StringComparison.Ordinal);
        Assert.DoesNotContain("경로를 직접 입력하지 않고", Markup,
            StringComparison.Ordinal);
        Assert.DoesNotContain("송출 장면(.t2s)과 장면별 디자인 자산", Markup,
            StringComparison.Ordinal);
        Assert.DoesNotContain("핵심 자산 확인됨", Markup, StringComparison.Ordinal);
    }

    [Fact]
    public void SettingsDispatchesOnlyClosedFolderKindsAndNeverWebSuppliedPaths()
    {
        var dispatch = Slice(
            App,
            "Object.keys(operatorFolderControls).forEach(function (kind)",
            "searchButton.addEventListener");

        Assert.Contains("send(\"choose-operator-folder\", { kind: kind })", dispatch,
            StringComparison.Ordinal);
        Assert.Contains("send(\"reset-operator-folder\", { kind: kind })", dispatch,
            StringComparison.Ordinal);
        Assert.Contains(
            "send(\"set-operator-navigation-expanded\", { expanded: expanded })",
            dispatch,
            StringComparison.Ordinal);
        Assert.DoesNotContain("path", dispatch, StringComparison.OrdinalIgnoreCase);
        Assert.DoesNotContain("displayName", dispatch, StringComparison.Ordinal);
    }

    [Fact]
    public void AppearanceSettingsExposeThreeConciseClosedChoices()
    {
        var appearance = Slice(
            Markup,
            "<article class=\"settings-card settings-appearance-card\">",
            "<article class=\"settings-card\">");

        Assert.Contains("<h3>화면</h3>", appearance, StringComparison.Ordinal);
        Assert.Contains("id=\"operator-color-theme\"", appearance, StringComparison.Ordinal);
        Assert.Contains("value=\"system\">자동", appearance, StringComparison.Ordinal);
        Assert.Contains("value=\"light\">라이트", appearance, StringComparison.Ordinal);
        Assert.Contains("value=\"dark\">다크", appearance, StringComparison.Ordinal);
        Assert.Contains("id=\"operator-view-mode\"", appearance, StringComparison.Ordinal);
        Assert.Contains("<strong>그래픽 목록</strong>", appearance, StringComparison.Ordinal);
        Assert.Contains("value=\"automatic\">자동", appearance, StringComparison.Ordinal);
        Assert.Contains("value=\"compact\">간결한 목록", appearance, StringComparison.Ordinal);
        Assert.Contains("value=\"cards\">비주얼 카드", appearance, StringComparison.Ordinal);
        Assert.Contains("id=\"operator-start-workspace\"", appearance,
            StringComparison.Ordinal);
        Assert.Contains("value=\"stockCut\">종목·컷", appearance, StringComparison.Ordinal);
        Assert.Contains("value=\"lastWorkspace\">마지막 화면", appearance,
            StringComparison.Ordinal);
        Assert.DoesNotContain("<p", appearance, StringComparison.OrdinalIgnoreCase);
        Assert.DoesNotContain("<small", appearance, StringComparison.OrdinalIgnoreCase);
    }

    [Fact]
    public void AppearanceSettingsApplyClosedValuesAndDispatchOneCompleteSnapshot()
    {
        var render = Slice(App, "function normalizeOperatorAppearanceValue", "function render(state)");
        var dispatch = Slice(
            App,
            "Object.keys(operatorFolderControls).forEach(function (kind)",
            "document.addEventListener(\"input\"");

        Assert.Contains("definition.values.includes(value)", render, StringComparison.Ordinal);
        Assert.Contains("element.dataset.colorTheme = appearance.colorTheme", render,
            StringComparison.Ordinal);
        Assert.Contains("element.dataset.viewMode = appearance.viewMode", render,
            StringComparison.Ordinal);
        Assert.Contains("element.dataset.startWorkspace = appearance.startWorkspace", render,
            StringComparison.Ordinal);
        Assert.Contains("operatorAppearanceControls[key].element.disabled = isBusy", render,
            StringComparison.Ordinal);
        Assert.Contains("applyStartPreference(appearance.startWorkspace)", render,
            StringComparison.Ordinal);
        Assert.DoesNotContain("isInitialSettingsSnapshot", render, StringComparison.Ordinal);
        Assert.Contains("send(\"set-operator-appearance\", {", dispatch,
            StringComparison.Ordinal);
        Assert.Contains("colorTheme: appearance.colorTheme", dispatch, StringComparison.Ordinal);
        Assert.Contains("viewMode: appearance.viewMode", dispatch, StringComparison.Ordinal);
        Assert.Contains("startWorkspace: appearance.startWorkspace", dispatch,
            StringComparison.Ordinal);
        Assert.Contains("syncOperatorAppearanceControls(operatorAppearanceSnapshot)", dispatch,
            StringComparison.Ordinal);
    }

    [Fact]
    public void RepeatedSettingsStateKeepsStaticControlsAndAppliesNavigationByRevision()
    {
        var folderRender = Slice(App, "function renderOperatorFolder", "function render(state)");
        var settingsRender = Slice(
            App,
            "function renderOperatorSettings",
            "function render(state)");

        Assert.DoesNotContain("innerHTML", folderRender, StringComparison.Ordinal);
        Assert.DoesNotContain("replaceChildren", folderRender, StringComparison.Ordinal);
        Assert.Contains("operatorSettingsRevisionInitialized", settingsRender,
            StringComparison.Ordinal);
        Assert.Contains("revision !== operatorSettingsRenderRevision", settingsRender,
            StringComparison.Ordinal);
        Assert.Matches(
            new Regex(
                @"if \(revisionChanged\) \{[\s\S]*?LegacyWorkspaceNavigation\.setExpanded\(expanded\);[\s\S]*?operatorSettingsRenderRevision = revision;",
                RegexOptions.CultureInvariant),
            settingsRender);
        Assert.Single(
            Regex.Matches(settingsRender, "LegacyWorkspaceNavigation.setExpanded").Cast<Match>());
        Assert.Contains("pendingOperatorSettingsFocusId", settingsRender,
            StringComparison.Ordinal);
        Assert.Contains("target.focus({ preventScroll: true });", settingsRender,
            StringComparison.Ordinal);
    }

    [Fact]
    public void SettingsKeepsSafeDiagnosticsWithoutProtectedValueExplanations()
    {
        foreach (var id in new[]
                 {
                     "operator-settings-app-version",
                     "operator-settings-playout-mode",
                     "operator-settings-playout-connection",
                     "operator-settings-oracle-state",
                     "operator-settings-maria-state",
                     "operator-settings-db-monitor"
                 })
        {
            Assert.Contains($"id=\"{id}\"", Markup, StringComparison.Ordinal);
        }

        Assert.Contains("id=\"operator-navigation-expanded\"", Markup,
            StringComparison.Ordinal);
        Assert.Contains("databasePollingSeconds", App, StringComparison.Ordinal);
        Assert.Contains("LegacyOperatorFolderValidator.TryValidate", SettingsHost,
            StringComparison.Ordinal);
        Assert.DoesNotContain("CanReadSelectedDatabaseProfile", SettingsHost,
            StringComparison.Ordinal);
        Assert.DoesNotContain("LegacyIniDatabaseOptionsLoader", SettingsHost,
            StringComparison.Ordinal);
        Assert.Contains("ResolveDatabaseHealthLabel", SettingsHost,
            StringComparison.Ordinal);

        Assert.Contains("\"저장됨\"", SettingsHost, StringComparison.Ordinal);
        Assert.Contains("\"저장됨 · 재시작 필요\"", SettingsHost,
            StringComparison.Ordinal);
        Assert.Contains("\"초기화됨 · 재시작 필요\"", SettingsHost,
            StringComparison.Ordinal);
        Assert.Contains("\"사용 가능\"", SettingsHost, StringComparison.Ordinal);
        Assert.DoesNotContain("외부 영상", SettingsHost, StringComparison.Ordinal);

        Assert.DoesNotContain("보호되는 운영 설정", Markup, StringComparison.Ordinal);
        Assert.DoesNotContain("DB 자격 증명", Markup, StringComparison.Ordinal);
        Assert.DoesNotContain("Tornado2 송출 대상", Markup, StringComparison.Ordinal);
        Assert.DoesNotContain("Live/DryRun 권한", Markup, StringComparison.Ordinal);
        Assert.DoesNotContain("장면 허용 목록", Markup, StringComparison.Ordinal);
    }

    private static string Slice(string source, string startMarker, string endMarker)
    {
        var start = source.IndexOf(startMarker, StringComparison.Ordinal);
        Assert.True(start >= 0, $"Missing start marker: {startMarker}");
        var end = source.IndexOf(endMarker, start, StringComparison.Ordinal);
        Assert.True(end > start, $"Missing end marker: {endMarker}");
        return source[start..end];
    }

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

            directory = directory.Parent;
        }

        throw new DirectoryNotFoundException("Repository root could not be located.");
    }
}
