package pwc.taxtech.atms.service.impl;

import org.apache.commons.io.FileUtils;
import org.nutz.lang.Files;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
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.CustomerMessage;
import pwc.taxtech.atms.dao.CustomerMapper;
import pwc.taxtech.atms.dao.EnterpriseAccountSetMapper;
import pwc.taxtech.atms.dto.OperationLogDto;
import pwc.taxtech.atms.dto.OperationResultDto;
import pwc.taxtech.atms.dto.UpdateLogParams;
import pwc.taxtech.atms.dto.ValidateInfoDto;
import pwc.taxtech.atms.dto.customer.CustomerDto;
import pwc.taxtech.atms.dto.customer.CustomerValidateInfoDto;
import pwc.taxtech.atms.dto.vatdto.CustomsInvoiceDto;
import pwc.taxtech.atms.entity.Customer;
import pwc.taxtech.atms.entity.CustomerExample;
import pwc.taxtech.atms.entity.CustomerExample.Criteria;
import pwc.taxtech.atms.entity.EnterpriseAccountSet;
import pwc.taxtech.atms.exception.ApplicationException;
import pwc.taxtech.atms.vat.dao.CustomsInvoiceMapper;
import pwc.taxtech.atms.vat.entity.CustomsInvoice;
import pwc.taxtech.atms.vat.entity.CustomsInvoiceExample;

import java.io.File;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 */
@Service
public class CustomerServiceImpl {

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

    @Autowired
    private CustomerMapper customerMapper;

    @Autowired
    private AuthUserHelper authUserHelper;

    @Autowired
    private OperationLogServiceImpl operationLogService;

    @Autowired
    private EnterpriseAccountSetMapper enterpriseAccountSetMapper;

    @Autowired
    private FileService fileService;
    @Autowired
    private CustomsInvoiceMapper customsInvoiceMapper;

    public List<OperationResultDto<CustomerDto>> addRange(List<CustomerDto> customerDtoList) {
        logger.debug("CustomerService addRange");
//        logger.debug("customer list to add:{}", JSON.toJSONString(customerDtoList, true));
        if (customerDtoList == null) {
            throw new ApplicationException("input CustomerDto is null");
        }

        List<OperationResultDto<CustomerDto>> errList = validateList(customerDtoList);

        if (!errList.isEmpty()) {
            return errList;
        }

        if (!customerDtoList.isEmpty()) {
            String enterPriseAccountId = customerDtoList.get(0).getEnterPriseAccountId();
            String comment = getAccountNameById(enterPriseAccountId);
            for (CustomerDto customerDto : customerDtoList) {
                // insert record
                Customer customer = new Customer();
                CommonUtils.copyProperties(customerDto, customer);
                customerMapper.insert(customer);

                // operation log
                OperationLogDto operationLogDto = new OperationLogDto();
                makeUpOperationLogDto(operationLogDto);
                operationLogDto.setOperationObject(
                        customerDto.getCode() + CommonConstants.PLUS_SIGN_SEPARATOR + customerDto.getName());
                operationLogDto.setComment(comment);
                operationLogDto.setAction(OperationAction.New.value());
                operationLogDto.setLogType(OperateLogType.OperationLogBasicData.value());
                operationLogService.addOperationLog(operationLogDto);
            }
        }
        return errList;
    }

    private void makeUpOperationLogDto(OperationLogDto operationLogDto) {
        operationLogDto.setModule(OperationModule.BasicDataCustomer.value());
        operationLogDto.setLogType(OperateLogType.OperationLogBasicData.value());
    }

    public List<CustomerDto> getCustomer() {
        logger.debug("CustomerService getCustomer");
        List<CustomerDto> customerDtoList = new ArrayList<>();
        CustomerExample example = new CustomerExample();
        example.setOrderByClause("code ASC, name ASC");
        List<Customer> customerList = customerMapper.selectByExample(example);
        customerList.forEach(a -> {
            CustomerDto customerDto = new CustomerDto();
            CommonUtils.copyProperties(a, customerDto);
            customerDtoList.add(customerDto);
        });
        return customerDtoList;
    }

