package vn.azteam.tpf.web.rest;

import com.codahale.metrics.annotation.Timed;
import com.google.common.base.Enums;
import io.github.jhipster.service.filter.StringFilter;
import io.github.jhipster.web.util.ResponseUtil;
import org.casbin.jcasbin.main.Enforcer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import vn.azteam.tpf.Constants.Constants;
import vn.azteam.tpf.domain.TBFunction;
import vn.azteam.tpf.repository.CasbinRepository;
import vn.azteam.tpf.service.TBFunctionService;
import vn.azteam.tpf.service.TBFunctionGroupQueryService;
import vn.azteam.tpf.service.dto.*;
import vn.azteam.tpf.service.mapper.CasbinMapper;
import vn.azteam.tpf.web.rest.util.PaginationUtil;
import vn.azteam.tpf.service.CasbinQueryService;
import vn.azteam.tpf.service.CasbinService;
import vn.azteam.tpf.service.UserService;
import vn.azteam.tpf.web.rest.errors.BadRequestAlertException;
import vn.azteam.tpf.web.rest.util.HeaderUtil;
import vn.azteam.tpf.web.rest.util.ConvertCharacterUtil;

import java.net.URI;
import java.net.URISyntaxException;
import java.time.Instant;
import java.util.*;
import java.util.stream.Collectors;

/**
 * REST controller for managing Casbin.
 */
@RestController
@RequestMapping("/api")
public class CasbinResource {

    private final Logger log = LoggerFactory.getLogger(CasbinResource.class);

    private static final String ENTITY_NAME = "casbin_rule";

    @Autowired
    private final CasbinService casbinService;

    private final CasbinQueryService casbinQueryService;

    private final UserService userService;

    private final TBFunctionService tbFunctionService;

    private final TBFunctionGroupQueryService tbFunctionGroupQueryService;

    private final CasbinRepository casbinRepository;

    private final CasbinMapper casbinMapper;
    @Autowired
    private final ApplicationContext context;

    public CasbinResource(CasbinService casbinService
        , CasbinQueryService casbinQueryService
        , UserService userService
        , TBFunctionService tbFunctionService
        , TBFunctionGroupQueryService tbFunctionGroupQueryService
        , CasbinRepository casbinRepository, CasbinMapper casbinMapper, ApplicationContext context) {
        this.casbinService = casbinService;
        this.casbinQueryService = casbinQueryService;
        this.userService = userService;
        this.tbFunctionService = tbFunctionService;
        this.tbFunctionGroupQueryService = tbFunctionGroupQueryService;
        this.casbinRepository = casbinRepository;
        this.casbinMapper = casbinMapper;
        this.context = context;
    }

    /**
     * POST  /rules : Create a new casbin.
     *
     * @param casbinDTO the CasbinDTO to create
     * @return the ResponseEntity with status 201 (Created) and with body the new casbinDTO, or with status 400 (Bad Request) if the permission has already an ID
     * @throws URISyntaxException if the Location URI syntax is incorrect
     */
    @PostMapping("/rules")
    @Timed
    public ResponseEntity<CasbinDTO> createRule(@RequestBody CasbinDTO casbinDTO) throws URISyntaxException {
        log.debug("REST request to save Casbin : {}", casbinDTO);
        if (casbinDTO.getId() != null) {
            throw new BadRequestAlertException("A new casbin cannot already have an ID", ENTITY_NAME, "idexists");
        }
        Optional<UserDTO> currentUser = userService.getCurrentUserDTO();
        Long currentUserId = currentUser.get().getId();
        casbinDTO.setCreatedDate(Instant.now().getEpochSecond());
        casbinDTO.setCreatedById(currentUserId);
        CasbinDTO result = casbinService.save(casbinDTO);
        Enforcer enforcer = context.getBean(Enforcer.class);
        enforcer.loadPolicy();
        return ResponseEntity.created(new URI("/api/rules/" + result.getId()))
            .headers(HeaderUtil.createEntityCreationAlert(ENTITY_NAME, result.getId().toString()))
            .body(result);
    }

