using System.Text.Json;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Tiku.Application.Auth;
using Tiku.Domain.Identity;
using Tiku.Domain.Operations;
using Tiku.Domain.Tenancy;
using Tiku.Infrastructure.Persistence;

namespace Tiku.Infrastructure.Auth;

internal sealed class AuthAdministrationService(
    TikuDbContext dbContext,
    UserManager<User> userManager,
    IAuthSessionStore sessionStore) : IAuthAdministrationService
{
    public async Task ResetPasswordAsync(
        AdministrativePasswordResetRequest request,
        CancellationToken cancellationToken = default)
    {
        if (string.IsNullOrWhiteSpace(request.Reason))
            throw new InvalidCredentialsException("password_reset_reason_required");

        var permitted = request.TenantId is { } tenantId
            ? await dbContext.TenantMemberships.AnyAsync(
                item => item.TenantId == tenantId && item.UserId == request.TargetUserId &&
                        item.Status == MembershipStatus.Active,
                cancellationToken)
            : await dbContext.PlatformBackendUserRoles.AnyAsync(
                item => item.UserId == request.TargetUserId,
                cancellationToken);
        if (!permitted) throw new AuthSessionNotFoundException();

        var user = await userManager.FindByIdAsync(request.TargetUserId.ToString())
                   ?? throw new AuthSessionNotFoundException();
        var token = await userManager.GeneratePasswordResetTokenAsync(user);
        var reset = await userManager.ResetPasswordAsync(user, token, request.TemporaryPassword);
        if (!reset.Succeeded) throw new InvalidCredentialsException("invalid_new_password");

        user.ForcePasswordChange = true;
        var updated = await userManager.UpdateAsync(user);
        if (!updated.Succeeded)
            throw new InvalidOperationException("Unable to require a password change after the administrative reset.");

        await sessionStore.RevokeAllAsync(user.Id, "administrative_password_reset", cancellationToken);
        dbContext.AuditLogs.Add(new AuditLog
        {
            TenantId = request.TenantId,
            ActorUserId = request.ActorUserId,
            Action = "auth.password.reset_by_administrator",
            TargetType = "user",
            TargetId = request.TargetUserId.ToString(),
            Details = JsonSerializer.SerializeToElement(new
            {
                request.Reason,
                ForcePasswordChange = true
            })
        });
        await dbContext.SaveChangesAsync(cancellationToken);
    }
}