using MediatR;
using System.Text.Json;
using TakeoutSaaS.Application.Identity.Abstractions;
using TakeoutSaaS.Application.Identity.Commands;
using TakeoutSaaS.Application.Identity.Contracts;
using TakeoutSaaS.Application.Identity.Events;
using TakeoutSaaS.Application.Identity.Models;
using TakeoutSaaS.Domain.Identity.Entities;
using TakeoutSaaS.Domain.Identity.Enums;
using TakeoutSaaS.Domain.Identity.Repositories;
using TakeoutSaaS.Shared.Abstractions.Constants;
using TakeoutSaaS.Shared.Abstractions.Exceptions;
using TakeoutSaaS.Shared.Abstractions.Security;
using TakeoutSaaS.Shared.Abstractions.Tenancy;

namespace TakeoutSaaS.Application.Identity.Handlers;

/// <summary>
/// 批量用户操作处理器。
/// </summary>
public sealed class BatchIdentityUserOperationCommandHandler(
    IIdentityUserRepository identityUserRepository,
    IUserRoleRepository userRoleRepository,
    IRoleRepository roleRepository,
    ITenantProvider tenantProvider,
    ICurrentUserAccessor currentUserAccessor,
    IAdminAuthService adminAuthService,
    IIdentityOperationLogPublisher operationLogPublisher)
    : IRequestHandler<BatchIdentityUserOperationCommand, BatchIdentityUserOperationResult>
{
    /// <inheritdoc />
    public async Task<BatchIdentityUserOperationResult> Handle(BatchIdentityUserOperationCommand request, CancellationToken cancellationToken)
    {
        // 1. 获取当前租户与操作者档案
        var currentTenantId = tenantProvider.GetCurrentTenantId();
        var operatorProfile = await adminAuthService.GetProfileAsync(currentUserAccessor.UserId, cancellationToken);

        // 2. 校验跨租户访问
        if (request.TenantId.HasValue && request.TenantId.Value != currentTenantId)
        {
            throw new BusinessException(ErrorCodes.Forbidden, "禁止跨租户批量操作用户");
        }

        // 3. 解析用户 ID 列表
        var tenantId = currentTenantId;
        var userIds = ParseIds(request.UserIds, "用户");
        if (userIds.Length == 0)
        {
            return new BatchIdentityUserOperationResult
            {
                SuccessCount = 0,
                FailureCount = 0,
                Failures = Array.Empty<BatchIdentityUserFailureItem>(),
                ExportItems = Array.Empty<UserListItemDto>()
            };
        }

        // 4. 查询目标用户集合
        var includeDeleted = request.Operation == IdentityUserBatchOperation.Restore;
        var users = await identityUserRepository.GetForUpdateByIdsAsync(tenantId, userIds, includeDeleted, cancellationToken);
        var usersById = users.ToDictionary(user => user.Id, user => user, EqualityComparer<long>.Default);

        // 5. 预计算租户管理员约束
        var tenantAdminRole = await roleRepository.FindByCodeAsync("tenant-admin", tenantId, cancellationToken);
        var tenantAdminUserIds = tenantAdminRole == null
            ? Array.Empty<long>()
            : (await userRoleRepository.GetByUserIdsAsync(tenantId, usersById.Keys, cancellationToken))
                .Where(x => x.RoleId == tenantAdminRole.Id)
                .Select(x => x.UserId)
                .Distinct()
                .ToArray();
        var activeAdminCount = tenantAdminRole == null
            ? 0
            : (await identityUserRepository.SearchPagedAsync(new IdentityUserSearchFilter
            {
                TenantId = tenantId,
                RoleId = tenantAdminRole.Id,
                Status = IdentityUserStatus.Active,
                IncludeDeleted = false,
                Page = 1,
                PageSize = 1
            }, cancellationToken)).Total;
        var remainingActiveAdmins = activeAdminCount;

        // 6. 执行批量操作
        var failures = new List<BatchIdentityUserFailureItem>();
        var successCount = 0;
        var exportItems = new List<UserListItemDto>();
        foreach (var userId in userIds)
        {
            if (!usersById.TryGetValue(userId, out var user))
            {
                failures.Add(new BatchIdentityUserFailureItem
                {
                    UserId = userId,
                    Reason = "用户不存在"
                });
                continue;
            }

            try
            {
                switch (request.Operation)
                {
                    case IdentityUserBatchOperation.Enable:
                        user.Status = IdentityUserStatus.Active;
                        user.LockedUntil = null;
                        user.FailedLoginCount = 0;
                        await identityUserRepository.SaveChangesAsync(cancellationToken);
                        successCount++;
                        break;
                    case IdentityUserBatchOperation.Disable:
                        if (user.Status == IdentityUserStatus.Active
                            && tenantAdminUserIds.Contains(user.Id)
                            && remainingActiveAdmins <= 1)
                        {
                            throw new BusinessException(ErrorCodes.Conflict, "至少保留一个管理员");
                        }

                        if (user.Status == IdentityUserStatus.Active && tenantAdminUserIds.Contains(user.Id))
                        {
                            remainingActiveAdmins--;
                        }

                        user.Status = IdentityUserStatus.Disabled;
                        user.LockedUntil = null;
                        await identityUserRepository.SaveChangesAsync(cancellationToken);
                        successCount++;
                        break;
                    case IdentityUserBatchOperation.Delete:
                        if (user.Status == IdentityUserStatus.Active
                            && tenantAdminUserIds.Contains(user.Id)
                            && remainingActiveAdmins <= 1)
                        {
                            throw new BusinessException(ErrorCodes.Conflict, "至少保留一个管理员");
                        }

                        if (user.Status == IdentityUserStatus.Active && tenantAdminUserIds.Contains(user.Id))
                        {
                            remainingActiveAdmins--;
                        }

                        await identityUserRepository.RemoveAsync(user, cancellationToken);
                        await identityUserRepository.SaveChangesAsync(cancellationToken);
                        successCount++;
                        break;
                    case IdentityUserBatchOperation.Restore:
                        if (!user.DeletedAt.HasValue)
                        {
                            throw new BusinessException(ErrorCodes.BadRequest, "用户未删除");
                        }

                        user.DeletedAt = null;
                        user.DeletedBy = null;
                        await identityUserRepository.SaveChangesAsync(cancellationToken);
                        successCount++;
                        break;
                    case IdentityUserBatchOperation.Export:
                        successCount++;
                        break;
                    default:
                        throw new BusinessException(ErrorCodes.BadRequest, "无效的批量操作类型");
                }
            }
            catch (Exception ex) when (IsConcurrencyException(ex))
            {
                failures.Add(new BatchIdentityUserFailureItem
                {
                    UserId = userId,
                    Reason = "用户数据已被修改，请刷新后重试"
                });
            }
            catch (BusinessException ex)
            {
                failures.Add(new BatchIdentityUserFailureItem
                {
                    UserId = userId,
                    Reason = ex.Message
                });
            }
        }
        // 6.1 处理导出数据
        if (request.Operation == IdentityUserBatchOperation.Export)
        {
            var roleCodesLookup = await ResolveRoleCodesAsync(users, userRoleRepository, roleRepository, cancellationToken);
            var now = DateTime.UtcNow;
            exportItems.AddRange(users.Select(user => new UserListItemDto
            {
                UserId = user.Id,
                TenantId = user.TenantId,
                Account = user.Account,
                DisplayName = user.DisplayName,
                Avatar = user.Avatar,
                Phone = user.Phone,
                Email = user.Email,
                Status = user.Status,
                IsLocked = user.Status == IdentityUserStatus.Locked || (user.LockedUntil.HasValue && user.LockedUntil.Value > now),
                IsDeleted = user.DeletedAt.HasValue,
                Roles = roleCodesLookup.TryGetValue(user.Id, out var codes) ? codes : Array.Empty<string>(),
                CreatedAt = user.CreatedAt,
                LastLoginAt = user.LastLoginAt
            }));
        }

        // 7. 构建操作日志消息
        var operatorName = string.IsNullOrWhiteSpace(operatorProfile.DisplayName)
            ? operatorProfile.Account
            : operatorProfile.DisplayName;
        if (string.IsNullOrWhiteSpace(operatorName))
        {
            operatorName = $"user:{currentUserAccessor.UserId}";
        }

        var logMessage = new IdentityUserOperationLogMessage
        {
            OperationType = "identity-user:batch",
            TargetType = "identity_user",
            TargetIds = JsonSerializer.Serialize(userIds),
            OperatorId = currentUserAccessor.UserId.ToString(),
            OperatorName = operatorName,
            Parameters = JsonSerializer.Serialize(new { tenantId, operation = request.Operation.ToString() }),
            Result = JsonSerializer.Serialize(new { successCount, failureCount = failures.Count }),
            Success = failures.Count == 0
        };

        // 8. 写入 Outbox 并保存变更
        await operationLogPublisher.PublishAsync(logMessage, cancellationToken);
        await identityUserRepository.SaveChangesAsync(cancellationToken);

        return new BatchIdentityUserOperationResult
        {
            SuccessCount = successCount,
            FailureCount = failures.Count,
            Failures = failures,
            ExportItems = exportItems
        };
    }

    private static long[] ParseIds(string[] values, string name)
    {
        // 1. 空数组直接返回
        if (values.Length == 0)
        {
            return Array.Empty<long>();
        }

        // 2. 解析并去重
        var ids = new List<long>(values.Length);
        foreach (var value in values)
        {
            if (!long.TryParse(value, out var id) || id <= 0)
            {
                throw new BusinessException(ErrorCodes.BadRequest, $"{name} ID 无效");
            }

            ids.Add(id);
        }

        // 3. 返回去重结果
        return ids.Distinct().ToArray();
    }

    private static async Task<Dictionary<long, string[]>> ResolveRoleCodesAsync(
        IReadOnlyList<IdentityUser> users,
        IUserRoleRepository userRoleRepository,
        IRoleRepository roleRepository,
        CancellationToken cancellationToken)
    {
        // 1. 预分配字典容量
        var result = new Dictionary<long, string[]>(users.Count);

        // 2. 按租户分组，降低角色查询次数
        foreach (var group in users.GroupBy(user => user.TenantId))
        {
            var tenantId = group.Key;
            var userIds = group.Select(user => user.Id).Distinct().ToArray();
            if (userIds.Length == 0)
            {
                continue;
            }

            // 3. 查询用户角色映射
            var relations = await userRoleRepository.GetByUserIdsAsync(tenantId, userIds, cancellationToken);
            if (relations.Count == 0)
            {
                continue;
            }

            // 4. 查询角色并构建映射
            var roleIds = relations.Select(x => x.RoleId).Distinct().ToArray();
            var roles = roleIds.Length == 0
                ? Array.Empty<Role>()
                : await roleRepository.GetByIdsAsync(tenantId, roleIds, cancellationToken);
            var roleCodeMap = roles.ToDictionary(role => role.Id, role => role.Code, EqualityComparer<long>.Default);

            // 5. 组装用户角色编码列表
            foreach (var relationGroup in relations.GroupBy(x => x.UserId))
            {
                var codes = relationGroup
                    .Select(x => roleCodeMap.TryGetValue(x.RoleId, out var code) ? code : null)
                    .OfType<string>()
                    .Where(code => !string.IsNullOrWhiteSpace(code))
                    .Distinct(StringComparer.OrdinalIgnoreCase)
                    .ToArray();
                result[relationGroup.Key] = codes.Length == 0 ? Array.Empty<string>() : codes;
            }
        }

        return result;
    }

    private static bool IsConcurrencyException(Exception exception)
        => string.Equals(exception.GetType().Name, "DbUpdateConcurrencyException", StringComparison.Ordinal);
}