    /**
     * PUT  /rules : Updates an existing casbin.
     *
     * @param casbinDTO the casbinDTO to update
     * @return the ResponseEntity with status 200 (OK) and with body the updated casbinDTO,
     * or with status 400 (Bad Request) if the casbinDTO is not valid,
     * or with status 500 (Internal Server Error) if the casbinDTO couldn't be updated
     * @throws URISyntaxException if the Location URI syntax is incorrect
     */
    @PutMapping("/rules")
    @Timed
    public ResponseEntity<CasbinDTO> updateRule(@RequestBody CasbinDTO casbinDTO) throws URISyntaxException {
        log.debug("REST request to update Permission : {}", casbinDTO);
        if (casbinDTO.getId() == null) {
            throw new BadRequestAlertException("Invalid id", ENTITY_NAME, "idnull");
        }
        Optional<UserDTO> currentUser = userService.getCurrentUserDTO();
        Long currentUserId = currentUser.get().getId();
        Optional<CasbinDTO> casbinDTOOptional = casbinService.findOne(casbinDTO.getId());
        CasbinDTO casbin = casbinDTOOptional.get();
        casbin.setV4(casbinDTO.getV4());
        casbin.setModifiedDate(Instant.now().getEpochSecond());
        casbin.setModifiedById(currentUserId);
        CasbinDTO result = casbinService.save(casbin);
        Enforcer enforcer = context.getBean(Enforcer.class);
        enforcer.loadPolicy();
        return ResponseEntity.ok()
            .headers(HeaderUtil.createEntityUpdateAlert(ENTITY_NAME, casbin.getId().toString()))
            .body(result);
    }

    /**
     * PUT  /rules : Updates an existing casbin.
     *
     * @param casbinListDTO the casbinDTO to update
     * @return the ResponseEntity with status 200 (OK) and with body the updated casbinDTO,
     * or with status 400 (Bad Request) if the casbinDTO is not valid,
     * or with status 500 (Internal Server Error) if the casbinDTO couldn't be updated
     * @throws URISyntaxException if the Location URI syntax is incorrect
     */
    @PutMapping("/update-list-rules")
    @Transactional
    @Timed
    public ResponseEntity<List<CasbinDTO>> updateListRule(@RequestBody CasbinListDTO casbinListDTO,@PageableDefault(page = 0,size = Integer.MAX_VALUE) Pageable pageable) throws URISyntaxException {
        log.debug("REST request to update List Permission : {}", casbinListDTO);
        if (casbinListDTO == null) {
            throw new BadRequestAlertException("Invalid id", ENTITY_NAME, "1029");
        }

        Optional<UserDTO> currentUser = userService.getCurrentUserDTO();
        Long currentUserId = currentUser.get().getId();
        Optional<CasbinDTO> firstCasbinDTOOptional = casbinService.findOne(casbinListDTO.getListId().get(0));
        String v0 = firstCasbinDTOOptional.get().getV0();

        CasbinCriteria casbinCriteria = new CasbinCriteria();
        StringFilter v0Filter = new StringFilter();
        v0Filter.setEquals(v0);
        casbinCriteria.setV0(v0Filter);
        Page<CasbinDTO> result = casbinQueryService.findByCriteria(casbinCriteria,pageable);
        result.getContent().stream()
            .forEach(casbin -> {
                String v4 = casbinListDTO.getListId().contains(casbin.getId()) ? "true" : "false";
                casbin.setV4(v4);
                casbin.setModifiedDate(Instant.now().getEpochSecond());
                casbin.setModifiedById(currentUserId);
                casbinRepository.saveAndFlush(casbinMapper.toEntity(casbin));
                Enforcer enforcer = context.getBean(Enforcer.class);
                enforcer.loadPolicy();
            });

        return ResponseEntity.ok()
            .headers(HeaderUtil.createEntityUpdateAlert(ENTITY_NAME, v0))
            .body(result.getContent());
    }

