/**
 * 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 lombok.AllArgsConstructor;
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.PortTerminalExcel;
import org.springblade.system.mapper.PortTerminalMapper;
import org.springblade.system.pojo.entity.PortTerminal;
import org.springblade.system.pojo.entity.Region;
import org.springblade.system.pojo.vo.PortTerminalVO;
import org.springblade.system.service.IPortTerminalService;
import org.springblade.system.service.IRegionService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

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

/**
 * 港口码头主数据 服务实现类
 *
 * @author Chill
 */
@Service
@AllArgsConstructor
public class PortTerminalServiceImpl extends BaseServiceImpl<PortTerminalMapper, PortTerminal> implements IPortTerminalService {

	private static final String CATEGORY_PORT = "港口";
	private static final String CATEGORY_TERMINAL = "码头";
	private static final String SOURCE_INITIAL = "初始化导入";
	private static final String SOURCE_INITIAL_OLD = "初始导入";
	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 CODE_MAX_LENGTH = 30;
	private static final int NAME_MAX_LENGTH = 100;
	private static final int REGION_MAX_LENGTH = 50;
	private static final int REGION_CODE_MAX_LENGTH = 32;
	private static final int DETAIL_ADDRESS_MAX_LENGTH = 255;
	private static final int REMARK_MAX_LENGTH = 200;
	private static final BigDecimal MIN_LONGITUDE = new BigDecimal("-180");
	private static final BigDecimal MAX_LONGITUDE = new BigDecimal("180");
	private static final BigDecimal MIN_LATITUDE = new BigDecimal("-90");
	private static final BigDecimal MAX_LATITUDE = new BigDecimal("90");
	private static final Pattern PORT_CODE_PATTERN = Pattern.compile("^[A-Z]{5}$");
	private static final Pattern TERMINAL_CODE_PATTERN = Pattern.compile("^[A-Z]{5}-[A-Z0-9]+$");

	private final IRegionService regionService;

	@Override
	public IPage<PortTerminalVO> selectPortTerminalPage(IPage<PortTerminalVO> page, PortTerminalVO portTerminal) {
		return page.setRecords(baseMapper.selectPortTerminalPage(page, portTerminal));
	}

	@Override
	@Transactional(rollbackFor = Exception.class)
	public boolean submit(PortTerminal portTerminal) {
		prepare(portTerminal, SOURCE_MANUAL);
		validate(portTerminal);
		return saveOrUpdate(portTerminal);
	}

	@Override
	@Transactional(rollbackFor = Exception.class)
	public boolean changeStatus(Long id, Integer status) {
		if (Func.isEmpty(id)) {
			throw new ServiceException("主键不能为空");
		}
		PortTerminal portTerminal = getById(id);
		if (Func.isEmpty(portTerminal)) {
			throw new ServiceException("港口码头不存在");
		}
		if (!Objects.equals(status, STATUS_ENABLED) && !Objects.equals(status, STATUS_DISABLED)) {
			throw new ServiceException("启停状态不正确");
		}
		if (Objects.equals(status, STATUS_DISABLED) && CATEGORY_PORT.equals(portTerminal.getCategory())) {
			validateEnabledTerminal(portTerminal.getId());
		}
		PortTerminal update = new PortTerminal();
		update.setId(id);
		update.setStatus(status);
		return updateById(update);
	}

	@Override
	public List<PortTerminal> selectEnabledPorts() {
		return list(Wrappers.<PortTerminal>lambdaQuery()
			.eq(PortTerminal::getCategory, CATEGORY_PORT)
			.eq(PortTerminal::getStatus, STATUS_ENABLED)
			.orderByAsc(PortTerminal::getCode));
	}

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

