using System.Diagnostics;

namespace DodoSSH.Client.Auth;

/// <summary>Opens a URL in the user's browser.</summary>
/// <remarks>
/// An interface so the sign-in flow can be driven end to end in tests without a browser, and so a
/// packaged build can substitute a platform-specific opener if the default proves unreliable.
/// </remarks>
public interface IBrowserLauncher
{
    /// <summary>Opens the authorization URL.</summary>
    Task OpenAsync(Uri url, CancellationToken cancellationToken);
}

/// <summary>
/// Opens the system browser.
/// </summary>
/// <remarks>
/// <para>
/// The <em>system</em> browser, never an embedded one. RFC 8252 §8.12: an embedded user-agent lets
/// the application read the user's credentials as they are typed, and denies the user any way to see
/// the real address bar or inspect the provider's certificate. It also cannot reuse an existing
/// single-sign-on session.
/// </para>
/// <para>
/// <c>UseShellExecute</c> delegates to the platform handler — <c>ShellExecute</c> on Windows,
/// <c>open</c> on macOS, <c>xdg-open</c> on Linux. The Linux path depends on <c>xdg-utils</c> being
/// installed, which is not guaranteed on a minimal desktop; see docs/platform-flags.md.
/// </para>
/// </remarks>
public sealed class SystemBrowserLauncher : IBrowserLauncher
{
    /// <inheritdoc />
    public Task OpenAsync(Uri url, CancellationToken cancellationToken)
    {
        ArgumentNullException.ThrowIfNull(url);
        cancellationToken.ThrowIfCancellationRequested();

        // Only ever an authorization URL, but asserted rather than assumed: UseShellExecute on a
        // file path would launch it, so a scheme check is the difference between opening a page and
        // executing something.
        if (!string.Equals(url.Scheme, Uri.UriSchemeHttps, StringComparison.Ordinal)
            && !string.Equals(url.Scheme, Uri.UriSchemeHttp, StringComparison.Ordinal))
        {
            throw new ArgumentException($"Refusing to open a '{url.Scheme}' URL.", nameof(url));
        }

        using var process = Process.Start(new ProcessStartInfo(url.AbsoluteUri)
        {
            UseShellExecute = true,
        });

        return Task.CompletedTask;
    }
}