    /**
     * GET  /rules : get all the casbin.
     *
     * @param pageable the pagination information
     * @param criteria the criterias which the requested entities should match
     * @return the ResponseEntity with status 200 (OK) and the list of casbin in body
     */
    @GetMapping("/rules")
    @Timed
    public ResponseEntity<List<CasbinDTO>> getAllRules(CasbinCriteria criteria, Pageable pageable) {
        log.debug("REST request to get Casbin by criteria: {}", criteria);
        Page<CasbinDTO> page = casbinQueryService.findByCriteria(criteria, pageable);
        HttpHeaders headers = PaginationUtil.generatePaginationHttpHeaders(page, "/api/rules");
        return ResponseEntity.ok().headers(headers).body(page.getContent());
    }

    /**
     * GET  /rules/count : count all the casbin.
     *
     * @param criteria the criterias which the requested entities should match
     * @return the ResponseEntity with status 200 (OK) and the count in body
     */
    @GetMapping("/rules/count")
    @Timed
    public ResponseEntity<Long> countRules(CasbinCriteria criteria) {
        log.debug("REST request to count Permissions by criteria: {}", criteria);
        return ResponseEntity.ok().body(casbinQueryService.countByCriteria(criteria));
    }

    /**
     * GET  /rules/:id : get the "id" casbin.
     *
     * @param id the id of the CasbinDTO to retrieve
     * @return the ResponseEntity with status 200 (OK) and with body the CasbinDTO, or with status 404 (Not Found)
     */
    @GetMapping("/rules/{id}")
    @Timed
    public ResponseEntity<CasbinDTO> getRule(@PathVariable Long id) {
        log.debug("REST request to get Casbin : {}", id);
        Optional<CasbinDTO> casbinDTO = casbinService.findOne(id);
        return ResponseUtil.wrapOrNotFound(casbinDTO);
    }

    /**
     * DELETE  /rules/:id : delete the "id" casbin.
     *
     * @param id the id of the permissionDTO to delete
     * @return the ResponseEntity with status 200 (OK)
     */
    @DeleteMapping("/rules/{id}")
    @Timed
    public ResponseEntity<Void> deleteRule(@PathVariable Long id) {
        log.debug("REST request to delete Casbin : {}", id);
        casbinService.delete(id);
        return ResponseEntity.ok().headers(HeaderUtil.createEntityDeletionAlert(ENTITY_NAME, id.toString())).build();
    }

    /**
     * SEARCH  /_search/rules?query=:query : search for the casbin corresponding
     * to the query.
     *
     * @param query    the query of the casbin search
     * @param pageable the pagination information
     * @return the result of the search
     */
    @GetMapping("/_search/rules")
    @Timed
    public ResponseEntity<List<CasbinDTO>> searchRules(@RequestParam String query, Pageable pageable) {
        log.debug("REST request to search for a page of Casbin for query {}", query);
        Page<CasbinDTO> page = casbinService.search(query, pageable);
        HttpHeaders headers = PaginationUtil.generateSearchPaginationHttpHeaders(query, page, "/api/_search/rules");
        return new ResponseEntity<>(page.getContent(), headers, HttpStatus.OK);
    }

//    /**
//     * GET  /rules/permission/{roleId} : get all the casbin by roleId.
//     *
//     * @param pageable the pagination information
//     * @param roleId   the Long which the requested entities should match
//     * @return the ResponseEntity with status 200 (OK) and the list of casbin in body
//     */
//    @GetMapping("/rules/permissions/{roleId}")
//    @Timed
//    public ResponseEntity<List<CasbinDTO>> getAllRulesByRoleId(@PathVariable Long roleId, Pageable pageable) {
//        log.debug("REST request to get Casbin by roleId: {}", roleId);
//        Page<CasbinDTO> page = casbinQueryService.findByRoleId(roleId, pageable);
//        HttpHeaders headers = PaginationUtil.generatePaginationHttpHeaders(page, "/api/rules");
//        return ResponseEntity.ok().headers(headers).body(page.getContent());
//    }