    public OperationResultDto<String> deleteRange(List<CustomerDto> customerDtoList) {
        logger.debug("CustomerService deleteRange");
//        logger.debug("customer list to delete:{}", JSON.toJSONString(customerDtoList, true));

        if (customerDtoList.isEmpty()) {
            throw new ApplicationException(CommonConstants.JSONNULLOBJECT);
        }

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

        for (CustomerDto customerDto : customerDtoList) {
            Customer customer = customerMapper.selectByPrimaryKey(customerDto.getId());
            if (customer == null) {
                throw new ApplicationException("can't find customer to delete, id: " + customerDto.getId());
            }
            customerMapper.deleteByPrimaryKey(customerDto.getId());
            operationLogDtoList.add(populateLogDto(customerDto));
        }

        // add operation log
        operationLogService.addOperationLogList(operationLogDtoList);

        // return operation result
        OperationResultDto<String> operationResultDto = new OperationResultDto<>();
        operationResultDto.setResult(true);

        return operationResultDto;
    }

    public List<OperationResultDto<CustomerDto>> updateRange(List<CustomerDto> customerDtoList) {
        logger.debug("CustomerService updateRange");
//        logger.debug("customer list to update:{}", JSON.toJSONString(customerDtoList, true));

        if (customerDtoList.isEmpty()) {
            throw new ApplicationException(CommonConstants.JSONNULLOBJECT);
        }

        List<OperationResultDto<CustomerDto>> errList = validateList(customerDtoList);
        if (!errList.isEmpty()) {
            return errList;
        }

        List<UpdateLogParams> updateLogParamsList = new ArrayList<>();

        for (CustomerDto customerDto : customerDtoList) {

            Assert.notNull(customerDto.getId(), "customer id is null");
            // check if customer existed
            Customer existedCustomer = customerMapper.selectByPrimaryKey(customerDto.getId());
            if (existedCustomer == null) {
                OperationResultDto<CustomerDto> operationResultDto = new OperationResultDto<>();
                operationResultDto.setResult(false);
                operationResultDto.setResultMsg(CustomerMessage.CannotFindCustomerById);
                errList.add(operationResultDto);
                return errList;
            }

            // keep origin customer for operation log
            Customer originCustomer = new Customer();
            CommonUtils.copyProperties(existedCustomer, originCustomer);

            // update
            existedCustomer.setCode(customerDto.getCode());
            existedCustomer.setName(customerDto.getName());
            customerMapper.updateByPrimaryKey(existedCustomer);

            // operation log
            UpdateLogParams updateLogParams = new UpdateLogParams();
            updateLogParams.setOperationObject(
                    customerDto.getCode() + CommonConstants.PLUS_SIGN_SEPARATOR + customerDto.getName());
            updateLogParams.setOriginalState(originCustomer);
            updateLogParams.setUpdateState(existedCustomer);
            updateLogParams.setOperationUser(authUserHelper.getCurrentAuditor().get());
            updateLogParams.setOperationModule(OperationModule.BasicDataCustomer.value());
            updateLogParams.setComment(getAccountNameById(customerDto.getEnterPriseAccountId()));
            updateLogParams.setOperationAction(OperationAction.Update.value());
            updateLogParams.setOperateLogType(OperateLogType.OperationLogBasicData.value());
            updateLogParamsList.add(updateLogParams);
        }
        updateLogParamsList.forEach(a -> operationLogService.updateDataAddLog(a));
        return errList;
    }