	@Override
	public List<PortTerminalExcel> exportPortTerminal(Wrapper<PortTerminal> queryWrapper) {
		List<PortTerminal> portTerminalList = list(queryWrapper);
		return portTerminalList.stream().map(portTerminal -> {
			PortTerminalExcel excel = Objects.requireNonNull(BeanUtil.copyProperties(portTerminal, PortTerminalExcel.class));
			excel.setRegionCode(portTerminal.getDistrictCode());
			excel.setLongitude(scaleCoordinate(portTerminal.getLongitude(), MIN_LONGITUDE, MAX_LONGITUDE));
			excel.setLatitude(scaleCoordinate(portTerminal.getLatitude(), MIN_LATITUDE, MAX_LATITUDE));
			excel.setDataSource(normalizeDataSource(portTerminal.getDataSource()));
			excel.setStatusName(Objects.equals(portTerminal.getStatus(), STATUS_ENABLED) ? "启用" : "停用");
			return excel;
		}).toList();
	}

	private BigDecimal scaleCoordinate(BigDecimal value, BigDecimal min, BigDecimal max) {
		if (Func.isEmpty(value)) {
			return null;
		}
		return validRange(value, min, max) ? value.setScale(6, RoundingMode.HALF_UP) : null;
	}

	private void prepare(PortTerminal portTerminal, String defaultDataSource) {
		portTerminal.setCode(trimToEmpty(portTerminal.getCode()).toUpperCase(Locale.ROOT));
		portTerminal.setCategory(trimToEmpty(portTerminal.getCategory()));
		portTerminal.setName(trimToEmpty(portTerminal.getName()));
		portTerminal.setCountry(trimToEmpty(portTerminal.getCountry()));
		portTerminal.setCity(trimToEmpty(portTerminal.getCity()));
		portTerminal.setDistrictCode(trimToNull(portTerminal.getDistrictCode()));
		portTerminal.setDistrictName(trimToNull(portTerminal.getDistrictName()));
		portTerminal.setRegionCode(trimToNull(portTerminal.getRegionCode()));
		if (Func.isEmpty(portTerminal.getDistrictCode()) && Func.isNotEmpty(portTerminal.getRegionCode())) {
			portTerminal.setDistrictCode(portTerminal.getRegionCode());
		}
		portTerminal.setDetailAddress(trimToNull(portTerminal.getDetailAddress()));
		portTerminal.setRemark(trimToNull(portTerminal.getRemark()));
		portTerminal.setDataSource(normalizeDataSource(Func.toStrWithEmpty(portTerminal.getDataSource(), defaultDataSource)));
		if (Func.isEmpty(portTerminal.getStatus())) {
			portTerminal.setStatus(STATUS_ENABLED);
		}
		if (CATEGORY_PORT.equals(portTerminal.getCategory())) {
			fillRegion(portTerminal);
			portTerminal.setParentId(null);
			portTerminal.setParentCode(null);
			portTerminal.setParentName(null);
			return;
		}
		fillParentPort(portTerminal);
	}

	private void fillParentPort(PortTerminal portTerminal) {
		PortTerminal parent = null;
		if (Func.isNotEmpty(portTerminal.getParentId())) {
			parent = getById(portTerminal.getParentId());
		}
		if (Func.isEmpty(parent) && Func.isNotEmpty(portTerminal.getParentCode())) {
			parent = getOne(Wrappers.<PortTerminal>lambdaQuery()
				.eq(PortTerminal::getCode, trimToEmpty(portTerminal.getParentCode()).toUpperCase(Locale.ROOT))
				.eq(PortTerminal::getCategory, CATEGORY_PORT)
				.eq(PortTerminal::getIsDeleted, 0));
		}
		if (Func.isEmpty(parent)) {
			throw new ServiceException("码头必须选择上级港口");
		}
		if (!CATEGORY_PORT.equals(parent.getCategory())) {
			throw new ServiceException("上级港口类型不正确");
		}
		portTerminal.setParentId(parent.getId());
		portTerminal.setParentCode(parent.getCode());
		portTerminal.setParentName(parent.getName());
		portTerminal.setCountry(parent.getCountry());
		portTerminal.setCity(parent.getCity());
		portTerminal.setDistrictCode(parent.getDistrictCode());
		portTerminal.setDistrictName(parent.getDistrictName());
		portTerminal.setRegionCode(parent.getDistrictCode());
	}

