using System.Xml.Linq;

namespace MBN_STOCK_WEBVIEW.LegacyWeb.Tests;

public sealed class LegacyFirstRunSetupNativeContractTests
{
    private static readonly string RepositoryRoot = FindRepositoryRoot();
    private static readonly string AppRoot = Path.Combine(
        RepositoryRoot,
        "src",
        "MBN_STOCK_WEBVIEW.LegacyParityApp");

    [Fact]
    public void SourceOnlyLaunchShowsOnlyTheNativeFirstRunWindow()
    {
        var app = File.ReadAllText(Path.Combine(AppRoot, "App.xaml.cs"))
            .ReplaceLineEndings("\n");

        var sourceOnlyStart = app.IndexOf("#if SOURCE_ONLY_RUNTIME",
            StringComparison.Ordinal);
        var fullRuntimeStart = app.IndexOf("#else", sourceOnlyStart,
            StringComparison.Ordinal);
        var runtimeEnd = app.IndexOf("#endif", fullRuntimeStart,
            StringComparison.Ordinal);
        Assert.True(sourceOnlyStart >= 0);
        Assert.True(fullRuntimeStart > sourceOnlyStart);
        Assert.True(runtimeEnd > fullRuntimeStart);

        var sourceOnly = app[sourceOnlyStart..fullRuntimeStart];
        var fullRuntime = app[fullRuntimeStart..runtimeEnd];
        Assert.Contains("new FirstRunSetupWindow()", sourceOnly, StringComparison.Ordinal);
        Assert.DoesNotContain("new MainWindow()", sourceOnly, StringComparison.Ordinal);
        Assert.DoesNotContain(
            "DevelopmentLiveLaunchBootstrap.TryApply(",
            sourceOnly,
            StringComparison.Ordinal);
        Assert.Contains("DevelopmentLiveLaunchBootstrap.TryApply(", fullRuntime,
            StringComparison.Ordinal);
        Assert.Contains("new MainWindow()", fullRuntime, StringComparison.Ordinal);
        Assert.DoesNotContain("new FirstRunSetupWindow()", fullRuntime,
            StringComparison.Ordinal);
        Assert.DoesNotContain(
            "new FirstRunSetupWindow(App.IsDevelopmentLiveLaunch",
            app,
            StringComparison.Ordinal);
    }

    [Fact]
    public void FirstRunUiRequestsOnlyCutsAndTheWholeResFolder()
    {
        var xaml = XDocument.Load(Path.Combine(
            AppRoot,
            "FirstRunSetupWindow.xaml"));
        XNamespace presentation =
            "http://schemas.microsoft.com/winfx/2006/xaml/presentation";
        var buttons = xaml.Descendants(presentation + "Button")
            .Select(element => element.Attribute("Content")?.Value)
            .Where(value => value is not null)
            .ToArray();
        var source = File.ReadAllText(Path.Combine(
            AppRoot,
            "FirstRunSetupWindow.xaml"));

        Assert.Contains("Cuts 폴더 선택", buttons);
        Assert.Contains("Res 폴더 선택", buttons);
        Assert.DoesNotContain("INI 파일 선택", source, StringComparison.Ordinal);
        Assert.DoesNotContain("비밀번호", source, StringComparison.Ordinal);
        Assert.DoesNotContain("포트 입력", source, StringComparison.Ordinal);
        Assert.DoesNotContain("해시 입력", source, StringComparison.Ordinal);
    }

    [Fact]
    public void BothFolderSelectionsValidateOnlyCanonicalPaths()
    {
        var setup = File.ReadAllText(Path.Combine(
            AppRoot,
            "FirstRunSetupWindow.xaml.cs"));

        Assert.Contains(
            "LegacyOperatorSettingsFolderKind.Scene",
            setup,
            StringComparison.Ordinal);
        Assert.Contains(
            "LegacyOperatorSettingsFolderKind.Resource",
            setup,
            StringComparison.Ordinal);
        Assert.DoesNotContain("MmoneyCoder.ini", setup, StringComparison.Ordinal);
        Assert.DoesNotContain("LegacyIniDatabaseOptionsLoader", setup,
            StringComparison.Ordinal);
        var clearCuts = setup.IndexOf(
            "_cutsDirectory = null;",
            StringComparison.Ordinal);
        var validateCuts = setup.IndexOf(
            "LegacyOperatorSettingsFolderKind.Scene",
            StringComparison.Ordinal);
        var clearResource = setup.IndexOf(
            "_resourceDirectory = null;",
            StringComparison.Ordinal);
        var validateResource = setup.IndexOf(
            "LegacyOperatorSettingsFolderKind.Resource",
            StringComparison.Ordinal);
        Assert.True(clearCuts >= 0);
        Assert.True(validateCuts > clearCuts);
        Assert.True(clearResource > validateCuts);
        Assert.True(validateResource > clearResource);
        Assert.Contains(
            "Path.GetFileName(cuts),\n" +
            "                \"Cuts\"",
            setup.ReplaceLineEndings("\n"),
            StringComparison.Ordinal);
        Assert.Contains(
            "Path.GetFileName(resource),\n" +
            "                \"Res\"",
            setup.ReplaceLineEndings("\n"),
            StringComparison.Ordinal);
    }