    @SuppressWarnings("rawtypes")
    public Object upload(InputStream inputStream, String fileName, String action, String enterpriseAccountId) {
        logger.debug("导入excel文件开始, action:{}, enterpriseAccountId:{}", action, enterpriseAccountId);
        String filePath = FileUtils.getTempDirectory().getAbsolutePath() + File.separator + "customer" + File.separator
                + CommonUtils.getUUID() + "_" + fileName;
        OperationResultDto<Object> saveResult = fileService.saveFile(inputStream, filePath);
        if (saveResult.getResult() != null && !saveResult.getResult()) {
            return saveResult;
        }
        InputStream newInputStream = Files.findFileAsStream(filePath);
        Collection<Map> mapCollection = fileService.readExcelAndClose(newInputStream);

        List<CustomerDto> customerDtoList = new ArrayList<CustomerDto>();
        for (Map map : mapCollection) {
            String name = map.get(CommonConstants.CUSTOMER_NAME) == null ? null
                    : map.get(CommonConstants.CUSTOMER_NAME).toString();
            String code = map.get(CommonConstants.CUSTOMER_CODE) == null ? null
                    : map.get(CommonConstants.CUSTOMER_CODE).toString();
            CustomerDto item = new CustomerDto();
            item.setName(name);
            item.setCode(code);
            item.setId(CommonUtils.getUUID());
            item.setEnterPriseAccountId(enterpriseAccountId);
            customerDtoList.add(item);
        }
        logger.debug("Map转为CustomerDto完毕, customerDtoList.size:{}", customerDtoList.size());

        if (customerDtoList.isEmpty()) {
            logger.error("excel 导入出错,customerDtoList为空");
            OperationResultDto<List<CustomerDto>> operationResultDto = new OperationResultDto<>();
            String errorMsg = CustomerMessage.CustomerImportDataFormatError;
            Map<String, String> error = new HashMap<>();
            error.put(CommonUtils.getUUID(), "文件导入出错");
            operationResultDto.setErrors(error);
            operationResultDto.setResult(false);
            operationResultDto.setResultMsg(errorMsg);
            return operationResultDto;
        }
        // save data
        List<OperationResultDto<CustomerDto>> finalResult = saveData(enterpriseAccountId, customerDtoList, action);
        return finalResult;
    }

    public List<CustomsInvoiceDto> GetCustomsInvoicesByPeriodIds(int fromPeriod, int toPeriod) {
        CustomsInvoiceExample example = new CustomsInvoiceExample();
        example.createCriteria().andPeriodIdGreaterThanOrEqualTo(fromPeriod).andPeriodIdLessThanOrEqualTo(toPeriod);
        List<CustomsInvoice> reuslt = customsInvoiceMapper.selectByExample(example);
        reuslt.stream().sorted(Comparator.comparing(CustomsInvoice::getPayNum));

        List<CustomsInvoiceDto> returnResult = new ArrayList<>();
        int seqNo = 1;
        for (CustomsInvoice r : reuslt) {
            CustomsInvoiceDto dto = new CustomsInvoiceDto();
            dto.setSeqNo(seqNo++);
            if (r.getInvoiceAmount() != null) {
                r.setInvoiceAmount(r.getInvoiceAmount().setScale(2));
            }

            if (r.getInvoiceTaxAmount() != null) {
                r.setInvoiceTaxAmount(r.getInvoiceTaxAmount().setScale(2));
            }
            returnResult.add(dto.extractFromEntity(r));
        }
        return returnResult;
    }

