using Application.Abstractions.Identity;
using Application.Abstractions.Identity.Models;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;

namespace Infrastructure.Persistence.Identity
{
    public sealed class IdentityUserWriter(UserManager<ApplicationUser> userManager) : IIdentityUserWriter
    {
        public async Task UpdateUserAsync(ApplicationUserDto a, CancellationToken ct)
        {
            if (string.IsNullOrWhiteSpace(a.ID))
            {
                throw new InvalidOperationException("ID는 필수입니다.");
            }

            var user = await userManager.FindByIdAsync(a.ID);
            if (user is null)
            {
                throw new InvalidOperationException("사용자 정보를 찾을 수 없습니다.");
            }

            // 이메일 중복 확인(본인 제외)
            if (!string.IsNullOrWhiteSpace(a.Email))
            {
                var exists = await userManager.Users.AsNoTracking().AnyAsync(u => u.Email == a.Email && u.Id != a.ID, ct);
                if (exists)
                {
                    throw new InvalidOperationException("이미 존재하는 이메일 주소입니다.");
                }
            }

            user.SetFullName(a.Name);
            user.SetEmail(a.Email);
            user.SetPhoneNumber(a.Phone);
            user.SetDeleted(a.IsDeleted);
            user.SetEmailConfirmed(a.EmailConfirmed);
            user.SetLockoutEnd(a.LockoutEnd);

            // 비밀번호 변경(입력되었을 때만)
            if (!string.IsNullOrWhiteSpace(a.NewPassword))
            {
                if (a.NewPassword != a.ConfirmPassword)
                {
                    throw new InvalidOperationException("두 비밀번호가 서로 일치하지 않습니다.");
                }

                var token = await userManager.GeneratePasswordResetTokenAsync(user);
                var reset = await userManager.ResetPasswordAsync(user, token, a.NewPassword);

                if (!reset.Succeeded)
                {
                    throw new InvalidOperationException(string.Join(Environment.NewLine, reset.Errors.Select(x => x.Description)));
                }
            }

            var updated = await userManager.UpdateAsync(user);
            if (!updated.Succeeded)
            {
                throw new InvalidOperationException(string.Join(Environment.NewLine, updated.Errors.Select(x => x.Description)));
            }
        }

        public async Task UpdateUserRolesAsync(string userID, UserRolesDto? b, CancellationToken ct)
        {
            if (string.IsNullOrWhiteSpace(userID))
            {
                throw new InvalidOperationException("ID는 필수입니다.");
            }

            var user = await userManager.FindByIdAsync(userID);
            if (user is null)
            {
                throw new InvalidOperationException("사용자 정보를 찾을 수 없습니다.");
            }

            var userRoles = await userManager.GetRolesAsync(user);

            foreach (var role in b?.Roles ?? Enumerable.Empty<UserRolesDto.Checkbox>())
            {
                var roleName = role.DisplayValue?.Trim();
                if (string.IsNullOrWhiteSpace(roleName))
                {
                    continue;
                }

                // 현재 사용자의 역할에 포함되어 있으나 선택되지 않은 경우 제거
                if (userRoles.Contains(roleName, StringComparer.OrdinalIgnoreCase) && !role.IsSelected)
                {
                    var removed = await userManager.RemoveFromRoleAsync(user, roleName);
                    if (!removed.Succeeded)
                    {
                        throw new InvalidOperationException(string.Join(Environment.NewLine, removed.Errors.Select(e => e.Description)));
                    }
                }

                // 현재 사용자의 역할에 포함되지 않았으나 선택된 경우 추가
                if (!userRoles.Contains(roleName, StringComparer.OrdinalIgnoreCase) && role.IsSelected)
                {
                    var added = await userManager.AddToRoleAsync(user, roleName);
                    if (!added.Succeeded)
                    {
                        throw new InvalidOperationException(string.Join(Environment.NewLine, added.Errors.Select(e => e.Description)));
                    }
                }
            }
        }
    }
}