package com.brframework.commoncms.utils;

import com.brframework.commoncms.annatotion.AdminMenu;
import com.brframework.commoncms.annatotion.Panel;
import com.brframework.commoncms.annatotion.layout.ListLayout;
import com.brframework.commoncms.annatotion.layout.PageLayout;
import com.brframework.commoncms.annatotion.option.CmsOption;
import com.brframework.commoncms.core.UriProtocol;
import com.brframework.commoncms.core.permission.Menu;
import com.brframework.commoncms.core.permission.Permission;
import com.brframework.commoncms.core.uri.PanelUri;
import com.brframework.commoncms.utils.ControllerUtil;
import com.brframework.commonweb.core.SpringContext;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Service;

import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 用户-角色-菜单
 *
 * @author xu
 * @date 2019/11/6 16:12
 */
public class MenuPermissionUtils {

    private static List<Menu> holdMenus;
    private static List<Permission> holdPermissions;

    /**
     * 获取所有的功能列表
     *
     * @return
     */
    public static List<Menu> getMenus() {
        if (holdMenus != null) {
            return holdMenus;
        }
        holdMenus = getBeanByAdminMenu().stream().map(bean -> {
            AdminMenu annotation = AnnotationUtils.findAnnotation(bean.getClass(), AdminMenu.class);
            assert annotation != null;
            List<Menu> childrenMenus = Lists.newArrayList(bean.getClass().getMethods()).stream().filter(method ->
                    //过滤没有AdminMenu注解的Method
                    AnnotationUtils.findAnnotation(method, AdminMenu.class) != null
            ).map(method -> {
                //Method to Menu
                AdminMenu menuChildren = AnnotationUtils.findAnnotation(method, AdminMenu.class);
                assert menuChildren != null;
                PreAuthorize authAnn = AnnotationUtils.findAnnotation(method, PreAuthorize.class);
                String role = authAnn != null ? getRoleName(authAnn.value()) : null;
                String url = StringUtils.isEmpty(menuChildren.url()) ? getMenuUriByMethod(method) : menuChildren.url();

                return Menu.builder()
                        .role(role)
                        .title(menuChildren.menuName())
                        .url(url)
                        .order(menuChildren.order())
                        .build();
            }).collect(Collectors.toList());
            return Menu.builder()
                    .title(annotation.menuName())
                    .icon(annotation.icon())
                    .order(annotation.order())
                    .children(childrenMenus.stream().sorted(Comparator.comparing(Menu::getOrder)).collect(Collectors.toList()))
                    .build();
        }).sorted(Comparator.comparing(Menu::getOrder)).collect(Collectors.toList());
        return holdMenus;
    }

