using FluentValidation;
using MediatR;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using SolutionErp.Application.Common.Exceptions;
using SolutionErp.Application.Common.Interfaces;
using SolutionErp.Application.Permissions.Dtos;
using SolutionErp.Domain.Identity;

namespace SolutionErp.Application.Permissions;

// ========== List menu items (for matrix page) ==========

public record ListMenuItemsQuery : IRequest<List<MenuItemDto>>;

public class ListMenuItemsQueryHandler(IApplicationDbContext db) : IRequestHandler<ListMenuItemsQuery, List<MenuItemDto>>
{
    public async Task<List<MenuItemDto>> Handle(ListMenuItemsQuery request, CancellationToken ct)
    {
        return await db.MenuItems.AsNoTracking()
            .OrderBy(m => m.Order)
            .Select(m => new MenuItemDto(m.Key, m.Label, m.ParentKey, m.Order, m.Icon, m.IsVisible, m.DisplayLabel))
            .ToListAsync(ct);
    }
}

// ========== Update menu visibility + display label (admin only) ==========
// Session 20 Mig 27: admin Ẩn/Hiện + Đổi tên hiển thị menu cho fe-user (eOffice).
// fe-admin sidebar luôn render Label gốc (KHÔNG dùng DisplayLabel) — user Q2=b.
public record UpdateMenuItemCommand(
    string Key,
    bool IsVisible,
    string? DisplayLabel) : IRequest;

public class UpdateMenuItemCommandValidator : AbstractValidator<UpdateMenuItemCommand>
{
    public UpdateMenuItemCommandValidator()
    {
        RuleFor(x => x.Key).NotEmpty().MaximumLength(50);
        RuleFor(x => x.DisplayLabel).MaximumLength(200);
    }
}

public class UpdateMenuItemCommandHandler(IApplicationDbContext db) : IRequestHandler<UpdateMenuItemCommand>
{
    public async Task Handle(UpdateMenuItemCommand request, CancellationToken ct)
    {
        var menu = await db.MenuItems.FirstOrDefaultAsync(m => m.Key == request.Key, ct)
            ?? throw new NotFoundException("MenuItem", request.Key);

        menu.IsVisible = request.IsVisible;
        menu.DisplayLabel = string.IsNullOrWhiteSpace(request.DisplayLabel)
            ? null
            : request.DisplayLabel.Trim();

        await db.SaveChangesAsync(ct);
    }
}

// ========== List permissions by role (matrix edit) ==========

public record ListPermissionsByRoleQuery(Guid RoleId) : IRequest<List<PermissionDto>>;

public class ListPermissionsByRoleQueryHandler(IApplicationDbContext db) : IRequestHandler<ListPermissionsByRoleQuery, List<PermissionDto>>
{
    public async Task<List<PermissionDto>> Handle(ListPermissionsByRoleQuery request, CancellationToken ct)
    {
        return await db.Permissions.AsNoTracking()
            .Where(p => p.RoleId == request.RoleId)
            .Select(p => new PermissionDto(p.Id, p.RoleId, p.MenuKey, p.CanRead, p.CanCreate, p.CanUpdate, p.CanDelete))
            .ToListAsync(ct);
    }
}

// ========== Upsert permission (admin update matrix) ==========

public record UpsertPermissionCommand(
    Guid RoleId,
    string MenuKey,
    bool CanRead,
    bool CanCreate,
    bool CanUpdate,
    bool CanDelete) : IRequest;

public class UpsertPermissionCommandValidator : AbstractValidator<UpsertPermissionCommand>
{
    public UpsertPermissionCommandValidator()
    {
        RuleFor(x => x.RoleId).NotEmpty();
        RuleFor(x => x.MenuKey).NotEmpty().MaximumLength(50);
    }
}

public class UpsertPermissionCommandHandler(
    IApplicationDbContext db,
    RoleManager<Role> roleManager,
    ICurrentUser currentUser) : IRequestHandler<UpsertPermissionCommand>
{
    public async Task Handle(UpsertPermissionCommand request, CancellationToken ct)
    {
        var role = await roleManager.FindByIdAsync(request.RoleId.ToString())
            ?? throw new NotFoundException("Role", request.RoleId);

        // Guard: không cho tự xóa quyền của role Admin (nếu người đang edit là Admin)
        if (role.Name == AppRoles.Admin && currentUser.Roles.Contains(AppRoles.Admin))
        {
            if (!(request.CanRead && request.CanCreate && request.CanUpdate && request.CanDelete))
                throw new ForbiddenException("Không thể giảm quyền của role Admin khi bạn đang là Admin.");
        }

        var menu = await db.MenuItems.FirstOrDefaultAsync(m => m.Key == request.MenuKey, ct)
            ?? throw new NotFoundException("MenuItem", request.MenuKey);

        var existing = await db.Permissions.FirstOrDefaultAsync(
            p => p.RoleId == request.RoleId && p.MenuKey == request.MenuKey, ct);

        if (existing is null)
        {
            db.Permissions.Add(new Permission
            {
                RoleId = request.RoleId,
                MenuKey = request.MenuKey,
                CanRead = request.CanRead,
                CanCreate = request.CanCreate,
                CanUpdate = request.CanUpdate,
                CanDelete = request.CanDelete,
            });
        }
        else
        {
            existing.CanRead = request.CanRead;
            existing.CanCreate = request.CanCreate;
            existing.CanUpdate = request.CanUpdate;
            existing.CanDelete = request.CanDelete;
        }

        await db.SaveChangesAsync(ct);
    }
}