	private void fillRegion(PortTerminal portTerminal) {
		Region district = null;
		if (Func.isNotEmpty(portTerminal.getDistrictCode())) {
			district = regionService.getById(portTerminal.getDistrictCode());
		}
		if (Func.isEmpty(district) && Func.isNotEmpty(portTerminal.getDistrictName())) {
			if (Func.isNotEmpty(portTerminal.getCity())) {
				List<Region> cityList = regionService.list(Wrappers.<Region>lambdaQuery()
					.eq(Region::getName, portTerminal.getCity())
					.eq(Region::getRegionLevel, 2));
				for (Region city : cityList) {
					district = regionService.getOne(Wrappers.<Region>lambdaQuery()
						.eq(Region::getParentCode, city.getCode())
						.eq(Region::getName, portTerminal.getDistrictName()), false);
					if (Func.isNotEmpty(district)) {
						break;
					}
				}
			}
			if (Func.isEmpty(district)) {
				district = regionService.getOne(Wrappers.<Region>lambdaQuery()
					.eq(Region::getName, portTerminal.getDistrictName())
					.eq(Region::getRegionLevel, 3), false);
			}
		}
		if (Func.isEmpty(district)) {
			throw new ServiceException("请选择区县");
		}
		Region city = regionService.getById(district.getParentCode());
		if (Func.isEmpty(city)) {
			throw new ServiceException("区县所属城市不存在");
		}
		if (Func.isNotEmpty(portTerminal.getCity()) && !Objects.equals(portTerminal.getCity(), city.getName())) {
			throw new ServiceException("区县与城市不匹配");
		}
		portTerminal.setCity(city.getName());
		portTerminal.setDistrictCode(district.getCode());
		portTerminal.setDistrictName(district.getName());
		portTerminal.setRegionCode(district.getCode());
	}

	private void validate(PortTerminal portTerminal) {
		if (!CATEGORY_PORT.equals(portTerminal.getCategory()) && !CATEGORY_TERMINAL.equals(portTerminal.getCategory())) {
			throw new ServiceException("类型只能为港口或码头");
		}
		if (Func.isEmpty(portTerminal.getCode())) {
			throw new ServiceException("编码不能为空");
		}
		if (Func.isEmpty(portTerminal.getName())) {
			throw new ServiceException("港口/码头名称不能为空");
		}
		validateLength(portTerminal.getCode(), CODE_MAX_LENGTH, "编码不能超过30字");
		validateLength(portTerminal.getName(), NAME_MAX_LENGTH, "港口/码头名称不能超过100字");
		validateLength(portTerminal.getCountry(), REGION_MAX_LENGTH, "国家不能超过50字");
		validateLength(portTerminal.getCity(), REGION_MAX_LENGTH, "城市不能超过50字");
		validateLength(portTerminal.getDistrictName(), REGION_MAX_LENGTH, "区县不能超过50字");
		validateLength(portTerminal.getRegionCode(), REGION_CODE_MAX_LENGTH, "行政区划编码不能超过32字");
		validateLength(portTerminal.getDetailAddress(), DETAIL_ADDRESS_MAX_LENGTH, "详细地址不能超过255字");
		validateLength(portTerminal.getRemark(), REMARK_MAX_LENGTH, "备注不能超过200个字");
		if (CATEGORY_PORT.equals(portTerminal.getCategory()) && !PORT_CODE_PATTERN.matcher(portTerminal.getCode()).matches()) {
			throw new ServiceException("港口编码为5位大写字母");
		}
		if (CATEGORY_TERMINAL.equals(portTerminal.getCategory()) && !TERMINAL_CODE_PATTERN.matcher(portTerminal.getCode()).matches()) {
			throw new ServiceException("码头编码格式为港口编码-码头标识");
		}
		if (CATEGORY_TERMINAL.equals(portTerminal.getCategory()) && !portTerminal.getCode().startsWith(portTerminal.getParentCode() + "-")) {
			throw new ServiceException("码头编码必须以所属港口编码开头");
		}
		if (Func.isEmpty(portTerminal.getCountry())) {
			throw new ServiceException("国家不能为空");
		}
		if (Func.isEmpty(portTerminal.getCity())) {
			throw new ServiceException("城市不能为空");
		}
		if (Func.isEmpty(portTerminal.getDistrictCode())) {
			throw new ServiceException("区县不能为空");
		}
		validateDataSource(portTerminal.getDataSource());
		validateStatus(portTerminal.getStatus());
		validateRange(portTerminal.getLongitude(), MIN_LONGITUDE, MAX_LONGITUDE, "经度范围为 -180 到 180");
		validateRange(portTerminal.getLatitude(), MIN_LATITUDE, MAX_LATITUDE, "纬度范围为 -90 到 90");
		validateUniqueCode(portTerminal);
		if (Objects.equals(portTerminal.getStatus(), STATUS_DISABLED) && CATEGORY_PORT.equals(portTerminal.getCategory())) {
			validateEnabledTerminal(portTerminal.getId());
		}
	}

