using FluentValidation;
using MediatR;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using SolutionErp.Application.Common.Exceptions;
using SolutionErp.Application.Common.Interfaces;
using SolutionErp.Application.Common.Models;
using SolutionErp.Domain.Identity;
using ValidationException = SolutionErp.Application.Common.Exceptions.ValidationException;

namespace SolutionErp.Application.Users;

public record UserDto(
    Guid Id,
    string Email,
    string FullName,
    bool IsActive,
    bool IsLocked,
    DateTime CreatedAt,
    List<string> Roles,
    Guid? DepartmentId,
    string? DepartmentName,
    string? Position,
    bool CanBypassReview,
    int? PositionLevel);     // Mig 18 — 1=NV, 2=PP, 3=TP. Null cho admin/system/external user.

// ========== LIST ==========
public record ListUsersQuery : PagedRequest, IRequest<PagedResult<UserDto>>;

public class ListUsersQueryHandler(UserManager<User> userManager, IApplicationDbContext db, IDateTime dateTime)
    : IRequestHandler<ListUsersQuery, PagedResult<UserDto>>
{
    public async Task<PagedResult<UserDto>> Handle(ListUsersQuery request, CancellationToken ct)
    {
        var query = userManager.Users.AsNoTracking();
        if (!string.IsNullOrWhiteSpace(request.Search))
        {
            var s = request.Search.Trim();
            query = query.Where(u => u.Email!.Contains(s) || u.FullName.Contains(s));
        }
        query = request.SortDesc
            ? query.OrderByDescending(u => u.CreatedAt)
            : query.OrderBy(u => u.CreatedAt);

        var total = await query.CountAsync(ct);
        var users = await query
            .Skip((request.Page - 1) * request.PageSize)
            .Take(request.PageSize)
            .ToListAsync(ct);

        // Lookup department names cho denormalize trong DTO
        var deptIds = users.Where(u => u.DepartmentId is not null).Select(u => u.DepartmentId!.Value).Distinct().ToList();
        var deptNames = await db.Departments.AsNoTracking()
            .Where(d => deptIds.Contains(d.Id))
            .ToDictionaryAsync(d => d.Id, d => d.Name, ct);

        var items = new List<UserDto>(users.Count);
        var now = dateTime.UtcNow;
        foreach (var u in users)
        {
            var roles = await userManager.GetRolesAsync(u);
            var isLocked = u.LockoutEnd.HasValue && u.LockoutEnd.Value.UtcDateTime > now;
            string? deptName = u.DepartmentId is { } did && deptNames.TryGetValue(did, out var dn) ? dn : null;
            items.Add(new UserDto(u.Id, u.Email!, u.FullName, u.IsActive, isLocked, u.CreatedAt, roles.ToList(), u.DepartmentId, deptName, u.Position, u.CanBypassReview, (int?)u.PositionLevel));
        }

        return new PagedResult<UserDto>(items, total, request.Page, request.PageSize);
    }
}

// ========== GET ==========
public record GetUserQuery(Guid Id) : IRequest<UserDto>;

public class GetUserQueryHandler(UserManager<User> userManager, IApplicationDbContext db, IDateTime dateTime)
    : IRequestHandler<GetUserQuery, UserDto>
{
    public async Task<UserDto> Handle(GetUserQuery request, CancellationToken ct)
    {
        var u = await userManager.FindByIdAsync(request.Id.ToString())
            ?? throw new NotFoundException("User", request.Id);
        var roles = await userManager.GetRolesAsync(u);
        var isLocked = u.LockoutEnd.HasValue && u.LockoutEnd.Value.UtcDateTime > dateTime.UtcNow;
        string? deptName = null;
        if (u.DepartmentId is { } did)
            deptName = await db.Departments.AsNoTracking().Where(d => d.Id == did).Select(d => d.Name).FirstOrDefaultAsync(ct);
        return new UserDto(u.Id, u.Email!, u.FullName, u.IsActive, isLocked, u.CreatedAt, roles.ToList(), u.DepartmentId, deptName, u.Position, u.CanBypassReview, (int?)u.PositionLevel);
    }
}

// ========== CREATE ==========
public record CreateUserCommand(
    string Email, string FullName, string Password, List<string> Roles,
    Guid? DepartmentId = null, string? Position = null) : IRequest<Guid>;

public class CreateUserCommandValidator : AbstractValidator<CreateUserCommand>
{
    public CreateUserCommandValidator()
    {
        RuleFor(x => x.Email).NotEmpty().EmailAddress().MaximumLength(100);
        RuleFor(x => x.FullName).NotEmpty().MaximumLength(200);
        RuleFor(x => x.Password).NotEmpty().MinimumLength(8);
        RuleFor(x => x.Roles).NotNull();
    }
}

