using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Tiku.Application.PlatformAdmin;
using Tiku.Application.Security;
using Tiku.Domain.Identity;
using Tiku.Domain.Operations;
using Tiku.Domain.Tenancy;

namespace Tiku.Infrastructure.PlatformAdmin;

internal sealed partial class PlatformAdminService
{
    public async Task<PlatformStaffList> GetStaffAsync(
        PlatformAdminActor actor,
        PlatformAdminQuery query,
        CancellationToken cancellationToken = default)
    {
        await AssertPlatformPermissionAsync(actor, BackendPermissions.PlatformStaffManage, cancellationToken);
        return await ExecuteSystemAsync("platform staff list", async dbContext =>
        {
            var roleRows = await (
                    from userRole in dbContext.PlatformBackendUserRoles.AsNoTracking()
                    join role in dbContext.PlatformBackendRoles.AsNoTracking() on userRole.RoleId equals role.Id
                    join user in dbContext.Users.AsNoTracking() on userRole.UserId equals user.Id
                    select new { user, role.Code })
                .ToArrayAsync(cancellationToken);
            var items = roleRows
                .GroupBy(row => row.user.Id)
                .Select(group => ToStaffItem(group.First().user,
                    group.Select(row => row.Code).Distinct(StringComparer.Ordinal).Order(StringComparer.Ordinal)
                        .ToArray()))
                .OrderBy(item => item.Email ?? item.PhoneMasked ?? item.UserId.ToString())
                .Take(Limit(query.Limit))
                .ToArray();
            return new PlatformStaffList(items);
        }, cancellationToken);
    }

    public async Task<PlatformStaffItem> UpsertStaffAsync(
        PlatformAdminActor actor,
        UpsertPlatformStaffCommand command,
        CancellationToken cancellationToken = default)
    {
        await AssertPlatformPermissionAsync(actor, BackendPermissions.PlatformStaffManage, cancellationToken);
        return await ExecuteSystemAsync("platform staff upsert", async (provider, dbContext) =>
        {
            var user = command.UserId.HasValue
                ? await dbContext.Users.SingleOrDefaultAsync(item => item.Id == command.UserId.Value, cancellationToken)
                : await dbContext.Users.SingleOrDefaultAsync(item =>
                        (!string.IsNullOrWhiteSpace(command.Email) && item.Email == command.Email) ||
                        (!string.IsNullOrWhiteSpace(command.Phone) && item.Phone == command.Phone),
                    cancellationToken);
            if (user is null)
            {
                user = new User
                {
                    Email = Normalize(command.Email),
                    NormalizedEmail = Normalize(command.Email)?.ToUpperInvariant(),
                    UserName = Normalize(command.Email) ?? Normalize(command.Phone),
                    NormalizedUserName = (Normalize(command.Email) ?? Normalize(command.Phone))?.ToUpperInvariant(),
                    Phone = Normalize(command.Phone),
                    PhoneNumber = Normalize(command.Phone),
                    Name = Normalize(command.Name),
                    PrimaryRole = "platform_admin",
                    Status = command.Status,
                    ForcePasswordChange = true
                };
                dbContext.Users.Add(user);
            }
            else
            {
                user.Email = Normalize(command.Email) ?? user.Email;
                user.NormalizedEmail = user.Email?.ToUpperInvariant();
                user.Phone = Normalize(command.Phone) ?? user.Phone;
                user.PhoneNumber = user.Phone;
                user.Name = Normalize(command.Name) ?? user.Name;
                user.Status = command.Status;
                user.PrimaryRole = "platform_admin";
            }

            var roleIds = command.RoleIds.Distinct().ToArray();
            var roleCount =
                await dbContext.PlatformBackendRoles.CountAsync(role => roleIds.Contains(role.Id), cancellationToken);
            if (roleCount != roleIds.Length)
                throw new PlatformAdminException("One or more platform roles were not found.", "role_not_found");

            await dbContext.SaveChangesAsync(cancellationToken);
            await dbContext.PlatformBackendUserRoles.Where(binding => binding.UserId == user.Id)
                .ExecuteDeleteAsync(cancellationToken);
            dbContext.PlatformBackendUserRoles.AddRange(roleIds.Select(roleId => new PlatformBackendUserRole
            {
                UserId = user.Id,
                RoleId = roleId
            }));
            AddAudit(dbContext, actor, "platform.staff.upserted", user.Id,
                new { user.Email, Phone = MaskPhone(user.Phone), user.Status, RoleIds = roleIds });
            await dbContext.SaveChangesAsync(cancellationToken);
            var invalidator = provider.GetRequiredService<IAuthorizationStateInvalidator>();
            await invalidator.InvalidateUserAsync(user.Id, cancellationToken);
            await invalidator.BumpScopeAsync(AuthRealm.Platform, null, cancellationToken);
            var roleCodes = await dbContext.PlatformBackendRoles.AsNoTracking()
                .Where(role => roleIds.Contains(role.Id))
                .Select(role => role.Code)
                .ToArrayAsync(cancellationToken);
            return ToStaffItem(user, roleCodes);
        }, cancellationToken);
    }

    public async Task<PlatformStaffItem> UpdateStaffStatusAsync(
        PlatformAdminActor actor,
        UpdatePlatformStaffStatusCommand command,
        CancellationToken cancellationToken = default)
    {
        await AssertPlatformPermissionAsync(actor, BackendPermissions.PlatformStaffManage, cancellationToken);
        return await ExecuteSystemAsync("platform staff status update", async (provider, dbContext) =>
        {
            var user = await dbContext.Users.SingleOrDefaultAsync(item => item.Id == command.UserId, cancellationToken)
                       ?? throw new PlatformAdminException("Platform staff user was not found.", "staff_not_found");
            var from = user.Status;
            user.Status = command.Status;
            AddAudit(dbContext, actor, "platform.staff.status_changed", user.Id,
                new { From = from, To = command.Status, command.Reason });
            await dbContext.SaveChangesAsync(cancellationToken);
            await provider.GetRequiredService<IAuthorizationStateInvalidator>()
                .InvalidateUserAsync(user.Id, cancellationToken);
            var roleCodes = await (
                    from binding in dbContext.PlatformBackendUserRoles.AsNoTracking()
                    join role in dbContext.PlatformBackendRoles.AsNoTracking() on binding.RoleId equals role.Id
                    where binding.UserId == user.Id
                    select role.Code)
                .ToArrayAsync(cancellationToken);
            return ToStaffItem(user, roleCodes);
        }, cancellationToken);
    }
}