using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.EntityFrameworkCore;
using Phantom.Controller.Database;
using Phantom.Controller.Database.Entities;
using Phantom.Controller.Database.Enums;
using Phantom.Controller.Services.Users;
using Phantom.Utils.Tasks;

namespace Phantom.Controller.Services.Audit;

public sealed partial class AuditLog {
	private readonly CancellationToken cancellationToken;
	private readonly DatabaseProvider databaseProvider;
	private readonly AuthenticationStateProvider authenticationStateProvider;
	private readonly TaskManager taskManager;

	public AuditLog(ServiceConfiguration serviceConfiguration, DatabaseProvider databaseProvider, AuthenticationStateProvider authenticationStateProvider, TaskManager taskManager) {
		this.cancellationToken = serviceConfiguration.CancellationToken;
		this.databaseProvider = databaseProvider;
		this.authenticationStateProvider = authenticationStateProvider;
		this.taskManager = taskManager;
	}
	
	private async Task<Guid?> GetCurrentAuthenticatedUserId() {
		var authenticationState = await authenticationStateProvider.GetAuthenticationStateAsync();
		return UserManager.GetAuthenticatedUserId(authenticationState.User);
	}

	private async Task AddEntityToDatabase(AuditLogEntity logEntity) {
		using var scope = databaseProvider.CreateScope();
		scope.Ctx.AuditLog.Add(logEntity);
		await scope.Ctx.SaveChangesAsync(cancellationToken);
	}

	private void AddItem(Guid? userGuid, AuditLogEventType eventType, string subjectId, Dictionary<string, object?>? extra = null) {
		var logEntity = new AuditLogEntity(userGuid, eventType, subjectId, extra);
		taskManager.Run("Store audit log item to database", () => AddEntityToDatabase(logEntity));
	}

	private async Task AddItem(AuditLogEventType eventType, string subjectId, Dictionary<string, object?>? extra = null) {
		AddItem(await GetCurrentAuthenticatedUserId(), eventType, subjectId, extra);
	}

	public async Task<AuditLogItem[]> GetItems(int count, CancellationToken cancellationToken) {
		using var scope = databaseProvider.CreateScope();
		return await scope.Ctx.AuditLog
		                  .Include(static entity => entity.User)
		                  .AsQueryable()
		                  .OrderByDescending(static entity => entity.UtcTime)
		                  .Take(count)
		                  .Select(static entity => new AuditLogItem(entity.UtcTime, entity.UserGuid, entity.User == null ? null : entity.User.Name, entity.EventType, entity.SubjectType, entity.SubjectId, entity.Data))
		                  .ToArrayAsync(cancellationToken);
	}
}