package pwc.taxtech.atms.service.impl;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import pwc.taxtech.atms.common.AuthUserHelper;
import pwc.taxtech.atms.common.CommonConstants;
import pwc.taxtech.atms.common.CommonUtils;
import pwc.taxtech.atms.common.OperateLogType;
import pwc.taxtech.atms.common.OperationAction;
import pwc.taxtech.atms.common.OperationModule;
import pwc.taxtech.atms.common.message.AreaMessage;
import pwc.taxtech.atms.common.message.LogMessage;
import pwc.taxtech.atms.common.message.RegionMessage;
import pwc.taxtech.atms.dao.AreaMapper;
import pwc.taxtech.atms.dto.AreaDto;
import pwc.taxtech.atms.dto.OperationLogDto;
import pwc.taxtech.atms.dto.OperationResultDto;
import pwc.taxtech.atms.entity.Area;
import pwc.taxtech.atms.entity.AreaExample;
import pwc.taxtech.atms.entity.AreaExample.Criteria;
import pwc.taxtech.atms.entity.Organization;
import pwc.taxtech.atms.exception.ApplicationException;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

@Service
public class AreaServiceImpl {

    @Autowired
    private AreaMapper areaMapper;
    @Autowired
    private OrganizationServiceImpl organizationService;
    @Autowired
    private OperationLogServiceImpl operationLogService;
    @Autowired
    private AuthUserHelper authUserHelper;

    private static final Logger logger = LoggerFactory.getLogger(AreaServiceImpl.class);

    /*
     * (non-Javadoc)
     *
     * @see pwc.taxtech.atms.service.AreaService#findTopAreas()
     */
    public List<Area> findTopAreas() {

//        AreaExample areaExample = new AreaExample();
//        Criteria criteria = areaExample.createCriteria();
////        criteria.andParentIdIsNull();
//        criteria.andParentIdEqualTo(StringUtils.EMPTY);

        return areaMapper.selectTopAreas();

    }

    /*
     * (non-Javadoc)
     *
     * @see pwc.taxtech.atms.service.AreaService#getSubAreaListIngoreActive()
     */
    public List<Area> getSubAreaListIngoreActive(String parentId) {
        AreaExample areaExample = new AreaExample();
        Criteria criteria = areaExample.createCriteria();
        criteria.andParentIdEqualTo(parentId);

        return areaMapper.selectByExample(areaExample);
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * pwc.taxtech.atms.service.AreaService#setIsActive(pwc.taxtech.atms.service.dto
     * .AreaDto, java.lang.String)
     */
    @Transactional
    public OperationResultDto<List<String>> setIsActive(AreaDto areaDto) {

        logger.info("Area: Set isactive. Area id: " + areaDto.getId() + ", to status: " + areaDto.getIsActive());
        Area targetArea = areaMapper.selectByPrimaryKey(areaDto.getId());

        List<Area> allAreasToUpdate = new ArrayList<>();
        List<Area> allSubAreas = getAllSubAreas(targetArea.getId());
        List<Area> subAreasToUpdate;
        allAreasToUpdate.add(targetArea);

        List<OperationLogDto> allLogs = new ArrayList<>();

        OperationResultDto<List<String>> operationResultDto = new OperationResultDto<>();

        // Deactive
        if (!areaDto.getIsActive()) {

            // cascade deactive sub areas
            subAreasToUpdate = allSubAreas.stream().filter(area -> area.getIsActive()).collect(Collectors.toList());
            allAreasToUpdate.addAll(subAreasToUpdate);

            // cannot deactive if there is linked organization
            List<Organization> linkedOrganizations = organizationService.getLinkedOrganization(allAreasToUpdate);
            if (null != linkedOrganizations && !linkedOrganizations.isEmpty()) {

                List<String> linkedOrgName = linkedOrganizations.stream().map(org -> org.getName())
                        .collect(Collectors.toList());

                operationResultDto.setResult(false);
                operationResultDto.setResultMsg(RegionMessage.CannotStopRegionTips);
                operationResultDto.setData(linkedOrgName);
                return operationResultDto;
            }
        } else {
            // cascade active sub areas
            subAreasToUpdate = allSubAreas.stream().filter(area -> !area.getIsActive()).collect(Collectors.toList());
            allAreasToUpdate.addAll(subAreasToUpdate);
        }

        // update area
        for (Area areaToUpdate : allAreasToUpdate) {
            areaToUpdate.setIsActive(areaDto.getIsActive());
            areaMapper.updateByPrimaryKey(areaToUpdate);
        }

        // target OperationLog
        OperationLogDto operationLogDto = new OperationLogDto();
        operationLogDto.setOperationContent(LogMessage.IsActive);
        operationLogDto.setAction(OperationAction.Update.value());
        operationLogDto.setOperationObject(targetArea.getName());
        operationLogDto.setOriginalState(String.valueOf(!areaDto.getIsActive()));
        operationLogDto.setUpdateState(String.valueOf(areaDto.getIsActive()));
        operationLogDto.setModule(OperationModule.BasicDataArea.value());
        operationLogDto.setComment("");
        operationLogDto.setLogType(OperateLogType.OperationLogBasicData.value());
        allLogs.add(operationLogDto);

        for (Area areaToUpdate : subAreasToUpdate) {
            // OperationLog
            operationLogDto = new OperationLogDto();
            operationLogDto.setId(CommonUtils.getUUID());
            operationLogDto.setOperationUser(authUserHelper.getCurrentAuditor().get());
            operationLogDto.setIp("");
            operationLogDto.setOperationContent(LogMessage.IsActive);
            operationLogDto.setAction(OperationAction.Update.value());
            operationLogDto.setOperationObject(areaToUpdate.getName());
            operationLogDto.setOriginalState(String.valueOf(!areaDto.getIsActive()));
            operationLogDto.setUpdateState(String.valueOf(areaDto.getIsActive()));
            operationLogDto.setModule(OperationModule.BasicDataArea.value());
            operationLogDto.setComment(AreaMessage.AreaName + ":" + targetArea.getName() + AreaMessage.DisableCascade);
            operationLogDto.setCreateTime(new Date());
            operationLogDto.setLogType(OperateLogType.OperationLogBasicData.value());
            allLogs.add(operationLogDto);
        }

        operationLogService.addOperationLogList(allLogs);

        operationResultDto.setResult(true);
        return operationResultDto;
    }

