using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using TeleWave.Application.Admin.Users;
using TeleWave.Application.Auth;
using TeleWave.Application.Common.Interfaces;
using TeleWave.Application.Common.Models;
using TeleWave.Infrastructure.Persistence;

namespace TeleWave.Infrastructure.Identity;

internal sealed class IdentityService(
    UserManager<AppUser> userManager,
    SignInManager<AppUser> signInManager,
    AppDbContext dbContext
) : IIdentityService
{
    public async Task<Result<Guid>> CreateUserAsync(
        string userName,
        string password,
        CancellationToken cancellationToken
    )
    {
        if (await userManager.FindByNameAsync(userName) is not null)
            return Result.Failure<Guid>(AuthErrors.UserNameTaken);

        var user = new AppUser { UserName = userName, CreatedAt = DateTimeOffset.UtcNow };
        var createResult = await userManager.CreateAsync(user, password);
        if (!createResult.Succeeded)
        {
            return Result.Failure<Guid>(
                Error.Validation(
                    "Auth.CreateFailed",
                    string.Join("; ", createResult.Errors.Select(e => e.Description))
                )
            );
        }

        await userManager.AddToRoleAsync(user, RoleNames.User);
        return Result.Success(user.Id);
    }

    public async Task<Result<AuthenticatedUser>> ValidateCredentialsAsync(
        string userName,
        string password,
        CancellationToken cancellationToken
    )
    {
        var user = await userManager.FindByNameAsync(userName);
        if (user is null)
            return Result.Failure<AuthenticatedUser>(AuthErrors.InvalidCredentials);

        var checkResult = await signInManager.CheckPasswordSignInAsync(
            user,
            password,
            lockoutOnFailure: true
        );
        if (!checkResult.Succeeded)
            return Result.Failure<AuthenticatedUser>(AuthErrors.InvalidCredentials);

        var role = await GetPrimaryRoleAsync(user);
        return Result.Success(new AuthenticatedUser(user.Id, user.UserName!, role));
    }

    public async Task<CurrentUserProfile?> GetProfileAsync(
        Guid userId,
        CancellationToken cancellationToken
    )
    {
        var user = await userManager.FindByIdAsync(userId.ToString());
        if (user is null)
            return null;

        var role = await GetPrimaryRoleAsync(user);

        return new CurrentUserProfile(user.Id, user.UserName!, role, user.IsBlocked);
    }

    public async Task<Result> ChangePasswordAsync(
        Guid userId,
        string currentPassword,
        string newPassword,
        CancellationToken cancellationToken
    )
    {
        var user = await userManager.FindByIdAsync(userId.ToString());
        if (user is null)
            return Result.Failure(AuthErrors.Unauthorized);

        var result = await userManager.ChangePasswordAsync(user, currentPassword, newPassword);
        return result.Succeeded
            ? Result.Success()
            : Result.Failure(
                Error.Validation(
                    "Auth.ChangePasswordFailed",
                    string.Join("; ", result.Errors.Select(e => e.Description))
                )
            );
    }

    public async Task<Result> ResetPasswordAsync(
        Guid userId,
        string newPassword,
        CancellationToken cancellationToken
    )
    {
        var user = await userManager.FindByIdAsync(userId.ToString());
        if (user is null)
            return Result.Failure(UserErrors.NotFound);

        // Админ меняет чужой пароль — без текущего: сбрасываем через одноразовый токен сброса.
        var token = await userManager.GeneratePasswordResetTokenAsync(user);
        var result = await userManager.ResetPasswordAsync(user, token, newPassword);
        return result.Succeeded
            ? Result.Success()
            : Result.Failure(
                Error.Validation(
                    "Auth.ResetPasswordFailed",
                    string.Join("; ", result.Errors.Select(e => e.Description))
                )
            );
    }

    public async Task<Result> ChangeUserNameAsync(
        Guid userId,
        string newUserName,
        CancellationToken cancellationToken
    )
    {
        var user = await userManager.FindByIdAsync(userId.ToString());
        if (user is null)
            return Result.Failure(AuthErrors.Unauthorized);

        var existing = await userManager.FindByNameAsync(newUserName);
        if (existing is not null && existing.Id != userId)
            return Result.Failure(AuthErrors.UserNameTaken);

        var result = await userManager.SetUserNameAsync(user, newUserName);
        return result.Succeeded
            ? Result.Success()
            : Result.Failure(
                Error.Validation(
                    "Auth.ChangeUserNameFailed",
                    string.Join("; ", result.Errors.Select(e => e.Description))
                )
            );
    }

    public async Task<Result> DeleteUserAsync(Guid userId, CancellationToken cancellationToken)
    {
        var user = await userManager.FindByIdAsync(userId.ToString());
        if (user is null)
            return Result.Failure(UserErrors.NotFound);

        var result = await userManager.DeleteAsync(user);
        return result.Succeeded
            ? Result.Success()
            : Result.Failure(
                Error.Failure(
                    "Users.DeleteFailed",
                    string.Join("; ", result.Errors.Select(e => e.Description))
                )
            );
    }

    public async Task<Result> BlockUserAsync(Guid userId, CancellationToken cancellationToken)
    {
        var user = await userManager.FindByIdAsync(userId.ToString());
        if (user is null)
            return Result.Failure(UserErrors.NotFound);

        user.IsBlocked = true;
        await userManager.UpdateAsync(user);
        return Result.Success();
    }

    public async Task<Result> UnblockUserAsync(Guid userId, CancellationToken cancellationToken)
    {
        var user = await userManager.FindByIdAsync(userId.ToString());
        if (user is null)
            return Result.Failure(UserErrors.NotFound);

        user.IsBlocked = false;
        await userManager.UpdateAsync(user);
        return Result.Success();
    }

    public async Task<PagedList<UserSummaryDto>> ListUsersAsync(
        UserListFilter filter,
        CancellationToken cancellationToken
    )
    {
        var (page, pageSize, search, roleId, isBlocked, sort, desc) = filter;

        var query =
            from user in dbContext.Users
            join userRole in dbContext.UserRoles on user.Id equals userRole.UserId into userRoles
            from userRole in userRoles.DefaultIfEmpty()
            join role in dbContext.Roles on userRole.RoleId equals role.Id into roles
            from role in roles.DefaultIfEmpty()
            select new
            {
                user,
                RoleId = (Guid?)userRole.RoleId,
                RoleName = role != null ? role.Name : null,
            };

        if (!string.IsNullOrWhiteSpace(search))
            query = query.Where(x => x.user.UserName!.Contains(search));

        if (roleId is not null)
            query = query.Where(x => x.RoleId == roleId);

        if (isBlocked is not null)
            query = query.Where(x => x.user.IsBlocked == isBlocked);

        var total = await query.CountAsync(cancellationToken);

        var ordered = (sort?.ToLowerInvariant()) switch
        {
            "username" => desc
                ? query.OrderByDescending(x => x.user.UserName)
                : query.OrderBy(x => x.user.UserName),
            "role" => desc
                ? query.OrderByDescending(x => x.RoleName)
                : query.OrderBy(x => x.RoleName),
            "blocked" => desc
                ? query.OrderByDescending(x => x.user.IsBlocked)
                : query.OrderBy(x => x.user.IsBlocked),
            "created" => desc
                ? query.OrderByDescending(x => x.user.CreatedAt)
                : query.OrderBy(x => x.user.CreatedAt),
            _ => query.OrderByDescending(x => x.user.CreatedAt),
        };

        var items = await ordered
            .ThenBy(x => x.user.Id)
            .Skip((page - 1) * pageSize)
            .Take(pageSize)
            .Select(x => new UserSummaryDto(
                x.user.Id,
                x.user.UserName!,
                x.RoleName ?? RoleNames.User,
                x.user.IsBlocked,
                x.user.CreatedAt
            ))
            .ToListAsync(cancellationToken);

        return new PagedList<UserSummaryDto>(items, total, page, pageSize);
    }

    private async Task<string> GetPrimaryRoleAsync(AppUser user)
    {
        var roles = await userManager.GetRolesAsync(user);
        return roles.FirstOrDefault() ?? RoleNames.User;
    }
}
