@page "/manage/api"
@using Wave.Data
@using Microsoft.EntityFrameworkCore
@using System.ComponentModel.DataAnnotations
@using System.Security.Cryptography
@using Wave.Utilities

@attribute [Authorize(Roles = "Admin")]

@inject IDbContextFactory<ApplicationDbContext> ContextFactory
@inject ILogger<ManageApi> Logger
@inject IMessageDisplay Message
@inject IStringLocalizer<ManageApi> Localizer

<PageTitle>@(Localizer["Title"] + TitlePostfix)</PageTitle>

<h1 class="text-3xl lg:text-5xl font-light mb-6 text-primary">@Localizer["Title"]</h1>

<section>
	@if (!string.IsNullOrWhiteSpace(Key)) {
		<Alert CanRemove="true" Type="Alert.MessageType.Information">
			<p>@Localizer["NewApiKey_Message"]</p>
			<div class="flex gap-2">
				<button type="button" class="btn btn-sm btn-square" onclick="navigator.clipboard.writeText('@Key')">
					<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="w-5 h-5">
						<path fill-rule="evenodd" d="M15.988 3.012A2.25 2.25 0 0 1 18 5.25v6.5A2.25 2.25 0 0 1 15.75 14H13.5V7A2.5 2.5 0 0 0 11 4.5H8.128a2.252 2.252 0 0 1 1.884-1.488A2.25 2.25 0 0 1 12.25 1h1.5a2.25 2.25 0 0 1 2.238 2.012ZM11.5 3.25a.75.75 0 0 1 .75-.75h1.5a.75.75 0 0 1 .75.75v.25h-3v-.25Z" clip-rule="evenodd" />
						<path fill-rule="evenodd" d="M2 7a1 1 0 0 1 1-1h8a1 1 0 0 1 1 1v10a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V7Zm2 3.25a.75.75 0 0 1 .75-.75h4.5a.75.75 0 0 1 0 1.5h-4.5a.75.75 0 0 1-.75-.75Zm0 3.5a.75.75 0 0 1 .75-.75h4.5a.75.75 0 0 1 0 1.5h-4.5a.75.75 0 0 1-.75-.75Z" clip-rule="evenodd" />
					</svg>
				</button>
				<div class="bg-base-100 text-base-content w-0 grow overflow-x-auto">
					<code class="p-2 whitespace-nowrap">@Key</code>
				</div>
			</div>
		</Alert>
	}

	<div class="overflow-x-auto">
		<table class="table table-zebra">
			<thead>
			<tr>
				<th>@Localizer["Owner_Header"]</th>
				<th>@Localizer["Claims_Header"]</th>
				<th> </th>
			</tr>
			</thead>
			<tbody>
			@foreach (var apiKey in ApiKeys) {
				<tr>
					<td>@apiKey.OwnerName</td>
					<td>@string.Join(", ", apiKey.Claims.Select(c => c.Type))</td>
					<td>
						<form method="post" @formname="@apiKey.Key" @onsubmit="async () => await DeleteApiKey(apiKey)">
							<AntiforgeryToken />
							<button type="submit" class="btn btn-sm btn-square btn-error" title="@Localizer["Delete_Label"]">
								<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="w-5 h-5">
									<path fill-rule="evenodd" d="M8.75 1A2.75 2.75 0 0 0 6 3.75v.443c-.795.077-1.584.176-2.365.298a.75.75 0 1 0 .23 1.482l.149-.022.841 10.518A2.75 2.75 0 0 0 7.596 19h4.807a2.75 2.75 0 0 0 2.742-2.53l.841-10.52.149.023a.75.75 0 0 0 .23-1.482A41.03 41.03 0 0 0 14 4.193V3.75A2.75 2.75 0 0 0 11.25 1h-2.5ZM10 4c.84 0 1.673.025 2.5.075V3.75c0-.69-.56-1.25-1.25-1.25h-2.5c-.69 0-1.25.56-1.25 1.25v.325C8.327 4.025 9.16 4 10 4ZM8.58 7.72a.75.75 0 0 0-1.5.06l.3 7.5a.75.75 0 1 0 1.5-.06l-.3-7.5Zm4.34.06a.75.75 0 1 0-1.5-.06l-.3 7.5a.75.75 0 1 0 1.5.06l.3-7.5Z" clip-rule="evenodd" />
								</svg>
							</button>
						</form>
					</td>
				</tr>
			}
			</tbody>
			<tfoot>
			<tr>
				<td colspan="3">
					<EditForm method="post" FormName="NewApiKey" Model="@Model" OnValidSubmit="CreateApiKey">
						<div class="join join-vertical md:join-horizontal w-full">
							<span class="btn no-animation join-item">@Localizer["Name_Label"]</span>
							<InputText @bind-Value="@Model.Name" placeholder="@Localizer["Name_Placeholder"]"
							           required aria-required max="128" class="input input-bordered join-item" 
							           autocomplete="off" />
							<button type="submit" class="btn btn-primary join-item">@Localizer["Submit"]</button>
						</div>
					</EditForm>
				</td>
			</tr>
			</tfoot>
		</table>
	</div>
</section>

@code {
	[CascadingParameter(Name = "TitlePostfix")]
	private string TitlePostfix { get; set; } = default!;
	[SupplyParameterFromForm(FormName = "NewApiKey")]
	private InputModel Model { get; set; } = new();

	private List<ApiKey> ApiKeys { get; set; } = [];

	private string? Key { get; set; }

	protected override async Task OnInitializedAsync() {
		await using var context = await ContextFactory.CreateDbContextAsync();
		
		ApiKeys = await context.Set<ApiKey>().Include(a => a.ApiClaims).ToListAsync();
	}

	private async Task CreateApiKey() {
		try {
			await using var context = await ContextFactory.CreateDbContextAsync();

			using var generator = RandomNumberGenerator.Create();
			byte[] data = new byte[128];
			generator.GetBytes(data);
			string key = Convert.ToBase64String(data);

			var apiKey = new ApiKey {
				OwnerName = Model.Name!,
				Key = Convert.ToBase64String(SHA256.HashData(data)),
				ApiClaims = {
					new ApiClaim(0, "EmailApi", "EmailApi")
				}
			};
			await context.AddAsync(apiKey);
			await context.SaveChangesAsync();

			Message.ShowSuccess(Localizer["Create_Success"]);
			Model = new();
			Key = key;
			ApiKeys.Add(apiKey);
		} catch (Exception ex) {
			Logger.LogError(ex, "Failed to create API key");
			Message.ShowError(Localizer["Create_Error"]);
		}
	}

	private async Task DeleteApiKey(ApiKey key) {
		try {
			await using var context = await ContextFactory.CreateDbContextAsync();

			context.Remove(key);
			await context.SaveChangesAsync();

			Message.ShowSuccess(Localizer["Delete_Success"]);
			Key = null;
			ApiKeys.Remove(key);
		} catch (Exception ex) {
			Logger.LogError(ex, "Failed to create API key");
			Message.ShowError(Localizer["Delete_Error"]);
		}
	}

	private sealed class InputModel {
		[Required(AllowEmptyStrings = false), MinLength(3), MaxLength(128)]
		public string? Name { get; set; }
	}
}
