package vn.azteam.tpf.web.rest;

import com.codahale.metrics.annotation.Timed;
import io.github.jhipster.service.filter.LongFilter;
import org.casbin.jcasbin.main.Enforcer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import vn.azteam.tpf.Constants.Constants;
import vn.azteam.tpf.domain.TBDetailUser;
import vn.azteam.tpf.domain.TBRole;
import vn.azteam.tpf.service.*;
import vn.azteam.tpf.service.dto.*;
import vn.azteam.tpf.service.util.UserRoleUtil;
import vn.azteam.tpf.web.rest.errors.BadRequestAlertException;
import vn.azteam.tpf.web.rest.util.HeaderUtil;
import vn.azteam.tpf.web.rest.util.PaginationUtil;
import vn.azteam.tpf.domain.User;
import io.github.jhipster.web.util.ResponseUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.context.ApplicationContext;

import java.net.URI;
import java.net.URISyntaxException;

import java.text.Normalizer;
import java.time.Instant;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

import static org.elasticsearch.index.query.QueryBuilders.*;

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

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

    private static final String ENTITY_NAME = "tBRole";

    private final TBRoleService tBRoleService;

    private final TBRoleQueryService tBRoleQueryService;

    private final TBFunctionQueryService tBFunctionQueryService;

    private final TBDetailUserQueryService tBDetailUserQueryService;

    private final UserService userService;

    private final UserRoleUtil userRoleUtil;

    private final CasbinService casbinService;

    @Autowired
    private final ApplicationContext context;

    public TBRoleResource(TBRoleService tBRoleService
            , TBRoleQueryService tBRoleQueryService
            , UserService userService
            , CasbinService casbinService
            , UserRoleUtil userRoleUtil
            , TBDetailUserQueryService tBDetailUserQueryService
            , ApplicationContext context
            , TBFunctionQueryService tBFunctionQueryService) {
        this.tBRoleService = tBRoleService;
        this.tBRoleQueryService = tBRoleQueryService;
        this.tBFunctionQueryService = tBFunctionQueryService;
        this.userService = userService;
        this.casbinService = casbinService;
        this.userRoleUtil = userRoleUtil;
        this.context = context;
        this.tBDetailUserQueryService = tBDetailUserQueryService;
    }

    /**
     * POST  /roles : Create a new role.
     *
     * @param roleDTO the roleDTO to create
     * @return the ResponseEntity with status 201 (Created) and with body the new roleDTO, or with status 400 (Bad Request) if the role has already an ID
     * @throws URISyntaxException if the Location URI syntax is incorrect
     */
    private static final Pattern NONLATIN = Pattern.compile("[^\\w-]");
    private static final Pattern WHITESPACE = Pattern.compile("[\\s]");

    public static String toSlug(String input) {
        String nowhitespace = WHITESPACE.matcher(input).replaceAll("-");
        String normalized = Normalizer.normalize(nowhitespace, Normalizer.Form.NFD);
        String slug = NONLATIN.matcher(normalized).replaceAll("");
        return slug.toLowerCase(Locale.ENGLISH);
    }

    /**
     * POST  /tb-roles : Create a new tBRole.
     *
     * @param tBRoleDTO the tBRoleDTO to create
     * @return the ResponseEntity with status 201 (Created) and with body the new tBRoleDTO, or with status 400 (Bad Request) if the tBRole has already an ID
     * @throws URISyntaxException if the Location URI syntax is incorrect
     */
    @PostMapping("/tb-roles")
    @Timed
    public ResponseEntity<TBRoleDTO> createTBRole(@RequestBody TBRoleDTO tBRoleDTO) throws URISyntaxException {
        log.debug("REST request to save TBRole : {}", tBRoleDTO);
        if (tBRoleDTO.getId() != null) {
            throw new BadRequestAlertException("A new tBRole cannot already have an ID", ENTITY_NAME, "idexists");
        }
        UserDTO userDTO = userService.getCurrentUserDTO().get();
        Instant now = Instant.now();
        tBRoleDTO.setCreatedById(userDTO.getId());
        tBRoleDTO.setCreatedDate(now);
        //set current roleLevel
        TBRoleDTO currentRole = tBRoleService.findOne(userDTO.getRoleId()).get();
        Integer newRoleLevel = currentRole.getRoleLevel() != null ? currentRole.getRoleLevel() + 1 : currentRole.getRoleLevel();
        tBRoleDTO.setRoleLevel(newRoleLevel);
        TBRoleDTO result = tBRoleService.save(tBRoleDTO);
        // Generate casbin rule for new role with hasPermission = false
        List<TBFunctionDTO> functionsDTOS = tBFunctionQueryService.findByCriteria(null);
        functionsDTOS.forEach(functionsDTO -> {
            CasbinDTO casbinDTO = new CasbinDTO();
            casbinDTO.setCreatedById(userDTO.getId());
            casbinDTO.setTbFunctionId(functionsDTO.getId());
            casbinDTO.setTbRoleId(result.getId());
            casbinDTO.setCreatedDate(now.getEpochSecond());
            casbinDTO.setpType("p");
            casbinDTO.setV0(result.getId().toString());
            casbinDTO.setV1(functionsDTO.getPath());
            casbinDTO.setV2(functionsDTO.getMethod());
            casbinDTO.setV3(functionsDTO.getTypeRequest());
            casbinDTO.setV4("false");
            casbinDTO.setV5(toSlug(functionsDTO.getMethod() + functionsDTO.getPath()).replace("-", ""));
            casbinService.save(casbinDTO);
        });
        Enforcer enforcer = context.getBean(Enforcer.class);
        enforcer.loadPolicy();
        return ResponseEntity.created(new URI("/api/tb-roles/" + result.getId()))
            .headers(HeaderUtil.createEntityCreationAlert(ENTITY_NAME, result.getId().toString()))
            .body(result);
    }

    /**
     * PUT  /tb-roles : Updates an existing tBRole.
     *
     * @param tBRoleDTO the tBRoleDTO to update
     * @return the ResponseEntity with status 200 (OK) and with body the updated tBRoleDTO,
     * or with status 400 (Bad Request) if the tBRoleDTO is not valid,
     * or with status 500 (Internal Server Error) if the tBRoleDTO couldn't be updated
     * @throws URISyntaxException if the Location URI syntax is incorrect
     */
    @PutMapping("/tb-roles")
    @Timed
    public ResponseEntity<TBRoleDTO> updateTBRole(@RequestBody TBRoleDTO tBRoleDTO) throws URISyntaxException {
        log.debug("REST request to update TBRole : {}", tBRoleDTO);
        if (tBRoleDTO.getId() == null) {
            throw new BadRequestAlertException("Invalid id", ENTITY_NAME, "idnull");
        }
        UserDTO userDTO = userService.getCurrentUserDTO().get();
        Instant now = Instant.now();
        tBRoleDTO.setModifiedById(userDTO.getId());
        tBRoleDTO.setModifiedDate(now);
        //set current roleLevel
        TBRoleDTO currentRole = tBRoleService.findOne(tBRoleDTO.getId()).get();
        tBRoleDTO.setRoleLevel(currentRole.getRoleLevel());
        TBRoleDTO result = tBRoleService.save(tBRoleDTO);
        Enforcer enforcer = context.getBean(Enforcer.class);
        enforcer.loadPolicy();
        return ResponseEntity.ok()
            .headers(HeaderUtil.createEntityUpdateAlert(ENTITY_NAME, tBRoleDTO.getId().toString()))
            .body(result);
    }

    /**
     * GET  /tb-roles : get all the tBRoles.
     *
     * @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 tBRoles in body
     */
    @GetMapping("/tb-roles")
    @Timed
    public ResponseEntity<List<TBRoleDTO>> getAllTBRoles(TBRoleCriteria criteria, Pageable pageable) {
        log.debug("REST request to get TBRoles by criteria: {}", criteria);
        UserDTO currentUser = userService.getCurrentUserDTO().get();
        Page<TBRoleDTO> page = tBRoleQueryService.findByCriteria(criteria, pageable);
        HttpHeaders headers = PaginationUtil.generatePaginationHttpHeaders(page, "/api/tb-roles");
        return ResponseEntity.ok().headers(headers).body(page.getContent());
    }


    /**
     * GET  /tb-roles : get all the tBRoles.
     *
     * @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 tBRoles in body
     */
    @GetMapping("/tb-roles-dropdown-list")
    @Timed
    public ResponseEntity<List<TBRoleDTO>> getAllTBRolesByCurrentUser(TBRoleCriteria criteria, Pageable pageable) {
        log.debug("REST request to get TBRoles by criteria: {}", criteria);
        UserDTO currentUser = userService.getCurrentUserDTO().get();
        Optional<TBRoleDTO> currentRoleOptional = tBRoleService.findOne(currentUser.getRoleId());
        if(!currentRoleOptional.isPresent()) {
            throw new BadRequestAlertException("1023", ENTITY_NAME, "1023");
        }
        TBRoleDTO currentRoleDTO = currentRoleOptional.get();

        Page<TBRoleDTO> page = tBRoleQueryService.findByCriteria(criteria, pageable);
        List<TBRoleDTO> result = page.getContent();
        Integer currentRoleLevel = currentRoleDTO.getRoleLevel();
        result = result.stream().filter(item->item.getRoleLevel() != null && item.getRoleLevel() >= currentRoleLevel)
                .collect(Collectors.toList());
        if (currentUser.getCustomerId() != null) {
            result = result.stream().filter(item->item.getTbCustomerId() == null
                || item.getTbCustomerId().equals(currentUser.getCustomerId()))
                .collect(Collectors.toList());
        }
        HttpHeaders headers = PaginationUtil.generatePaginationHttpHeaders(page, "/api/tb-roles-dropdown-list");
        return ResponseEntity.ok().headers(headers).body(result);
    }

    /**
    * GET  /tb-roles/count : count all the tBRoles.
    *
    * @param criteria the criterias which the requested entities should match
    * @return the ResponseEntity with status 200 (OK) and the count in body
    */
    @GetMapping("/tb-roles/count")
    @Timed
    public ResponseEntity<Long> countTBRoles(TBRoleCriteria criteria) {
        log.debug("REST request to count TBRoles by criteria: {}", criteria);
        return ResponseEntity.ok().body(tBRoleQueryService.countByCriteria(criteria));
    }

    /**
     * GET  /tb-roles/:id : get the "id" tBRole.
     *
     * @param id the id of the tBRoleDTO to retrieve
     * @return the ResponseEntity with status 200 (OK) and with body the tBRoleDTO, or with status 404 (Not Found)
     */
    @GetMapping("/tb-roles/{id}")
    @Timed
    public ResponseEntity<TBRoleDTO> getTBRole(@PathVariable Long id) {
        log.debug("REST request to get TBRole : {}", id);
        Optional<TBRoleDTO> tBRoleDTO = tBRoleService.findOne(id);
        return ResponseUtil.wrapOrNotFound(tBRoleDTO);
    }

    /**
     * DELETE  /tb-roles/:id : delete the "id" tBRole.
     *
     * @param id the id of the tBRoleDTO to delete
     * @return the ResponseEntity with status 200 (OK)
     */
    @DeleteMapping("/tb-roles/{id}")
    @Timed
    public ResponseEntity<Void> deleteTBRole(@PathVariable Long id) {
        try{
            log.debug("REST request to delete TBRole : {}", id);
            TBDetailUserCriteria criteria = new TBDetailUserCriteria();
            LongFilter userFilter = new LongFilter();
            userFilter.setEquals(id);
            criteria.setTbRoleId(userFilter);
            long count = tBDetailUserQueryService.countByCriteria(criteria);
            if (count <= 0) {
                // Delete casbin rule with role Id
                casbinService.deleteByRoleId(id);
                // Delete rules of role
                tBRoleService.delete(id);
                Enforcer enforcer = context.getBean(Enforcer.class);
                enforcer.loadPolicy();
                return ResponseEntity.ok().headers(HeaderUtil.createEntityDeletionAlert(ENTITY_NAME, id.toString())).build();
            } else {
                throw new BadRequestAlertException("A role is assigned with user", ENTITY_NAME, "idexists");
            }
        }
        catch (Exception e){
            throw new BadRequestAlertException("A role is assigned with user", ENTITY_NAME, "idexists");
        }
    }

    @GetMapping("/tb-roles-by-customer/{customerId}")
    @Timed
    public ResponseEntity<List<TBRoleDTO>> getTBRolesByCustomer(@PathVariable Long customerId, TBRoleCriteria criteria, Pageable pageable) {
        log.debug("REST request to filter of TBRoles for customerId {}", customerId);
        UserDTO currentUser = userService.getCurrentUserDTO().get();
        if (currentUser.getCustomerId() != null
            && currentUser.getCustomerId() != null
            && !currentUser.getCustomerId().equals(customerId)) {
            throw new BadRequestAlertException("Current user in not belongs to this customer", ENTITY_NAME, "forbiden");
        }
        LongFilter customerFilter = new LongFilter();
        customerFilter.setEquals(customerId);
        criteria.setTbCustomerId(customerFilter);
        Page<TBRoleDTO> page = tBRoleQueryService.findByCriteria(criteria, pageable);
        HttpHeaders headers = PaginationUtil.generateSearchPaginationHttpHeaders(customerId.toString(), page, "/tb-roles-by-customer/");
        return new ResponseEntity<>(page.getContent(), headers, HttpStatus.OK);
    }

    /**
     * SEARCH  /_search/tb-roles?query=:query : search for the tBRole corresponding
     * to the query.
     *
     * @param query the query of the tBRole search
     * @param pageable the pagination information
     * @return the result of the search
     */
    @GetMapping("/_search/tb-roles")
    @Timed
    public ResponseEntity<List<TBRoleDTO>> searchTBRoles(@RequestParam String query, Pageable pageable) {
        log.debug("REST request to search for a page of TBRoles for query {}", query);
        Page<TBRoleDTO> page = tBRoleService.search(query, pageable);
        HttpHeaders headers = PaginationUtil.generateSearchPaginationHttpHeaders(query, page, "/api/_search/tb-roles");
        return new ResponseEntity<>(page.getContent(), headers, HttpStatus.OK);
    }
}
