package com.izouma.uwip.web;

import com.izouma.uwip.domain.BaseEntity;
import com.izouma.uwip.domain.Menu;
import com.izouma.uwip.enums.AuthorityName;
import com.izouma.uwip.repo.MenuRepo;
import com.izouma.uwip.security.Authority;
import lombok.AllArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*;

import javax.persistence.criteria.Predicate;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@RestController
@RequestMapping("/menu")
@AllArgsConstructor
public class MenuController extends BaseController {
    private final MenuRepo menuRepo;

    @GetMapping("/userMenu")
    public List<Menu> userMenu() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        List<Menu> menuList = menuRepo.authorityMenus(authentication.getAuthorities()
                .stream()
                .map(GrantedAuthority::getAuthority)
                .collect(Collectors.toList()))
                .stream()
                .map(Menu::from)
                .collect(Collectors.toList());

        List<Menu> root = new ArrayList<>();

        for (Menu menu : menuList) {
            if (menu.getRoot()) {
                root.add(menu);
            } else {
                Menu parent = menuList.stream().filter(m -> m.getId().equals(menu.getParent())).findAny().orElse(null);
                if (parent != null) {
                    if (parent.getChildren() == null) {
                        parent.setChildren(new ArrayList<>());
                    }
                    parent.getChildren().add(menu);
                }
            }
        }

        sortMenu(root);
        return root;
    }

    @GetMapping("/authority/{name}/get")
    public List<Long> getAuthorityMenus(@PathVariable String name) {
        return menuRepo.findAll((Specification<Menu>) (root, criteriaQuery, criteriaBuilder) -> {
            List<Predicate> predicates = new ArrayList<>();
            predicates.add(criteriaBuilder.isMember(Authority.get(AuthorityName.valueOf(name)), root.get("authorities")));
            return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
        }).stream().map(BaseEntity::getId).collect(Collectors.toList());
    }

    @PostMapping("/authority/{name}/save")
    public void saveAuthorityMenus(@PathVariable String name, @RequestBody List<Long> ids) {
        menuRepo.clearAuthority(name);
        ids.stream().parallel().forEach(id -> menuRepo.saveAuthority(name, id));
    }

    @PreAuthorize("hasRole('ADMIN')")
    @GetMapping("/all")
    public List<Menu> all(String category) {
        List<Menu> menuList = menuRepo.findAll((Specification<Menu>) (root, criteriaQuery, criteriaBuilder) -> {
            List<Predicate> predicates = new ArrayList<>();
            predicates.add(criteriaBuilder.equal(root.get("root"), true));
            if (StringUtils.isNotBlank(category)) {
                predicates.add(criteriaBuilder.equal(root.get("category"), category));
            }
            return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
        });
        sortMenu(menuList);
        return menuList;
    }

    @PreAuthorize("hasRole('ADMIN')")
    @PostMapping("/save")
    public Menu save(@RequestBody Menu menu) {
        if (menu.getSort() == null) {
            menu.setSort(menuRepo.nextSort());
        }
        menuRepo.save(menu);
        return menu;
    }

    @PreAuthorize("hasRole('ADMIN')")
    @GetMapping("/groupByCategory")
    public Map<String, List<Menu>> groupByCategory() {
        return menuRepo.findAll().stream()
                .filter(menu -> menu.getCategory() != null)
                .collect(Collectors.groupingBy(Menu::getCategory));
    }

    @PreAuthorize("hasRole('ADMIN')")
    @GetMapping("/categories")
    public List<String> categories() {
        return menuRepo.categories().stream().filter(StringUtils::isNotBlank).collect(Collectors.toList());
    }

    private void sortMenu(List<Menu> menus) {
        menus.sort(Comparator.comparingInt(Menu::getSort));
        for (Menu menu : menus) {
            if (menu.getChildren() != null) {
                sortMenu(menu.getChildren());
            }
        }
    }
}