// ========== List all roles ==========

public record ListRolesQuery : IRequest<List<RoleDto>>;

public class ListRolesQueryHandler(RoleManager<Role> roleManager) : IRequestHandler<ListRolesQuery, List<RoleDto>>
{
    public async Task<List<RoleDto>> Handle(ListRolesQuery request, CancellationToken ct)
    {
        return await roleManager.Roles
            .OrderBy(r => r.Name)
            .Select(r => new RoleDto(r.Id, r.Name!, r.ShortName, r.Description, r.CreatedAt))
            .ToListAsync(ct);
    }
}

// ========== Create custom role ==========
// Hardcoded roles (AppRoles.All) đã seed lúc startup. Endpoint này cho phép
// admin tạo role tùy chỉnh ngoài 12 mặc định (vd Auditor, ITSupport, Reception,
// ContractManager — phục vụ nhu cầu tổ chức cụ thể).
public record CreateRoleCommand(string Name, string? ShortName, string? Description) : IRequest<Guid>;

public class CreateRoleCommandValidator : AbstractValidator<CreateRoleCommand>
{
    public CreateRoleCommandValidator()
    {
        RuleFor(x => x.Name).NotEmpty().MaximumLength(100)
            .Matches("^[A-Za-z][A-Za-z0-9_]*$")
            .WithMessage("Mã role chỉ chứa chữ + số + underscore, bắt đầu bằng chữ.");
        RuleFor(x => x.ShortName).MaximumLength(50);
        RuleFor(x => x.Description).MaximumLength(500);
    }
}

public class CreateRoleCommandHandler(RoleManager<Role> roleManager) : IRequestHandler<CreateRoleCommand, Guid>
{
    public async Task<Guid> Handle(CreateRoleCommand req, CancellationToken ct)
    {
        if (await roleManager.RoleExistsAsync(req.Name))
            throw new ConflictException($"Role '{req.Name}' đã tồn tại.");

        var role = new Role
        {
            Name = req.Name,
            ShortName = req.ShortName,
            Description = req.Description,
            CreatedAt = DateTime.UtcNow,
        };
        var result = await roleManager.CreateAsync(role);
        if (!result.Succeeded)
            throw new SolutionErp.Application.Common.Exceptions.ValidationException(result.Errors.Select(e =>
                new FluentValidation.Results.ValidationFailure("Role", e.Description)));
        return role.Id;
    }
}

// ========== Update role labels ==========
// CHỈ update ShortName + Description. Không update Name (Identity FK trong
// UserRoles, Permission, WorkflowStepApprover.AssignmentValue, [Authorize]
// attr — đổi tên = data corruption widespread).
public record UpdateRoleCommand(Guid Id, string? ShortName, string? Description) : IRequest;

public class UpdateRoleCommandValidator : AbstractValidator<UpdateRoleCommand>
{
    public UpdateRoleCommandValidator()
    {
        RuleFor(x => x.ShortName).MaximumLength(50);
        RuleFor(x => x.Description).MaximumLength(500);
    }
}

public class UpdateRoleCommandHandler(RoleManager<Role> roleManager) : IRequestHandler<UpdateRoleCommand>
{
    public async Task Handle(UpdateRoleCommand req, CancellationToken ct)
    {
        var role = await roleManager.FindByIdAsync(req.Id.ToString())
            ?? throw new NotFoundException("Role", req.Id);
        role.ShortName = req.ShortName;
        role.Description = req.Description;
        var result = await roleManager.UpdateAsync(role);
        if (!result.Succeeded)
            throw new SolutionErp.Application.Common.Exceptions.ValidationException(result.Errors.Select(e =>
                new FluentValidation.Results.ValidationFailure("Role", e.Description)));
    }
}

// ========== Delete custom role ==========
// Block delete nếu:
// - Role thuộc AppRoles.All (hardcoded — workflow guard reference)
// - Còn user assigned (UserRoles FK)
public record DeleteRoleCommand(Guid Id) : IRequest;

public class DeleteRoleCommandHandler(
    RoleManager<Role> roleManager,
    UserManager<User> userManager) : IRequestHandler<DeleteRoleCommand>
{
    public async Task Handle(DeleteRoleCommand req, CancellationToken ct)
    {
        var role = await roleManager.FindByIdAsync(req.Id.ToString())
            ?? throw new NotFoundException("Role", req.Id);

        if (AppRoles.All.Contains(role.Name))
            throw new ConflictException(
                $"Không xóa được role '{role.Name}' — thuộc 12 role mặc định, " +
                "có tham chiếu trong workflow guard + [Authorize] attribute.");

        // Check user assigned
        var users = await userManager.GetUsersInRoleAsync(role.Name!);
        if (users.Count > 0)
            throw new ConflictException(
                $"Role '{role.Name}' đang gán cho {users.Count} user — bỏ gán trước khi xóa.");

        var result = await roleManager.DeleteAsync(role);
        if (!result.Succeeded)
            throw new SolutionErp.Application.Common.Exceptions.ValidationException(result.Errors.Select(e =>
                new FluentValidation.Results.ValidationFailure("Role", e.Description)));
    }
}