    private List<OperationResultDto<CustomerDto>> saveData(String enterpriseAccountId,
                                                           List<CustomerDto> customerDtoList, String action) {
        logger.debug("enter customerDtoList, enterpriseAccountId:{}, customerDtoList.size:{}, action:{}",
                enterpriseAccountId, customerDtoList.size(), action);
        List<OperationResultDto<CustomerDto>> errList = new ArrayList<OperationResultDto<CustomerDto>>();
        List<OperationResultDto<CustomerDto>> invalidList = new ArrayList<OperationResultDto<CustomerDto>>();
        boolean overwriteFlag = false;
        if (CommonConstants.IMPORT_APPEND.equals(action)) {
            // no logic
        } else if (CommonConstants.IMPORT_OVERWRITE.equals(action)) {
            logger.debug("overwriteFlag is true");
            overwriteFlag = true;
        } else {
            throw new ApplicationException("Invalid action:" + action);
        }
        for (CustomerDto customerDto : customerDtoList) {
            // 代码为空,不能进DB,名称为空,可以进DB
            if (!StringUtils.hasText(customerDto.getCode())) {
                OperationResultDto<CustomerDto> errorResult = new OperationResultDto<CustomerDto>();
                errorResult.setResult(false);
                errorResult.setData(customerDto);
                errorResult.setResultMsg(CustomerMessage.CustomerCodeEmptyNode);
                logger.debug("代码为空");
                errList.add(errorResult);
            }
            // 代码或者名称长度超过一定长度,不能进DB
            String errorMessage = checkLength(customerDto);
            if (StringUtils.hasText(errorMessage)) {
                OperationResultDto<CustomerDto> errorResult = new OperationResultDto<CustomerDto>();
                errorResult.setResult(false);
                errorResult.setData(customerDto);
                errorResult.setResultMsg(errorMessage);
                logger.debug("代码或者名称长度超过一定长度");
                errList.add(errorResult);
            }

            if (!StringUtils.hasText(customerDto.getName())) {
                OperationResultDto<CustomerDto> errorResult = new OperationResultDto<CustomerDto>();
                errorResult.setResult(false);
                errorResult.setData(customerDto);
                errorResult.setResultMsg(CustomerMessage.CustomerNameEmptyNode);
                logger.debug("名称为空");
                invalidList.add(errorResult);
            }
        }
        if (!CollectionUtils.isEmpty(errList)) {
            logger.debug("return errList");
            return errList;
        }

        long start = System.currentTimeMillis();
        if (overwriteFlag) {
            logger.debug("删除数据开始, overwriteFlag is {}, enterpriseAccountId:{}", overwriteFlag, enterpriseAccountId);
            CustomerExample example = new CustomerExample();
            example.createCriteria().andEnterPriseAccountIdEqualTo(enterpriseAccountId);
            customerMapper.deleteByExample(example);
            logger.debug("删除数据结束");
        }

        for (CustomerDto customerDto : customerDtoList) {
            CustomerExample example = new CustomerExample();
            Criteria criteria = example.createCriteria();
            criteria.andEnterPriseAccountIdEqualTo(enterpriseAccountId);
            criteria.andCodeEqualTo(customerDto.getCode());
            List<Customer> existRecord = customerMapper.selectByExample(example);
            if (!CollectionUtils.isEmpty(existRecord)) {
                OperationResultDto<CustomerDto> errorResult = new OperationResultDto<CustomerDto>();
                errorResult.setResult(false);
                errorResult.setResultMsg(CustomerMessage.SameCustomerExist);
                logger.debug("重复的记录");
                invalidList.add(errorResult);
            }
            // BUGFIX 重复的记录可以进入数据库 2018-03-22
            Customer customer = new Customer();
            CommonUtils.copyProperties(customerDto, customer);
            logger.debug("insert start");
            customerMapper.insert(customer);
            logger.debug("insert finished");

        }

        OperationLogDto operationLogDto = new OperationLogDto();
        operationLogDto.setOperationObject(getAccountNameById(enterpriseAccountId));
        operationLogDto.setModule(OperationModule.BasicDataCustomer.value());
        operationLogDto.setAction(OperationAction.New.value());
        operationLogDto.setLogType(OperateLogType.OperationLogBasicData.value());
        operationLogService.addOperationLog(operationLogDto);
        logger.debug("导入数据结束, 用时[{}ms]", System.currentTimeMillis() - start);

        return invalidList;
    }

    private OperationLogDto populateLogDto(CustomerDto customerDto) {
        OperationLogDto operationLogDto = new OperationLogDto();
        makeUpOperationLogDto(operationLogDto);
        operationLogDto.setOperationObject(
                customerDto.getCode() + CommonConstants.PLUS_SIGN_SEPARATOR + customerDto.getName());
        operationLogDto.setComment(customerDto.getEnterPriseAccountId());
        operationLogDto.setAction(OperationAction.Delete.value());
        return operationLogDto;
    }

