/**
 * BladeX Commercial License Agreement
 * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
 * <p>
 * Use of this software is governed by the Commercial License Agreement
 * obtained after purchasing a license from BladeX.
 * <p>
 * 1. This software is for development use only under a valid license
 * from BladeX.
 * <p>
 * 2. Redistribution of this software's source code to any third party
 * without a commercial license is strictly prohibited.
 * <p>
 * 3. Licensees may copyright their own code but cannot use segments
 * from this software for such purposes. Copyright of this software
 * remains with BladeX.
 * <p>
 * Using this software signifies agreement to this License, and the software
 * must not be used for illegal purposes.
 * <p>
 * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
 * not liable for any claims arising from secondary or illegal development.
 * <p>
 * Author: Chill Zhuang (bladejava@qq.com)
 */
package org.springblade.system.service.impl;

import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import org.springblade.core.log.exception.ServiceException;
import org.springblade.core.mp.base.BaseServiceImpl;
import org.springblade.core.tool.utils.BeanUtil;
import org.springblade.core.tool.utils.Func;
import org.springblade.system.cache.DictBizCache;
import org.springblade.system.excel.FeeItemExcel;
import org.springblade.system.excel.FeeItemImportFailureExcel;
import org.springblade.system.mapper.FeeItemMapper;
import org.springblade.system.pojo.entity.DictBiz;
import org.springblade.system.pojo.entity.FeeItem;
import org.springblade.system.pojo.vo.FeeItemVO;
import org.springblade.system.service.IFeeItemService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * 费用项 服务实现类
 *
 * @author Chill
 */
@Service
public class FeeItemServiceImpl extends BaseServiceImpl<FeeItemMapper, FeeItem> implements IFeeItemService {

	private static final String FEE_CATEGORY_CODE = "fee_category";
	private static final int STATUS_ENABLED = 1;
	private static final int STATUS_DISABLED = 2;
	private static final int FEE_CATEGORY_MAX_LENGTH = 50;
	private static final int NAME_MAX_LENGTH = 50;
	private static final int ENGLISH_NAME_MAX_LENGTH = 100;

	@Override
	public IPage<FeeItemVO> selectFeeItemPage(IPage<FeeItemVO> page, FeeItemVO feeItem) {
		return page.setRecords(baseMapper.selectFeeItemPage(page, feeItem));
	}

	@Override
	@Transactional(rollbackFor = Exception.class)
	public boolean submit(FeeItem feeItem) {
		prepare(feeItem);
		validate(feeItem);
		return saveOrUpdate(feeItem);
	}

	@Override
	@Transactional(rollbackFor = Exception.class)
	public boolean changeStatus(Long id, Integer status) {
		if (Func.isEmpty(id)) {
			throw new ServiceException("主键不能为空");
		}
		FeeItem feeItem = getById(id);
		if (Func.isEmpty(feeItem)) {
			throw new ServiceException("费用项不存在");
		}
		if (!Objects.equals(status, STATUS_ENABLED) && !Objects.equals(status, STATUS_DISABLED)) {
			throw new ServiceException("启停状态不正确");
		}
		FeeItem update = new FeeItem();
		update.setId(id);
		update.setStatus(status);
		return updateById(update);
	}

	@Override
	@Transactional(rollbackFor = Exception.class)
	public List<FeeItemImportFailureExcel> importFeeItem(List<FeeItemExcel> data) {
		if (Func.isEmpty(data)) {
			throw new ServiceException("导入数据不能为空");
		}
		List<FeeItemImportFailureExcel> failureList = new ArrayList<>();
		for (int index = 0; index < data.size(); index++) {
			FeeItemExcel excel = data.get(index);
			try {
				FeeItem feeItem = buildImportFeeItem(excel);
				prepare(feeItem);
				validate(feeItem);
				save(feeItem);
			} catch (Exception exception) {
				String message = exception instanceof ServiceException ? exception.getMessage() : "导入失败";
				failureList.add(toImportFailureExcel(excel, "第" + (index + 2) + "行：" + message));
			}
		}
		return failureList;
	}

	@Override
	public List<FeeItemExcel> exportFeeItem(Wrapper<FeeItem> queryWrapper) {
		return list(queryWrapper).stream().map(this::toExcel).toList();
	}

	private void prepare(FeeItem feeItem) {
		feeItem.setFeeCategory(trimToEmpty(feeItem.getFeeCategory()));
		feeItem.setName(trimToEmpty(feeItem.getName()));
		feeItem.setEnglishName(trimToNull(feeItem.getEnglishName()));
		appendFeeCategoryPrefix(feeItem);
		if (Func.isEmpty(feeItem.getStatus())) {
			feeItem.setStatus(STATUS_ENABLED);
		}
	}