public class CreateUserCommandHandler(
    UserManager<User> userManager,
    RoleManager<Role> roleManager,
    IDateTime dateTime) : IRequestHandler<CreateUserCommand, Guid>
{
    public async Task<Guid> Handle(CreateUserCommand request, CancellationToken ct)
    {
        if (await userManager.FindByEmailAsync(request.Email) is not null)
            throw new ConflictException($"Email '{request.Email}' đã tồn tại.");

        var user = new User
        {
            UserName = request.Email,
            Email = request.Email,
            FullName = request.FullName,
            EmailConfirmed = true,
            IsActive = true,
            DepartmentId = request.DepartmentId,
            Position = request.Position,
            CreatedAt = dateTime.UtcNow,
        };
        var result = await userManager.CreateAsync(user, request.Password);
        if (!result.Succeeded)
            throw new ValidationException(result.Errors.Select(e => new FluentValidation.Results.ValidationFailure("Password", e.Description)));

        // Assign roles
        foreach (var roleName in request.Roles.Distinct())
        {
            if (await roleManager.RoleExistsAsync(roleName))
                await userManager.AddToRoleAsync(user, roleName);
        }

        return user.Id;
    }
}

// ========== UPDATE ==========
public record UpdateUserCommand(
    Guid Id, string FullName, bool IsActive,
    Guid? DepartmentId = null, string? Position = null) : IRequest;

public class UpdateUserCommandValidator : AbstractValidator<UpdateUserCommand>
{
    public UpdateUserCommandValidator()
    {
        RuleFor(x => x.Id).NotEmpty();
        RuleFor(x => x.FullName).NotEmpty().MaximumLength(200);
    }
}

public class UpdateUserCommandHandler(UserManager<User> userManager, ICurrentUser currentUser)
    : IRequestHandler<UpdateUserCommand>
{
    public async Task Handle(UpdateUserCommand request, CancellationToken ct)
    {
        var user = await userManager.FindByIdAsync(request.Id.ToString())
            ?? throw new NotFoundException("User", request.Id);

        // Self-disable protection: admin không thể tự deactivate
        if (!request.IsActive && user.Id == currentUser.UserId)
            throw new ForbiddenException("Không thể tự vô hiệu hóa tài khoản của mình.");

        user.FullName = request.FullName;
        user.IsActive = request.IsActive;
        user.DepartmentId = request.DepartmentId;
        user.Position = request.Position;
        user.UpdatedAt = DateTime.UtcNow;
        await userManager.UpdateAsync(user);
    }
}

// ========== ASSIGN ROLES (replace full set) ==========
public record AssignRolesCommand(Guid Id, List<string> Roles) : IRequest;

public class AssignRolesCommandValidator : AbstractValidator<AssignRolesCommand>
{
    public AssignRolesCommandValidator()
    {
        RuleFor(x => x.Id).NotEmpty();
        RuleFor(x => x.Roles).NotNull();
    }
}

public class AssignRolesCommandHandler(
    UserManager<User> userManager,
    RoleManager<Role> roleManager,
    ICurrentUser currentUser) : IRequestHandler<AssignRolesCommand>
{
    public async Task Handle(AssignRolesCommand request, CancellationToken ct)
    {
        var user = await userManager.FindByIdAsync(request.Id.ToString())
            ?? throw new NotFoundException("User", request.Id);

        var current = await userManager.GetRolesAsync(user);
        var target = request.Roles.Distinct().ToList();

        // Self-demote protection: admin không thể tự gỡ role Admin khỏi mình
        if (user.Id == currentUser.UserId
            && current.Contains(AppRoles.Admin)
            && !target.Contains(AppRoles.Admin))
        {
            throw new ForbiddenException("Không thể tự gỡ role Admin khỏi mình.");
        }

        var toRemove = current.Except(target).ToList();
        var toAdd = target.Except(current).ToList();

        if (toRemove.Count > 0) await userManager.RemoveFromRolesAsync(user, toRemove);
        foreach (var roleName in toAdd)
        {
            if (await roleManager.RoleExistsAsync(roleName))
                await userManager.AddToRoleAsync(user, roleName);
        }
    }
}

// ========== RESET PASSWORD (admin) ==========
public record ResetPasswordCommand(Guid Id, string NewPassword) : IRequest;

public class ResetPasswordCommandValidator : AbstractValidator<ResetPasswordCommand>
{
    public ResetPasswordCommandValidator()
    {
        RuleFor(x => x.Id).NotEmpty();
        RuleFor(x => x.NewPassword).NotEmpty().MinimumLength(8);
    }
}