    public CustomerValidateInfoDto getByEnterpriseAccountSetId(String setId) {
        logger.debug("CustomerService getByEnterpriseAccountSetId");
        logger.debug("get customer by set id, id: {}", setId);

        if (setId == null) {
            throw new ApplicationException("enterprise account set id is null");
        }

        CustomerValidateInfoDto customerValidateInfoDto = new CustomerValidateInfoDto();
        CustomerExample example = new CustomerExample();
        example.createCriteria().andEnterPriseAccountIdEqualTo(setId);
        example.setOrderByClause("code ASC, name ASC");
        List<Customer> customerList = customerMapper.selectByExample(example);
        if (!customerList.isEmpty()) {
            List<CustomerDto> customerDtoList = new ArrayList<>();
            customerValidateInfoDto.setCustomerList(customerDtoList);
            customerList.forEach(a -> {
                CustomerDto customerDto = new CustomerDto();
                CommonUtils.copyProperties(a, customerDto);
                customerDtoList.add(customerDto);
            });
            List<ValidateInfoDto> errorList = new ArrayList<>();
            // 取出重复数据
            getDuplicateData(customerDtoList, errorList);
            // 取出名称为空的数据
            getEmptyName(customerDtoList, errorList);
            customerValidateInfoDto.setValidateInfoList(errorList);
        }
        return customerValidateInfoDto;
    }

    // 拿出名称为空的数据
    private List<ValidateInfoDto> getEmptyName(List<CustomerDto> customerDtoList, List<ValidateInfoDto> errorList) {
        List<CustomerDto> newCustomerDtoList = new ArrayList<>();
        customerDtoList.stream().filter(a -> StringUtils.isEmpty(a.getName())).forEach(newCustomerDtoList::add);
        if (!newCustomerDtoList.isEmpty()) {
            List<String> codeList = new ArrayList<>();
            newCustomerDtoList.forEach(a -> codeList.add(a.getCode()));
            ValidateInfoDto errDto = new ValidateInfoDto();
            errDto.setType(CustomerMessage.CustomerNameEmptyNode);
            errDto.setInValidateCodeList(codeList);
            errorList.add(errDto);
        }
        return errorList;
    }

    // 根据代码取出重复数据
    private List<ValidateInfoDto> getDuplicateData(List<CustomerDto> customerDtoList, List<ValidateInfoDto> errorList) {
        List<CustomerDto> newCustomerDtoList = new ArrayList<>();
        customerDtoList.stream().collect(Collectors.groupingBy(CustomerDto::getCode, Collectors.toList()))
                .forEach((code, list) -> {
                    if (list.size() > 1) {
                        newCustomerDtoList.add(list.get(0));
                    }
                });
        if (!newCustomerDtoList.isEmpty()) {
            List<String> codeList = new ArrayList<>();
            newCustomerDtoList.forEach(a -> codeList.add(a.getCode()));
            ValidateInfoDto errDto = new ValidateInfoDto();
            errDto.setType(CustomerMessage.SameCustomerExist);
            errDto.setInValidateCodeList(codeList);
            errorList.add(errDto);
        }
        return errorList;
    }

    // Get EnterPrise Account Name by id
    private String getAccountNameById(String enterPriseAccountId) {
        String enterpriseAccountName = null;
        if (StringUtils.isEmpty(enterPriseAccountId)) {
            return enterpriseAccountName;
        }
        EnterpriseAccountSet enterpriseAccountSet = enterpriseAccountSetMapper.selectByPrimaryKey(enterPriseAccountId);
        if (enterpriseAccountSet == null) {
            return enterpriseAccountName;
        }
        enterpriseAccountName = enterpriseAccountSet.getName();
        return enterpriseAccountName;
    }

    private List<OperationResultDto<CustomerDto>> validateList(List<CustomerDto> customerDtoList) {

        List<OperationResultDto<CustomerDto>> errList = new ArrayList<>();

        List<CustomerDto> distinctList = new ArrayList<>();

        // group by customer name and get first record for each group
        customerDtoList.stream().collect(Collectors.groupingBy(CustomerDto::getName, Collectors.toList()))
                .forEach((name, customerList) -> distinctList.add(customerList.get(0)));

        // validate customerDto name and code: null and length
        for (CustomerDto customerDto : customerDtoList) {
            OperationResultDto<CustomerDto> validateResult = validateIsEmpty(customerDto);
            if (!validateResult.getResult()) {
                distinctList.remove(customerDto);
                errList.add(validateResult);
            }
        }

        // region customer validation
        List<CustomerDto> duplicateList = getDuplicate(customerDtoList);

        duplicateList.forEach(a -> {
            OperationResultDto<CustomerDto> operationResultDto = new OperationResultDto<>();
            operationResultDto.setResult(false);
            operationResultDto.setResultMsg(CustomerMessage.SameCustomerExist);
            operationResultDto.setData(a);
            errList.add(operationResultDto);
        });

        customerDtoList = distinctList;

        return errList;
    }