    [Fact]
    public void SetupPersistsOnlyTheSelectedPathsWithoutStartingAChildProcess()
    {
        var setup = File.ReadAllText(Path.Combine(
            AppRoot,
            "FirstRunSetupWindow.xaml.cs"));

        var saveSettings = RequiredIndex(
            setup,
            "var saved = store.Save(current with");
        var saveBinding = RequiredIndex(
            setup,
            "SaveRuntimeBinding(repositoryRoot, runtimeRoot);");
        Assert.True(saveSettings < saveBinding);
        Assert.Contains(
            "var store = new LegacyOperatorSettingsStore();",
            setup,
            StringComparison.Ordinal);
        Assert.Contains(
            "SceneDirectory = cutsDirectory",
            setup,
            StringComparison.Ordinal);
        Assert.Contains(
            "ResourceDirectory = resourceDirectory",
            setup,
            StringComparison.Ordinal);
        Assert.Contains(
            "Directory.Build.local.props",
            setup,
            StringComparison.Ordinal);
        Assert.Contains(
            "File.Move(temporary, destination, overwrite: true);",
            setup,
            StringComparison.Ordinal);
        Assert.DoesNotContain("ProcessStartInfo", setup, StringComparison.Ordinal);
        Assert.DoesNotContain("Process.Start", setup, StringComparison.Ordinal);
        Assert.DoesNotContain("RunSetupAsync", setup, StringComparison.Ordinal);
        Assert.DoesNotContain("powershell.exe", setup, StringComparison.OrdinalIgnoreCase);
        Assert.DoesNotContain("dotnet", setup, StringComparison.OrdinalIgnoreCase);
        Assert.DoesNotContain("ConfigureDevelopmentLive", setup,
            StringComparison.Ordinal);
        Assert.DoesNotContain("PinRegisteredK3D", setup, StringComparison.Ordinal);
        Assert.DoesNotContain("DatabaseIniPath", setup, StringComparison.Ordinal);
    }

    [Fact]
    public void SourceOnlyBuildEmbedsOnlyItsOwnClonePathForTheLocalBinding()
    {
        var project = XDocument.Load(Path.Combine(
            AppRoot,
            "MBN_STOCK_WEBVIEW.LegacyParityApp.csproj"));
        var metadata = Assert.Single(project.Descendants("AssemblyMetadata"));

        Assert.Equal("SourceRepositoryRoot", metadata.Attribute("Include")?.Value);
        Assert.Equal("$(SourceRepositoryRoot)", metadata.Attribute("Value")?.Value);
        Assert.Contains(
            "'$(LegacyRuntimeAssetsEnabled)' != 'true'",
            metadata.Attribute("Condition")?.Value,
            StringComparison.Ordinal);
        Assert.Contains(
            "'$(Configuration)' == 'Debug'",
            metadata.Attribute("Condition")?.Value,
            StringComparison.Ordinal);
    }

    [Fact]
    public void AutomaticK3dPinningIsPersistentAndCannotApproveAChangedDll()
    {
        var initializer = File.ReadAllText(Path.Combine(
            RepositoryRoot,
            "scripts",
            "Initialize-ExistingDevelopmentPc.ps1"));

        Assert.Contains("[switch] $PinRegisteredK3D", initializer,
            StringComparison.Ordinal);
        Assert.Contains("[switch] $ReplaceK3DPin", initializer,
            StringComparison.Ordinal);
        Assert.Contains(
            "Join-Path $ConfigurationRoot 'k3d-pins.local.json'",
            initializer,
            StringComparison.Ordinal);
        Assert.Contains(
            "$approvedNativeSha256 = $actualNativeHash",
            initializer,
            StringComparison.Ordinal);
        Assert.Contains(
            "$approvedInteropSha256 = $actualInteropHash",
            initializer,
            StringComparison.Ordinal);
        Assert.Contains(
            "-PinRegisteredK3D cannot be combined with independently",
            initializer,
            StringComparison.Ordinal);
        Assert.Contains(
            "Automatic pinning cannot approve a changed DLL.",
            initializer,
            StringComparison.Ordinal);
        Assert.Contains(
            "-ReplaceK3DPin cannot be combined with -PinRegisteredK3D.",
            initializer,
            StringComparison.Ordinal);
        Assert.Contains(
            "A replacement requires independently supplied native and interop hashes.",
            initializer,
            StringComparison.Ordinal);
        Assert.Contains(
            "Write-ProtectedUtf8TextAtomically",
            initializer,
            StringComparison.Ordinal);
        Assert.Contains(
            "This is a durable trust anchor, not transactional runtime state.",
            initializer,
            StringComparison.Ordinal);
        Assert.DoesNotContain(
            "K3D persistent pin rollback failed:",
            initializer,
            StringComparison.Ordinal);
        var pinPreflight = initializer.LastIndexOf(
            "$k3dPinPreflight = Get-K3DPinPreflight",
            StringComparison.Ordinal);
        var pinWrite = initializer.IndexOf(
            "Write-ProtectedUtf8TextAtomically",
            pinPreflight,
            StringComparison.Ordinal);
        var livePreflight = initializer.IndexOf(
            "$livePreflight = Get-DevelopmentLivePreflight",
            pinWrite,
            StringComparison.Ordinal);
        Assert.True(pinPreflight >= 0);
        Assert.True(pinWrite > pinPreflight);
        Assert.True(livePreflight > pinWrite);
        Assert.Contains(
            "nativeSha256 = $ApprovedNativeHash.ToUpperInvariant()",
            initializer,
            StringComparison.Ordinal);
        Assert.Contains(
            "interopSha256 = $ApprovedInteropHash.ToUpperInvariant()",
            initializer,
            StringComparison.Ordinal);
        Assert.Contains(
            "Get-VerifiedK3DRegistration -Reports $registrationReports",
            initializer,
            StringComparison.Ordinal);
        Assert.DoesNotContain("Activator.CreateInstance", initializer,
            StringComparison.Ordinal);
    }

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

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