/**
 * 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.excel.CurrencyExcel;
import org.springblade.system.mapper.CurrencyMapper;
import org.springblade.system.pojo.entity.Currency;
import org.springblade.system.pojo.vo.CurrencyVO;
import org.springblade.system.service.ICurrencyService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.regex.Pattern;

/**
 * 币种汇率 服务实现类
 *
 * @author Chill
 */
@Service
public class CurrencyServiceImpl extends BaseServiceImpl<CurrencyMapper, Currency> implements ICurrencyService {

	private static final String SOURCE_BATCH = "批量导入";
	private static final String SOURCE_MANUAL = "手工录入";
	private static final int STATUS_ENABLED = 1;
	private static final int STATUS_DISABLED = 2;
	private static final int YES = 1;
	private static final int NO = 0;
	private static final int REMARK_MAX_LENGTH = 200;
	private static final int MIN_DECIMAL_PLACES = 0;
	private static final int MAX_DECIMAL_PLACES = 8;
	private static final int EXCHANGE_RATE_SCALE = 6;
	private static final Pattern CURRENCY_CODE_PATTERN = Pattern.compile("^[A-Z]{3}$");

	@Override
	public IPage<CurrencyVO> selectCurrencyPage(IPage<CurrencyVO> page, CurrencyVO currency) {
		return page.setRecords(baseMapper.selectCurrencyPage(page, currency));
	}

	@Override
	@Transactional(rollbackFor = Exception.class)
	public boolean submit(Currency currency) {
		Currency oldCurrency = Func.isNotEmpty(currency.getId()) ? getById(currency.getId()) : null;
		prepare(currency, SOURCE_MANUAL);
		validate(currency);
		boolean result = saveOrUpdate(currency);
		if (Func.isNotEmpty(currency.getId()) && Func.isEmpty(currency.getExchangeRate())) {
			update(Wrappers.<Currency>lambdaUpdate()
				.set(Currency::getExchangeRate, null)
				.eq(Currency::getId, currency.getId()));
		}
		rebuildEnabledValidity(currency.getCode());
		if (oldCurrency != null && !Objects.equals(oldCurrency.getCode(), currency.getCode())) {
			rebuildEnabledValidity(oldCurrency.getCode());
		}
		return result;
	}

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

	@Override
	public List<CurrencyExcel> importCurrency(List<CurrencyExcel> data) {
		if (Func.isEmpty(data)) {
			throw new ServiceException("导入数据不能为空");
		}
		List<CurrencyExcel> errorList = new ArrayList<>();
		int successCount = 0;
		for (int index = 0; index < data.size(); index++) {
			CurrencyExcel excel = data.get(index);
			try {
				Currency currency = Objects.requireNonNull(BeanUtil.copyProperties(excel, Currency.class));
				currency.setDataSource(SOURCE_BATCH);
				currency.setStatus(STATUS_ENABLED);
				prepare(currency, SOURCE_BATCH);
				validate(currency);
				save(currency);
				rebuildEnabledValidity(currency.getCode());
				successCount++;
			} catch (Exception exception) {
				String message = exception instanceof ServiceException ? exception.getMessage() : "导入失败";
				excel.setErrorMessage("第" + (index + 2) + "行：" + message);
				errorList.add(excel);
			}
		}
		return errorList;
	}

	@Override
	public List<CurrencyExcel> exportCurrency(Wrapper<Currency> queryWrapper) {
		List<Currency> currencyList = list(queryWrapper);
		return currencyList.stream().map(currency -> {
			CurrencyExcel excel = Objects.requireNonNull(BeanUtil.copyProperties(currency, CurrencyExcel.class));
			excel.setStatusName(Objects.equals(currency.getStatus(), STATUS_ENABLED) ? "启用" : "停用");
			return excel;
		}).toList();
	}

	@Override
	public CurrencyVO getEffectiveRate(String code, LocalDate businessDate) {
		String currencyCode = trimToEmpty(code).toUpperCase(Locale.ROOT);
		if (!CURRENCY_CODE_PATTERN.matcher(currencyCode).matches()) {
			throw new ServiceException("币种代码为3位大写字母");
		}
		if (count(Wrappers.<Currency>lambdaQuery()
			.eq(Currency::getCode, currencyCode)
			.eq(Currency::getIsDeleted, 0)) <= 0L) {
			throw new ServiceException("币种不存在");
		}
		LocalDate queryDate = businessDate == null ? LocalDate.now() : businessDate;
		Currency currency = getOne(Wrappers.<Currency>lambdaQuery()
			.eq(Currency::getCode, currencyCode)
			.eq(Currency::getStatus, STATUS_ENABLED)
			.eq(Currency::getIsDeleted, 0)
			.le(Currency::getEffectiveDate, queryDate)
			.and(wrapper -> wrapper.isNull(Currency::getExpiryDate).or().gt(Currency::getExpiryDate, queryDate))
			.orderByDesc(Currency::getEffectiveDate)
			.last("limit 1"));
		if (currency == null) {
			throw new ServiceException("未找到有效汇率");
		}
		return Objects.requireNonNull(BeanUtil.copyProperties(currency, CurrencyVO.class));
	}

