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.Queries.GetMyMenuTree;

public record GetMyMenuTreeQuery : IRequest<List<MenuNodeDto>>;

public class GetMyMenuTreeQueryHandler(
    IApplicationDbContext db,
    ICurrentUser currentUser,
    UserManager<User> userManager,
    RoleManager<Role> roleManager) : IRequestHandler<GetMyMenuTreeQuery, List<MenuNodeDto>>
{
    public async Task<List<MenuNodeDto>> Handle(GetMyMenuTreeQuery request, CancellationToken ct)
    {
        if (!currentUser.IsAuthenticated || currentUser.UserId is null)
            throw new UnauthorizedException();

        var user = await userManager.FindByIdAsync(currentUser.UserId.Value.ToString())
            ?? throw new UnauthorizedException();
        var roleNames = await userManager.GetRolesAsync(user);

        var roleIds = new List<Guid>();
        foreach (var name in roleNames)
        {
            var role = await roleManager.FindByNameAsync(name);
            if (role is not null) roleIds.Add(role.Id);
        }

        var menus = await db.MenuItems.AsNoTracking().OrderBy(m => m.Order).ToListAsync(ct);
        var perms = await db.Permissions.AsNoTracking()
            .Where(p => roleIds.Contains(p.RoleId))
            .ToListAsync(ct);

        // Union CRUD flags qua các role
        var resolved = perms
            .GroupBy(p => p.MenuKey)
            .ToDictionary(g => g.Key, g => (
                Read: g.Any(p => p.CanRead),
                Create: g.Any(p => p.CanCreate),
                Update: g.Any(p => p.CanUpdate),
                Delete: g.Any(p => p.CanDelete)));

        // Build tree. Descendants of certain roots inherit parent perms so
        // we don't add per-subitem permission rows for pure-navigation menus.
        // Inherit roots: Contracts (Ct_*), Workflows (Wf_*), PurchaseEvaluations
        // (Pe_*), PeWorkflows (PeWf_*).
        (bool Read, bool Create, bool Update, bool Delete) GetFlags(string key) =>
            resolved.TryGetValue(key, out var f) ? f : (false, false, false, false);

        var contractsFlags    = GetFlags(MenuKeys.Contracts);
        var workflowsFlags    = GetFlags(MenuKeys.Workflows);
        var peFlags           = GetFlags(MenuKeys.PurchaseEvaluations);
        var peWorkflowsFlags  = GetFlags(MenuKeys.PeWorkflows);

        List<MenuNodeDto> BuildChildren(string? parentKey, string? inheritFromKey) => menus
            .Where(m => m.ParentKey == parentKey)
            .Select(m =>
            {
                var flags = resolved.TryGetValue(m.Key, out var f) ? f : (false, false, false, false);
                if (inheritFromKey is not null && !resolved.ContainsKey(m.Key))
                {
                    flags = inheritFromKey switch
                    {
                        var k when k == MenuKeys.Contracts           => contractsFlags,
                        var k when k == MenuKeys.Workflows           => workflowsFlags,
                        var k when k == MenuKeys.PurchaseEvaluations => peFlags,
                        var k when k == MenuKeys.PeWorkflows         => peWorkflowsFlags,
                        _ => flags,
                    };
                }

                // Propagate inheritance downward from 4 inherit roots
                var nextInherit = inheritFromKey
                    ?? (m.Key == MenuKeys.Contracts           ? MenuKeys.Contracts
                        : m.Key == MenuKeys.Workflows         ? MenuKeys.Workflows
                        : m.Key == MenuKeys.PurchaseEvaluations ? MenuKeys.PurchaseEvaluations
                        : m.Key == MenuKeys.PeWorkflows       ? MenuKeys.PeWorkflows
                        : null);

                return new MenuNodeDto(m.Key, m.Label, m.ParentKey, m.Order, m.Icon,
                    flags.Item1, flags.Item2, flags.Item3, flags.Item4,
                    m.IsVisible, m.DisplayLabel,
                    BuildChildren(m.Key, nextInherit));
            })
            .ToList();

        var tree = BuildChildren(null, inheritFromKey: null);

        // Filter: chỉ trả về node có CanRead=true (hoặc có child CanRead=true)
        static bool HasAccess(MenuNodeDto n) => n.CanRead || n.Children.Any(HasAccess);
        List<MenuNodeDto> Filter(List<MenuNodeDto> nodes) => nodes
            .Where(HasAccess)
            .Select(n => n with { Children = Filter(n.Children) })
            .ToList();

        return Filter(tree);
    }
}