    private List<CustomerDto> getDuplicate(List<CustomerDto> customerDtoList) {
        List<CustomerDto> duplicateList = new ArrayList<>();

        // group by customer code and if count in group > 1, then get the first record
        // in the group
        customerDtoList.stream().collect(Collectors.groupingBy(CustomerDto::getCode, Collectors.toList()))
                .forEach((code, groupedList) -> {
                    if (groupedList.size() > 1) {
                        duplicateList.add(groupedList.get(0));
                    }
                });

        for (CustomerDto customerDto : customerDtoList) {
            List<CustomerDto> dup = checkDuplicate(customerDto);
            if (!dup.isEmpty() && !duplicateList.contains(customerDto)) {
                duplicateList.add(customerDto);
            }
        }

        return duplicateList;
    }

    // The customer code cannot be duplicated under the same enterpriseAccount
    private List<CustomerDto> checkDuplicate(CustomerDto customerDto) {
        List<CustomerDto> customerDtoList = new ArrayList<>();
        CustomerExample example = new CustomerExample();
        customerDto.getEnterPriseAccountId();
        example.createCriteria().andEnterPriseAccountIdEqualTo(customerDto.getEnterPriseAccountId())
                .andCodeEqualTo(customerDto.getCode()).andIdNotEqualTo(customerDto.getId());
        List<Customer> customerList = customerMapper.selectByExample(example);

        customerList.forEach(a -> {
            CustomerDto dto = new CustomerDto();
            dto.setCode(a.getCode());
            dto.setEnterPriseAccountId(a.getEnterPriseAccountId());
            dto.setId(a.getId());
            dto.setName(a.getName());
            customerDtoList.add(dto);
        });

        return customerDtoList;
    }

    private OperationResultDto<CustomerDto> validateIsEmpty(CustomerDto customerDto) {
        String errMsg = null;
        OperationResultDto<CustomerDto> operationResultDto = new OperationResultDto<>();
        errMsg = checkEmpty(customerDto);

        if (!StringUtils.isEmpty(errMsg)) {
            operationResultDto.setResult(false);
            operationResultDto.setResultMsg(errMsg);
            operationResultDto.setData(customerDto);
        }

        errMsg = checkLength(customerDto);
        if (!StringUtils.isEmpty(errMsg)) {
            operationResultDto.setResult(false);
            operationResultDto.setResultMsg(errMsg);
            operationResultDto.setData(customerDto);
        }

        operationResultDto.setResult(true);
        return operationResultDto;
    }

    private String checkLength(CustomerDto customerDto) {
        String errMsg = null;

        if (StringUtils.hasText(customerDto.getCode())
                && customerDto.getCode().length() > CommonConstants.CODE_MAX_LENGTH) {
            errMsg = CustomerMessage.CodeOutOfLength;
        }

        if (StringUtils.hasText(customerDto.getName())
                && customerDto.getName().length() > CommonConstants.NAME_MAX_LENGTH) {
            errMsg = CustomerMessage.NameOutOfLength;
        }

        return errMsg;
    }

    private String checkEmpty(CustomerDto customerDto) {
        String errMsg = null;
        if (StringUtils.isEmpty(customerDto.getCode())) {
            errMsg = CustomerMessage.CustomerCodeEmptyNode;
        }
        if (StringUtils.isEmpty(customerDto.getName())) {
            errMsg = CustomerMessage.CustomerNameEmptyNode;
        }
        return errMsg;
    }

}