    /**
     * 获取系统中的所有权限
     * @return
     */
    public static List<Permission> getPermissions() {
        if (holdPermissions != null) {
            return holdPermissions;
        }

        holdPermissions = getBeanByAdminMenu().stream().map(bean -> {
            AdminMenu annotation = AnnotationUtils.findAnnotation(bean.getClass(), AdminMenu.class);
            assert annotation != null;
            List<Method> methods = Lists.newArrayList(bean.getClass().getMethods());
            List<Permission> methodPermissions = methods.stream().filter(method ->
                    //过滤没有AdminMenu注解的Method
                    AnnotationUtils.findAnnotation(method, AdminMenu.class) != null
            ).map(method -> {
                //Method to Permission
                AdminMenu menuChildren = AnnotationUtils.findAnnotation(method, AdminMenu.class);
                assert menuChildren != null;

                List<CmsOption> options = getOptionByMenuMethod(method);
                Map<String, Permission> childrenPermissions = Maps.newHashMap();

                //处理uriMappingMethod相关的Option
                options.stream().filter(o -> StringUtils.isNotBlank(o.uriMappingMethod()))
                        .map(o -> methods.stream().filter(m -> m.getName().equals(o.uriMappingMethod()))
                                .filter(m -> AnnotationUtils.findAnnotation(m, PreAuthorize.class) != null)
                                .map(MenuPermissionUtils::preAuthorizeToPermission).collect(Collectors.toList()))
                        .flatMap(Collection::stream)
                        .filter(p -> !childrenPermissions.containsKey(p.getRole()))
                        .forEach(p -> childrenPermissions.put(p.getRole(), p));

                //处理roleMappingMethod相关的Option
                options.stream().filter(o -> StringUtils.isNotBlank(o.roleMappingMethod()))
                        .map(o -> methods.stream().filter(m -> m.getName().equals(o.roleMappingMethod()))
                                .filter(m -> AnnotationUtils.findAnnotation(m, PreAuthorize.class) != null)
                                .map(MenuPermissionUtils::preAuthorizeToPermission).collect(Collectors.toList()))
                        .flatMap(Collection::stream)
                        .filter(p -> !childrenPermissions.containsKey(p.getRole()))
                        .forEach(p -> childrenPermissions.put(p.getRole(), p));

                //处理role相关的Option
                options.stream().filter(o -> StringUtils.isNotBlank(o.role()))
                        .filter(o -> !childrenPermissions.containsKey(o.role()))
                        .forEach(o -> childrenPermissions.put(o.role(), Permission.builder()
                                .title(o.roleName())
                                .role(o.role())
                                .build()));

                PreAuthorize authAnn = AnnotationUtils.findAnnotation(method, PreAuthorize.class);
                if (authAnn != null) {
                    String childrenTitle = ControllerUtil.getTitle(method);
                    String childrenPermission = getRoleName(authAnn.value());
                    if (childrenPermissions.containsKey(childrenPermission)) {
                    } else {
                        childrenPermissions.put(childrenPermission, Permission.builder()
                                .title(childrenTitle)
                                .role(childrenPermission)
                                .build());
                    }
                }

                return Permission.builder()
                        .title(menuChildren.menuName())
                        .children(Lists.newArrayList(childrenPermissions.values()))
                        .build();

            }).collect(Collectors.toList());
            return Permission.builder()
                    .title(annotation.menuName())
                    .children(methodPermissions)
                    .build();
        }).collect(Collectors.toList());
        return holdPermissions;
    }

    /**
     * 截取权限字符串
     *
     * @param roleString
     * @return
     */
    public static String getRoleName(String roleString) {
        return StringUtils.substring(roleString, roleString.indexOf("'") + 1, roleString.lastIndexOf("'")).trim();
    }

    private static String getMenuUriByMethod(Method method) {
        PanelUri path = new PanelUri();
        path.setMethod(ControllerUtil.getHttpMethod(method));
        path.setUrl(ControllerUtil.getControllerUrl(method));
        return UriProtocol.builder()
                .protocol(UriProtocol.PANEL)
                .path(path.toString())
                .build().toString();
    }

    private static List<Object> getBeanByAdminMenu() {
        Map<String, Object> beansWithAnnotation = SpringContext.getApplicationContext().getBeansWithAnnotation(AdminMenu.class);
        return Lists.newArrayList(beansWithAnnotation.values());
    }

    private static List<CmsOption> getOptionByMenuMethod(Method method) {
        //权限粒度到每一个option
        PageLayout pageLayout = AnnotatedElementUtils.findMergedAnnotation(method, PageLayout.class);
        ListLayout listLayout = AnnotatedElementUtils.findMergedAnnotation(method, ListLayout.class);

        List<CmsOption> optionList = Lists.newArrayList();
        if (pageLayout != null) {
            optionList.addAll(Lists.newArrayList(pageLayout.options()));
            optionList.addAll(Lists.newArrayList(pageLayout.columnOptions()));
        } else if (listLayout != null) {
            optionList.addAll(Lists.newArrayList(listLayout.options()));
            optionList.addAll(Lists.newArrayList(listLayout.columnOptions()));
        }
        return optionList;
    }

    private static Permission preAuthorizeToPermission(Method method) {
        PreAuthorize annotation = AnnotationUtils.findAnnotation(method, PreAuthorize.class);
        String childrenTitle = ControllerUtil.getTitle(method);
        String role = getRoleName(annotation.value());
        return Permission.builder()
                .title(childrenTitle)
                .role(role)
                .build();
    }
}