    /*
     * (non-Javadoc)
     *
     * @see pwc.taxtech.atms.service.AreaService#getAllSubAreas(java.lang.String)
     */
    public List<Area> getAllSubAreas(String areaId) {

        List<Area> tempList = getSubAreaListIngoreActive(areaId);
        if (null != tempList && !tempList.isEmpty()) {
            List<Area> subAreaList = new ArrayList<>();
            subAreaList.addAll(tempList);
            for (Area subArea : tempList) {
                subAreaList.addAll(getAllSubAreas(subArea.getId()));
            }
            return subAreaList;
        }
        return new ArrayList<>();
    }

    /*
     * (non-Javadoc)
     *
     * @see pwc.taxtech.atms.service.AreaService#getAreaList(int)
     */
    public List<AreaDto> getAreaList(int type) {

        List<Area> areaList = new ArrayList<>();

        if (type == 1) {
            AreaExample example = new AreaExample();
            example.setOrderByClause("ID ASC, PARENT_ID ASC");
            areaList = areaMapper.selectByExample(example);
        } else if (type == 0) {
            AreaExample example = new AreaExample();
            example.createCriteria().andIsActiveEqualTo(CommonConstants.ACTIVE_STATUS);
            example.setOrderByClause("ID ASC, PARENT_ID ASC");
            areaList = areaMapper.selectByExample(example);
        }

        if (areaList == null || areaList.isEmpty()) {
            throw new ApplicationException(AreaMessage.NoAreaRootData);
        }

        List<AreaDto> areaDtoList = new ArrayList<>();
        areaList.stream().forEach(area -> {
            AreaDto areaDto = new AreaDto();
            CommonUtils.copyProperties(area, areaDto);
            areaDtoList.add(areaDto);
        });
        return areaDtoList;
    }

    public List<AreaDto> getAreaTree(int type) {
        List<AreaDto> treeList = new ArrayList<AreaDto>();
        List<AreaDto> areaList = getAreaList(type);

        // Top Node
        List<AreaDto> topNode = areaList.stream().filter(sa -> StringUtils.isBlank(sa.getParentId())).collect(Collectors.toList());
        if (topNode == null) {
            throw new ApplicationException(AreaMessage.NoAreaRootData);
        }

        for (AreaDto item : topNode) {
            AreaDto top = item;
            top.setSubAreaList(getSubAreaList(top, areaList));
            treeList.add(top);
        }
        return treeList;
    }

    private List<AreaDto> getSubAreaList(AreaDto currentNode, List<AreaDto> areaList) {
        List<AreaDto> subList = new ArrayList<AreaDto>();
        for (AreaDto area : areaList) {
            if (currentNode.getId().equals(area.getParentId())) {
                subList.add(area);
            }
        }
        subList.forEach(area -> {
            area.setSubAreaList(getSubAreaList(area, areaList));
        });
        return subList;
    }

}