public class ResetPasswordCommandHandler(UserManager<User> userManager)
    : IRequestHandler<ResetPasswordCommand>
{
    public async Task Handle(ResetPasswordCommand request, CancellationToken ct)
    {
        var user = await userManager.FindByIdAsync(request.Id.ToString())
            ?? throw new NotFoundException("User", request.Id);

        // Admin flow: remove + set password (không cần token)
        await userManager.RemovePasswordAsync(user);
        var result = await userManager.AddPasswordAsync(user, request.NewPassword);
        if (!result.Succeeded)
            throw new ValidationException(result.Errors.Select(e => new FluentValidation.Results.ValidationFailure("NewPassword", e.Description)));

        // Invalidate refresh token → force re-login
        user.RefreshToken = null;
        user.RefreshTokenExpiresAt = null;
        await userManager.UpdateAsync(user);

        // Unlock nếu đang lock
        await userManager.ResetAccessFailedCountAsync(user);
        await userManager.SetLockoutEndDateAsync(user, null);
    }
}

// ========== UNLOCK ==========
public record UnlockUserCommand(Guid Id) : IRequest;

public class UnlockUserCommandHandler(UserManager<User> userManager) : IRequestHandler<UnlockUserCommand>
{
    public async Task Handle(UnlockUserCommand request, CancellationToken ct)
    {
        var user = await userManager.FindByIdAsync(request.Id.ToString())
            ?? throw new NotFoundException("User", request.Id);
        await userManager.SetLockoutEndDateAsync(user, null);
        await userManager.ResetAccessFailedCountAsync(user);
    }
}

// ========== SET BYPASS REVIEW (Phase 9 — Migration 16) ==========
// Admin toggle CanBypassReview cho 1 user. Khi true, user (NV) được duyệt
// thay TPB ở 2-stage department approval (skip Stage Review, đẩy thẳng
// Stage Confirm). Mặc định false (an toàn).
public record SetUserBypassReviewCommand(Guid Id, bool CanBypassReview) : IRequest;

public class SetUserBypassReviewCommandHandler(UserManager<User> userManager)
    : IRequestHandler<SetUserBypassReviewCommand>
{
    public async Task Handle(SetUserBypassReviewCommand request, CancellationToken ct)
    {
        var user = await userManager.FindByIdAsync(request.Id.ToString())
            ?? throw new NotFoundException("User", request.Id);
        user.CanBypassReview = request.CanBypassReview;
        var result = await userManager.UpdateAsync(user);
        if (!result.Succeeded)
            throw new ConflictException(string.Join("; ", result.Errors.Select(e => e.Description)));
    }
}

// ========== SET POSITION LEVEL (Mig 18) ==========
// Admin set cấp chức danh trong phòng cho user phục vụ N-stage workflow inner
// step matching. Nullable — null cho admin/system/external user. Chỉ accept
// 1 (NV) / 2 (PP) / 3 (TP) hoặc null. Không kiểm role tương ứng — admin
// tự chịu trách nhiệm map đúng.
public record SetUserPositionLevelCommand(Guid Id, int? PositionLevel) : IRequest;

public class SetUserPositionLevelCommandValidator : AbstractValidator<SetUserPositionLevelCommand>
{
    public SetUserPositionLevelCommandValidator()
    {
        RuleFor(x => x.PositionLevel)
            .Must(v => v == null || (v >= 1 && v <= 3))
            .WithMessage("PositionLevel chỉ chấp nhận null hoặc 1=NV, 2=PP, 3=TP.");
    }
}

public class SetUserPositionLevelCommandHandler(UserManager<User> userManager)
    : IRequestHandler<SetUserPositionLevelCommand>
{
    public async Task Handle(SetUserPositionLevelCommand request, CancellationToken ct)
    {
        var user = await userManager.FindByIdAsync(request.Id.ToString())
            ?? throw new NotFoundException("User", request.Id);
        user.PositionLevel = request.PositionLevel == null
            ? null
            : (PositionLevel?)request.PositionLevel.Value;
        var result = await userManager.UpdateAsync(user);
        if (!result.Succeeded)
            throw new ConflictException(string.Join("; ", result.Errors.Select(e => e.Description)));
    }
}

// Mig 31 (S23 t1 Plan K Chunk D) — DELETED F2 Command + Handler per-Drafter.
// F2 semantic + storage refactor sang ApprovalWorkflowLevels (per-Approver
// slot, admin opt-in qua Workflow Designer). Plan K backout zombie Command.
// Replaced bởi Workflow Designer per-Level toggle trong ApprovalWorkflowV2AdminFeatures.
