using System.Diagnostics;
using System.Net;
using System.Text;
using Duende.IdentityModel.OidcClient;
using Duende.IdentityModel.OidcClient.Browser;

namespace ClaudeDo.Ui.Services;

public sealed record OnlineLoginResult(bool Success, string? RefreshToken, string? Error, string? Warning = null);

public sealed class OnlineLoginService
{
    public async Task<OnlineLoginResult> LoginAsync(
        string authority, string clientId, string scope, string redirectUri,
        CancellationToken ct = default)
    {
        try
        {
            var browser = new LoopbackBrowser(redirectUri);
            var options = new OidcClientOptions
            {
                Authority = authority,
                ClientId = clientId,
                Scope = scope,
                RedirectUri = redirectUri,
                Browser = browser,
            };

            var client = new OidcClient(options);
            var result = await client.LoginAsync(new LoginRequest(), ct);

            if (result.IsError)
                return new OnlineLoginResult(false, null, result.Error);

            if (string.IsNullOrEmpty(result.RefreshToken))
                return new OnlineLoginResult(false, null,
                    "No refresh token returned. Ensure 'offline_access' is in scope and the client allows it.");

            // Early heads-up: if the access token lacks the "user" project role the server will
            // reject sync with a 401. Login still succeeds; surface this as a warning, not an error.
            var warning = ZitadelTokenInspector.HasUserRole(result.AccessToken)
                ? null
                : "missing-user-role";

            return new OnlineLoginResult(true, result.RefreshToken, null, warning);
        }
        catch (OperationCanceledException)
        {
            return new OnlineLoginResult(false, null, "Login cancelled.");
        }
        catch (Exception ex)
        {
            return new OnlineLoginResult(false, null, ex.Message);
        }
    }
}

/// <summary>
/// IBrowser implementation: opens the system browser and captures the authorization
/// response via a loopback HttpListener on the redirect URI's host/port.
/// </summary>
sealed class LoopbackBrowser : IBrowser
{
    private static readonly TimeSpan Timeout = TimeSpan.FromMinutes(3);
    private readonly string _redirectUri;

    public LoopbackBrowser(string redirectUri) => _redirectUri = redirectUri;

    public async Task<BrowserResult> InvokeAsync(BrowserOptions options, CancellationToken ct = default)
    {
        // Derive the listener prefix from the redirect URI
        var uri = new Uri(_redirectUri);
        var prefix = $"{uri.Scheme}://{uri.Host}:{uri.Port}/";

        using var listener = new HttpListener();
        listener.Prefixes.Add(prefix);

        try
        {
            listener.Start();
        }
        catch (Exception ex)
        {
            return new BrowserResult
            {
                ResultType = BrowserResultType.UnknownError,
                Error = $"Could not start loopback listener on {prefix}: {ex.Message}"
            };
        }

        try
        {
            Process.Start(new ProcessStartInfo(options.StartUrl) { UseShellExecute = true });
        }
        catch (Exception ex)
        {
            return new BrowserResult
            {
                ResultType = BrowserResultType.UnknownError,
                Error = $"Could not open browser: {ex.Message}"
            };
        }

        using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
        cts.CancelAfter(Timeout);

        try
        {
            var context = await listener.GetContextAsync().WaitAsync(cts.Token);

            var responseBody = Encoding.UTF8.GetBytes(
                "<html><body style=\"font-family:sans-serif;background:#0D1311;color:#E4EBE4;padding:40px\">" +
                "<h2>Login successful</h2><p>You may close this tab.</p></body></html>");

            context.Response.ContentLength64 = responseBody.Length;
            context.Response.ContentType = "text/html; charset=utf-8";
            await context.Response.OutputStream.WriteAsync(responseBody, cts.Token);
            context.Response.OutputStream.Close();

            // rawUrl already includes the redirect path (e.g. "/callback?code=..."),
            // so build the full URL from the scheme://host:port base — NOT the full
            // redirect URI, or the path would be doubled (".../callback/callback").
            var rawUrl = context.Request.RawUrl ?? "";
            var fullUri = prefix.TrimEnd('/') + rawUrl;

            return new BrowserResult
            {
                ResultType = BrowserResultType.Success,
                Response = fullUri
            };
        }
        catch (OperationCanceledException)
        {
            return new BrowserResult
            {
                ResultType = BrowserResultType.Timeout,
                Error = "Login timed out waiting for browser callback."
            };
        }
        finally
        {
            listener.Stop();
        }
    }
}