	private void validate(FeeItem feeItem) {
		if (Func.isEmpty(feeItem.getFeeCategory())) {
			throw new ServiceException("费用类型不能为空");
		}
		if (!isValidFeeCategory(feeItem.getFeeCategory())) {
			throw new ServiceException("费用类型不正确");
		}
		if (feeItem.getFeeCategory().length() > FEE_CATEGORY_MAX_LENGTH) {
			throw new ServiceException("费用类型不能超过50字");
		}
		if (Func.isEmpty(feeItem.getName())) {
			throw new ServiceException("费用项不能为空");
		}
		if (feeItem.getName().length() > NAME_MAX_LENGTH) {
			throw new ServiceException("费用项不能超过50字");
		}
		if (Func.isNotEmpty(feeItem.getEnglishName()) && feeItem.getEnglishName().length() > ENGLISH_NAME_MAX_LENGTH) {
			throw new ServiceException("费用项代码不能超过100字");
		}
		validateUniqueName(feeItem);
	}

	private void validateUniqueName(FeeItem feeItem) {
		LambdaQueryWrapper<FeeItem> queryWrapper = Wrappers.<FeeItem>lambdaQuery()
			.eq(FeeItem::getName, feeItem.getName())
			.eq(FeeItem::getIsDeleted, 0);
		if (Func.isNotEmpty(feeItem.getId())) {
			queryWrapper.ne(FeeItem::getId, feeItem.getId());
		}
		if (count(queryWrapper) > 0L) {
			throw new ServiceException("该费用项已存在");
		}
	}

	private FeeItem buildImportFeeItem(FeeItemExcel excel) {
		FeeItem feeItem = new FeeItem();
		feeItem.setFeeCategory(resolveFeeCategory(excel.getFeeCategory()));
		feeItem.setEnglishName(trimToNull(excel.getEnglishName()));
		feeItem.setName(trimToEmpty(excel.getName()));
		feeItem.setStatus(STATUS_ENABLED);
		return feeItem;
	}

	private FeeItemExcel toExcel(FeeItem feeItem) {
		FeeItemExcel excel = Objects.requireNonNull(BeanUtil.copyProperties(feeItem, FeeItemExcel.class));
		excel.setFeeCategory(formatFeeCategory(feeItem.getFeeCategory()));
		return excel;
	}

	private FeeItemImportFailureExcel toImportFailureExcel(FeeItemExcel excel, String failureReason) {
		FeeItemImportFailureExcel failureExcel = new FeeItemImportFailureExcel();
		failureExcel.setFeeCategory(excel.getFeeCategory());
		failureExcel.setEnglishName(excel.getEnglishName());
		failureExcel.setName(excel.getName());
		failureExcel.setFailureReason(failureReason);
		return failureExcel;
	}

	private String resolveFeeCategory(String value) {
		String category = trimToEmpty(value);
		if (Func.isEmpty(category)) {
			return category;
		}
		return feeCategoryList().stream()
			.filter(dict -> Objects.equals(category, trimToEmpty(dict.getDictKey())) || Objects.equals(category, trimToEmpty(dict.getDictValue())))
			.findFirst()
			.map(DictBiz::getDictKey)
			.orElse(category);
	}

	private String formatFeeCategory(String value) {
		String category = trimToEmpty(value);
		return feeCategoryList().stream()
			.filter(dict -> Objects.equals(category, trimToEmpty(dict.getDictKey())))
			.findFirst()
			.map(DictBiz::getDictValue)
			.orElse(category);
	}

	private boolean isValidFeeCategory(String value) {
		String category = trimToEmpty(value);
		return feeCategoryList().stream().anyMatch(dict -> Objects.equals(category, trimToEmpty(dict.getDictKey())));
	}

	private List<DictBiz> feeCategoryList() {
		List<DictBiz> dictBizList = DictBizCache.getList(FEE_CATEGORY_CODE);
		return dictBizList == null ? List.of() : dictBizList;
	}

	private void appendFeeCategoryPrefix(FeeItem feeItem) {
		if (Func.isEmpty(feeItem.getEnglishName()) || Func.isEmpty(feeItem.getFeeCategory())) {
			return;
		}
		if (!feeItem.getEnglishName().startsWith(feeItem.getFeeCategory())) {
			feeItem.setEnglishName(feeItem.getFeeCategory() + feeItem.getEnglishName());
		}
	}

	private String trimToEmpty(String value) {
		return value == null ? "" : value.trim();
	}

	private String trimToNull(String value) {
		String trimValue = trimToEmpty(value);
		return trimValue.isEmpty() ? null : trimValue;
	}

}