	private void prepare(Currency currency, String defaultDataSource) {
		currency.setCode(trimToEmpty(currency.getCode()).toUpperCase(Locale.ROOT));
		currency.setName(trimToEmpty(currency.getName()));
		currency.setEnglishName(trimToNull(currency.getEnglishName()));
		currency.setSymbol(trimToNull(currency.getSymbol()));
		currency.setRemark(trimToNull(currency.getRemark()));
		currency.setDataSource(Func.toStrWithEmpty(currency.getDataSource(), defaultDataSource));
		currency.setExpiryDate(null);
		if (Func.isEmpty(currency.getDecimalPlaces())) {
			currency.setDecimalPlaces(2);
		}
		if (Func.isEmpty(currency.getBaseCurrency())) {
			currency.setBaseCurrency(NO);
		}
		if (Func.isEmpty(currency.getStatus())) {
			currency.setStatus(STATUS_ENABLED);
		}
	}

	private void validate(Currency currency) {
		if (Func.isEmpty(currency.getCode())) {
			throw new ServiceException("币种代码不能为空");
		}
		if (!CURRENCY_CODE_PATTERN.matcher(currency.getCode()).matches()) {
			throw new ServiceException("币种代码为3位大写字母");
		}
		if (Func.isEmpty(currency.getName())) {
			throw new ServiceException("币种名称不能为空");
		}
		if (currency.getDecimalPlaces() < MIN_DECIMAL_PLACES || currency.getDecimalPlaces() > MAX_DECIMAL_PLACES) {
			throw new ServiceException("小数位数范围为0到8");
		}
		if (Func.isNotEmpty(currency.getExchangeRate())) {
			if (currency.getExchangeRate().compareTo(BigDecimal.ZERO) <= 0) {
				throw new ServiceException("汇率必须大于0");
			}
			if (currency.getExchangeRate().stripTrailingZeros().scale() > EXCHANGE_RATE_SCALE) {
				throw new ServiceException("汇率最多保留6位小数");
			}
		}
		if (Func.isEmpty(currency.getEffectiveDate())) {
			throw new ServiceException("生效日期不能为空");
		}
		if (!Objects.equals(currency.getBaseCurrency(), YES) && !Objects.equals(currency.getBaseCurrency(), NO)) {
			throw new ServiceException("是否本位币取值不正确");
		}
		if (Func.isNotEmpty(currency.getRemark()) && currency.getRemark().length() > REMARK_MAX_LENGTH) {
			throw new ServiceException("备注不能超过200字");
		}
		validateCodeName(currency);
		validateUniqueEffectiveDate(currency);
	}

	private void validateUniqueEffectiveDate(Currency currency) {
		LambdaQueryWrapper<Currency> queryWrapper = Wrappers.<Currency>lambdaQuery()
			.eq(Currency::getCode, currency.getCode())
			.eq(Currency::getEffectiveDate, currency.getEffectiveDate())
			.eq(Currency::getIsDeleted, 0);
		if (Func.isNotEmpty(currency.getId())) {
			queryWrapper.ne(Currency::getId, currency.getId());
		}
		if (count(queryWrapper) > 0L) {
			throw new ServiceException("同币种同生效日期已存在");
		}
	}

	private void validateCodeName(Currency currency) {
		LambdaQueryWrapper<Currency> queryWrapper = Wrappers.<Currency>lambdaQuery()
			.eq(Currency::getCode, currency.getCode())
			.eq(Currency::getIsDeleted, 0);
		if (Func.isNotEmpty(currency.getId())) {
			queryWrapper.ne(Currency::getId, currency.getId());
		}
		Currency exists = getOne(queryWrapper.last("limit 1"));
		if (exists != null && !Objects.equals(exists.getName(), currency.getName())) {
			throw new ServiceException("同一币种编码的币种名称必须一致");
		}
	}

	private void rebuildEnabledValidity(String code) {
		String currencyCode = trimToEmpty(code).toUpperCase(Locale.ROOT);
		if (Func.isEmpty(currencyCode)) {
			return;
		}
		List<Currency> enabledRecords = list(Wrappers.<Currency>lambdaQuery()
			.eq(Currency::getCode, currencyCode)
			.eq(Currency::getStatus, STATUS_ENABLED)
			.eq(Currency::getIsDeleted, 0)
			.orderByAsc(Currency::getEffectiveDate)
			.orderByAsc(Currency::getCreateTime));
		for (int index = 0; index < enabledRecords.size(); index++) {
			Currency record = enabledRecords.get(index);
			Currency update = new Currency();
			update.setId(record.getId());
			update.setExpiryDate(index + 1 < enabledRecords.size() ? enabledRecords.get(index + 1).getEffectiveDate() : null);
			updateById(update);
		}
	}

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

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

}
