using System.Runtime.Versioning;
using DodoSSH.Client.App.Platform;
using DodoSSH.Client.Session;

namespace DodoSSH.Client.App.Tests;

/// <summary>
/// The real TPM-backed store, as far as it can be exercised without a person. Which is not far.
/// </summary>
/// <remarks>
/// <para>
/// <b>Two tests, and the reason there are only two is a measured finding.</b> A key created under
/// <c>CngUIProtectionLevels.ProtectKey</c> prompts at <em>creation</em>, not only at use: the policy means
/// "protect this key with a PIN", so Windows asks the user to set that up when the key is made. Sealing
/// therefore prompts as well as opening, even though sealing needs only the public half.
/// </para>
/// <para>
/// So anything that calls <c>SaveAsync</c>, <c>TryLoadAsync</c> with a blob present, or <c>ForgetAsync</c>
/// after a save will block a suite forever waiting for somebody to enter a PIN. That was found by writing
/// those tests and watching the run hang for ten minutes. They are gone; what is left is the two paths that
/// provably reach no dialog.
/// </para>
/// <para>
/// The rest of this store is verified by using the application, and that is not a gap this file can close —
/// a consent dialog needs hardware and a person by design. Disabling the UI policy to make it testable
/// would be testing a different class, and the one property worth having would be the property removed.
/// </para>
/// </remarks>
[SupportedOSPlatform("windows")]
public sealed class WindowsDeviceKeyStoreTests
{
    private static CancellationToken Token => TestContext.Current.CancellationToken;

    [Fact]
    public async Task OnAMachineWithATpm_TheStoreOffersItself()
    {
        // IsSupported probes with a throwaway key carrying no UI policy, which is why this one is safe to
        // run: no policy, no dialog. It is also the only honest availability test, because the platform
        // provider reports itself present on machines where creating a key then fails.
        SkipUnlessSupported();

        var store = DesktopDeviceKeyStores.ForThisMachine(new ClientPaths(Path.GetTempPath()));

        store.ShouldBeOfType<WindowsDeviceKeyStore>();
        (await store.IsAvailableAsync(Token)).ShouldBeTrue();
    }

    [Fact]
    public async Task WithNothingSaved_LoadingReturnsNullWithoutPrompting()
    {
        // Reaches no dialog because it returns on the missing file, before touching the TPM at all. That is
        // also what keeps a fresh machine's unlock screen quiet: it must not prompt for a key it has never
        // been given. If this test ever hangs, that ordering has been lost.
        SkipUnlessSupported();

        var directory = Path.Combine(Path.GetTempPath(), $"dodossh-devicekey-{Guid.CreateVersion7():N}");
        var store = new WindowsDeviceKeyStore(new ClientPaths(directory));

        (await store.TryLoadAsync(Token)).ShouldBeNull();

        // Nothing was created, so there is nothing to clean up — asserted, because a store that wrote a
        // directory just to answer "no" would be leaving litter on every launch of an unregistered machine.
        Directory.Exists(directory).ShouldBeFalse();
    }

    private static void SkipUnlessSupported()
    {
        var supported = OperatingSystem.IsWindows()
            && DesktopDeviceKeyStores.ForThisMachine(new ClientPaths(Path.GetTempPath()))
                is WindowsDeviceKeyStore;

        if (!supported)
        {
            Assert.Skip("This machine has no TPM the platform crypto provider will hold a key in.");
        }
    }
}
