namespace MyOffice.Web.Auth;

using DbContext;
using Infrastructure;
using Microsoft.Extensions.Options;
using OpenIddict.Abstractions;
using static OpenIddict.Abstractions.OpenIddictConstants;

public sealed class OpenIddictSeeder : IHostedService
{
	private readonly IServiceProvider _serviceProvider;
	private readonly IConfiguration _configuration;
	private readonly ILogger<OpenIddictSeeder> _logger;

	public OpenIddictSeeder(
		IServiceProvider serviceProvider,
		IConfiguration configuration,
		ILogger<OpenIddictSeeder> logger
	)
	{
		_serviceProvider = serviceProvider;
		_configuration = configuration;
		_logger = logger;
	}

	public async Task StartAsync(CancellationToken cancellationToken)
	{
		await using var scope = _serviceProvider.CreateAsyncScope();

		await RegisterScopesAsync(scope.ServiceProvider, cancellationToken);
		await RegisterClientAsync(scope.ServiceProvider, cancellationToken);
	}

	public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;

	private async Task RegisterScopesAsync(IServiceProvider provider, CancellationToken cancellationToken)
	{
		var manager = provider.GetRequiredService<IOpenIddictScopeManager>();

		if (await manager.FindByNameAsync(OpenIddictAuthConstants.ApiScope, cancellationToken) is null)
		{
			await manager.CreateAsync(new OpenIddictScopeDescriptor
			{
				Name = OpenIddictAuthConstants.ApiScope,
				DisplayName = OpenIddictAuthConstants.ApiFriendlyName,
				Resources = { OpenIddictAuthConstants.ApiScope }
			}, cancellationToken);

			_logger.LogInformation("Created OpenIddict scope {Scope}.", OpenIddictAuthConstants.ApiScope);
		}

		if (await manager.FindByNameAsync(OpenIddictAuthConstants.RolesScope, cancellationToken) is null)
		{
			await manager.CreateAsync(new OpenIddictScopeDescriptor
			{
				Name = OpenIddictAuthConstants.RolesScope,
				DisplayName = "User roles"
			}, cancellationToken);

			_logger.LogInformation("Created OpenIddict scope {Scope}.", OpenIddictAuthConstants.RolesScope);
		}
	}

	private async Task RegisterClientAsync(IServiceProvider provider, CancellationToken cancellationToken)
	{
		var manager = provider.GetRequiredService<IOpenIddictApplicationManager>();
		var globalSettings = provider.GetRequiredService<GlobalSettings>();

		var redirectUri = BuildRedirectUri(globalSettings.Host);
		var existing = await manager.FindByClientIdAsync(OpenIddictAuthConstants.SpaClientId, cancellationToken);
		var descriptor = CreateSpaClientDescriptor(redirectUri);

		if (existing is null)
		{
			await manager.CreateAsync(descriptor, cancellationToken);
			_logger.LogInformation("Created OpenIddict client {ClientId}.", OpenIddictAuthConstants.SpaClientId);
			return;
		}

		var currentRedirectUris = await manager.GetRedirectUrisAsync(existing, cancellationToken);
		foreach (var uri in currentRedirectUris)
		{
			if (!string.Equals(uri, redirectUri.ToString(), StringComparison.Ordinal))
				descriptor.RedirectUris.Add(new Uri(uri, UriKind.Absolute));
		}

		await manager.UpdateAsync(existing, descriptor, cancellationToken);
		_logger.LogInformation("Updated OpenIddict client {ClientId}.", OpenIddictAuthConstants.SpaClientId);
	}

	internal static OpenIddictApplicationDescriptor CreateSpaClientDescriptor(Uri redirectUri)
	{
		var descriptor = new OpenIddictApplicationDescriptor
		{
			ClientId = OpenIddictAuthConstants.SpaClientId,
			DisplayName = "MyOffice SPA",
			ClientType = ClientTypes.Public,
			ConsentType = ConsentTypes.Implicit,
			Permissions =
			{
				Permissions.Endpoints.Authorization,
				Permissions.Endpoints.Token,
				Permissions.Endpoints.EndSession,
				Permissions.GrantTypes.AuthorizationCode,
				Permissions.GrantTypes.Password,
				Permissions.GrantTypes.RefreshToken,
				Permissions.Prefixes.GrantType + OpenIddictAuthConstants.ExternalGrantType,
				Permissions.ResponseTypes.Code,
				Permissions.Scopes.Email,
				Permissions.Scopes.Profile,
				Permissions.Scopes.Roles,
				Permissions.Prefixes.Scope + Scopes.OpenId,
				Permissions.Prefixes.Scope + Scopes.OfflineAccess,
				Permissions.Prefixes.Scope + OpenIddictAuthConstants.ApiScope,
				Permissions.Prefixes.Scope + OpenIddictAuthConstants.RolesScope
			}
		};

		descriptor.RedirectUris.Add(redirectUri);
		return descriptor;
	}

	internal static Uri BuildRedirectUri(string? host)
	{
		if (string.IsNullOrWhiteSpace(host))
			return new Uri("http://localhost:4300/silent-refresh.html");

		return new Uri($"{host.TrimEnd('/')}/silent-refresh.html");
	}
}
