using SharedKernel.Constants;

namespace Admin.Middlewares;

public class MenuAuthorizationMiddleware(RequestDelegate next)
{
    // 메뉴 경로 → 허용 역할 매핑 (앱 시작 시 1회 빌드)
    private static readonly Lazy<List<(string Path, List<string> Roles)>> MenuRoleMap = new(() =>
    {
        var result = new List<(string Path, List<string> Roles)>();
        var menus = Menus.GetMenus();
        CollectMenuRoles(menus, result);

        // 긴 경로 우선 매칭되도록 내림차순 정렬
        result.Sort((a, b) => b.Path.Length.CompareTo(a.Path.Length));

        return result;
    });

    public async Task InvokeAsync(HttpContext context)
    {
        // 인증되지 않은 사용자는 Identity가 처리 (로그인 페이지로 이동)
        if (context.User.Identity is not { IsAuthenticated: true })
        {
            await next(context);
            return;
        }

        var path = context.Request.Path.Value ?? "";

        // 정적 파일, Identity, Error 페이지는 권한 체크 제외
        if (ShouldSkip(path))
        {
            await next(context);
            return;
        }

        // 메뉴에 등록된 경로인지 확인
        var matchedRoles = FindMatchingRoles(path);

        if (matchedRoles != null)
        {
            // 역할 중 하나라도 가지고 있으면 허용
            var hasAccess = matchedRoles.Any(role => context.User.IsInRole(role));

            if (!hasAccess)
            {
                context.Response.StatusCode = 403;
                context.Response.Redirect("/Identity/Account/AccessDenied");
                return;
            }
        }

        await next(context);
    }

    private static List<string>? FindMatchingRoles(string path)
    {
        var trimmedPath = path.TrimEnd('/');

        if (string.IsNullOrEmpty(trimmedPath))
        {
            return null;
        }

        foreach (var (menuPath, roles) in MenuRoleMap.Value)
        {
            if (trimmedPath.Equals(menuPath, StringComparison.OrdinalIgnoreCase) || trimmedPath.StartsWith(menuPath + "/", StringComparison.OrdinalIgnoreCase))
            {
                return roles;
            }
        }

        return null;
    }

    // 권한 체크 제외 경로
    private static readonly string[] SkipPrefixes =
    [
        "/Identity/",
        "/lib/",
        "/css/",
        "/js/",
        "/images/",
        "/favicon",
        "/_framework",
        "/_blazor",
        "/.well-known/",
        "/Error"
    ];

    private static bool ShouldSkip(string path)
    {
        // 확장자가 있으면 정적 파일로 간주
        var lastSegment = path.AsSpan(path.LastIndexOf('/') + 1);
        if (lastSegment.Contains('.'))
        {
            return true;
        }

        foreach (var prefix in SkipPrefixes)
        {
            if (path.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
            {
                return true;
            }
        }

        return false;
    }

    // 메뉴 트리에서 경로와 역할을 수집하는 재귀 메서드
    private static void CollectMenuRoles(List<Menu> menus, List<(string Path, List<string> Roles)> result)
    {
        foreach (var menu in menus)
        {
            if (!string.IsNullOrEmpty(menu.Path) && menu.Roles is { Count: > 0 })
            {
                result.Add((menu.Path.TrimEnd('/'), menu.Roles));
            }

            if (menu.Children is { Count: > 0 })
            {
                CollectMenuRoles(menu.Children, result);
            }
        }
    }
}