	private void validateUniqueCode(PortTerminal portTerminal) {
		LambdaQueryWrapper<PortTerminal> queryWrapper = Wrappers.<PortTerminal>lambdaQuery()
			.eq(PortTerminal::getCode, portTerminal.getCode())
			.eq(PortTerminal::getIsDeleted, 0);
		if (Func.isNotEmpty(portTerminal.getId())) {
			queryWrapper.ne(PortTerminal::getId, portTerminal.getId());
		}
		if (count(queryWrapper) > 0L) {
			throw new ServiceException("该编码已存在");
		}
	}

	private void validateDataSource(String dataSource) {
		if (!SOURCE_INITIAL.equals(dataSource) && !SOURCE_BATCH.equals(dataSource) && !SOURCE_MANUAL.equals(dataSource)) {
			throw new ServiceException("数据来源不正确");
		}
	}

	private void validateStatus(Integer status) {
		if (!Objects.equals(status, STATUS_ENABLED) && !Objects.equals(status, STATUS_DISABLED)) {
			throw new ServiceException("启停状态不正确");
		}
	}

	private void validateLength(String value, int maxLength, String message) {
		if (Func.isNotEmpty(value) && value.length() > maxLength) {
			throw new ServiceException(message);
		}
	}

	private void validateRange(BigDecimal value, BigDecimal min, BigDecimal max, String message) {
		if (Func.isNotEmpty(value) && !validRange(value, min, max)) {
			throw new ServiceException(message);
		}
	}

	private boolean validRange(BigDecimal value, BigDecimal min, BigDecimal max) {
		return Func.isEmpty(value) || (value.compareTo(min) >= 0 && value.compareTo(max) <= 0);
	}

	private String normalizeDataSource(String dataSource) {
		String value = trimToEmpty(dataSource);
		return SOURCE_INITIAL_OLD.equals(value) ? SOURCE_INITIAL : value;
	}

	private void validateEnabledTerminal(Long parentId) {
		if (Func.isEmpty(parentId)) {
			return;
		}
		long count = count(Wrappers.<PortTerminal>lambdaQuery()
			.eq(PortTerminal::getParentId, parentId)
			.eq(PortTerminal::getCategory, CATEGORY_TERMINAL)
			.eq(PortTerminal::getStatus, STATUS_ENABLED)
			.eq(PortTerminal::getIsDeleted, 0));
		if (count > 0L) {
			throw new ServiceException("该港口下存在已启用码头，请先停用码头");
		}
	}

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

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

}