    @GetMapping("/rules/permissions")
    @Timed
    public ResponseEntity<List<GroupPermissionsDTO>> getAllPermission() {
//        Page<CasbinDTO> page = casbinQueryService.findByRoleId(roleId, pageable);
//        HttpHeaders headers = PaginationUtil.generatePaginationHttpHeaders(page, "/api/rules");
        List<GroupPermissionsDTO> result = new ArrayList<>();
        List<TBFunctionGroupDTO> functionGroupDTOS = tbFunctionGroupQueryService.findByCriteria(null);
        log.debug("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
        log.debug("REST logs all function group data: {}", functionGroupDTOS);
        log.debug("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
        functionGroupDTOS.forEach(functionGroupDTO -> {
            GroupPermissionsDTO groupPermissionsDTO = new GroupPermissionsDTO();
            groupPermissionsDTO.setId(functionGroupDTO.getId());
            groupPermissionsDTO.setName(functionGroupDTO.getName());
            List<TBPermissionDTO> permissionDTOS = new ArrayList<>();
            List<TBFunction> functionsDTOS = tbFunctionService.findByFunctionGroupId(functionGroupDTO.getId());
            log.debug("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
            log.debug("REST logs function group: {}", functionGroupDTO.getName());
            functionsDTOS.forEach(functionsDTO -> {
                log.debug("REST logs function name in group: {}", functionsDTO.getName());
                TBPermissionDTO permissionDTO = new TBPermissionDTO();
                permissionDTO.setId(functionsDTO.getId());
                permissionDTO.setName(functionsDTO.getName());
                List<CasbinDTO> casbinDTOS = casbinQueryService.getPermissionDTOSByFunctionId(functionsDTO.getId());
                permissionDTO.setCasbinDTOList(casbinDTOS);
                permissionDTOS.add(permissionDTO);
            });
            log.debug("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
            groupPermissionsDTO.setPermissionDTOList(permissionDTOS);
            result.add(groupPermissionsDTO);
        });

        return ResponseEntity.ok().body(result);
    }

    private boolean checkShowMenuPermission(Long functionGroupId,Long roleId){
        final boolean[] check = {false};
        List<TBFunction> functionsDTOS = tbFunctionService.findByFunctionGroupId(functionGroupId);
        if(null != functionsDTOS && functionsDTOS.size() > 0 && check[0] == false){
            functionsDTOS.stream().forEach(functionsDTO -> {
                List<CasbinDTO> casbinDTOS = casbinQueryService.getPermissionDTOSByFunctionId(functionsDTO.getId());
                List<CasbinDTO> listCasbinByRoleId = casbinDTOS.stream()
                    .filter(casbin -> roleId.equals(casbin.getTbRoleId()))
                    .collect(Collectors.toList());
                if(listCasbinByRoleId.size() > 0){
                    check[0] = true;
                }
            });
//            for (TBFunction functionsDTO: functionsDTOS) {
//                List<CasbinDTO> casbinDTOS = casbinQueryService.getPermissionDTOSByFunctionId(functionsDTO.getId());
//                List<CasbinDTO> listCasbinByRoleId = new ArrayList<>();
//                if(listCasbinByRoleId.size() > 0){
//                    check[0] = true;
//                }
//            }
        }
        return check[0];
    }

    @GetMapping("/menus")
    @Timed
    public ResponseEntity<List<MenuDTO>> getMenuByRoleId() {
        Optional<UserDTO> currentUser = userService.getCurrentUserDTO();
        Long roleId = currentUser.get().getRoleId();
        List<MenuDTO> result = new ArrayList<>();
        List<TBFunctionGroupDTO> functionGroupDTOS = tbFunctionGroupQueryService.findByCriteria(null);
        log.debug("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
        log.debug("REST logs all function group data: {}", functionGroupDTOS);
        log.debug("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
        functionGroupDTOS.forEach(functionGroupDTO -> {
            MenuDTO menuDTO = new MenuDTO();
            menuDTO.setId(functionGroupDTO.getId());
            menuDTO.setName(functionGroupDTO.getName());
            String constant_name = ConvertCharacterUtil.returnMenuNameContantFormat(functionGroupDTO.getName());
            menuDTO.setSlugName(constant_name);
            boolean isShowed = checkShowMenuPermission(functionGroupDTO.getId(),roleId);

            //Check always show menu
            if(Enums.getIfPresent(Constants.ENUM_MENU_ALWAYS_SHOW.class,constant_name).isPresent()){
                isShowed =true;
            }

            //Check always not show menu
            if(Enums.getIfPresent(Constants.ENUM_MENU_NOT_SHOW.class,constant_name).isPresent()){
                isShowed = false;
            }

            menuDTO.setStatus(isShowed);
            Integer menuIndex = Constants.ENUM_MENU_INDEX.valueOf(constant_name).getValue();
            menuDTO.setMenuIndex(menuIndex);
            result.add(menuDTO);
            log.debug("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
            log.debug("REST logs function group: {}", functionGroupDTO.getName());
        });
        Collections.sort(result, new Comparator<MenuDTO>() {
            @Override
            public int compare(MenuDTO o1, MenuDTO o2) {
                return o1.getMenuIndex() > o2.getMenuIndex() ? 1 : -1;
            }
        });
        return ResponseEntity.ok().body(result);
    }

    @GetMapping("/rules/permissions-by-current-user")
    @Timed
    public ResponseEntity<List<CasbinDTO>> getAllPermissionByRoleId(CasbinCriteria criteria, Pageable page) {
        log.debug("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
        List<CasbinDTO> result = casbinQueryService.getCasbinByCurrentUserRole(criteria,page);
        log.debug("REST logs all function casbin_rule by current user data: {} records", result.size());
        log.debug("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");

        return ResponseEntity.ok().body(result);
    }

    @GetMapping("/rules/permissions/{roleId}")
    @Timed
    public ResponseEntity<List<GroupPermissionsDTO>> getAllPermissionByRoleId(@PathVariable Long roleId) {
        List<GroupPermissionsDTO> result = new ArrayList<>();
        List<TBFunctionGroupDTO> functionGroupDTOS = tbFunctionGroupQueryService.findByCriteria(null);
        log.debug("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
        log.debug("REST logs all function group data: {}", functionGroupDTOS);
        log.debug("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
        functionGroupDTOS.forEach(functionGroupDTO -> {
            GroupPermissionsDTO groupPermissionsDTO = new GroupPermissionsDTO();
            groupPermissionsDTO.setId(functionGroupDTO.getId());
            groupPermissionsDTO.setName(functionGroupDTO.getName());
            String constant_name = ConvertCharacterUtil.returnMenuNameContantFormat(functionGroupDTO.getName());
            groupPermissionsDTO.setConstantName(constant_name);
            List<TBPermissionDTO> permissionDTOS = new ArrayList<>();
            List<TBFunction> functionsDTOS = tbFunctionService.findByFunctionGroupId(functionGroupDTO.getId());
            log.debug("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
            log.debug("REST logs function group: {}", functionGroupDTO.getName());
            functionsDTOS.forEach(functionsDTO -> {
                log.debug("REST logs function name in group: {}", functionsDTO.getName());
                TBPermissionDTO permissionDTO = new TBPermissionDTO();
                permissionDTO.setId(functionsDTO.getId());
                permissionDTO.setName(functionsDTO.getName());
                List<CasbinDTO> casbinDTOS = casbinQueryService.getPermissionDTOSByFunctionId(functionsDTO.getId());
                List<CasbinDTO> listCasbinByRoleId = casbinDTOS.stream()
                    .filter(casbin -> casbin.getTbRoleId() == roleId)
                    .collect(Collectors.toList());
                permissionDTO.setCasbinDTOList(listCasbinByRoleId);
                permissionDTOS.add(permissionDTO);
            });
            log.debug("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
            groupPermissionsDTO.setPermissionDTOList(permissionDTOS);
            result.add(groupPermissionsDTO);
        });
        return ResponseEntity.ok().body(result);
    }
}
