package com.xunhong.erp.turbo.biz.infrastructure.gateway;

import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.xunhong.erp.turbo.api.biz.dto.cmd.ExpenseRecordCreateCmd;
import com.xunhong.erp.turbo.api.biz.dto.cmd.ExpenseRecordUpdateCmd;
import com.xunhong.erp.turbo.api.biz.dto.common.ExpenseCost;
import com.xunhong.erp.turbo.api.biz.dto.common.ExpenseProvision;
import com.xunhong.erp.turbo.api.biz.dto.qry.ExpenseRecordListQry;
import com.xunhong.erp.turbo.api.biz.dto.qry.ExpenseRecordShowQry;
import com.xunhong.erp.turbo.biz.domain.entity.ExpenseRecord;
import com.xunhong.erp.turbo.biz.domain.gateway.ExpenseRecordGateway;
import com.xunhong.erp.turbo.biz.infrastructure.convert.ExpenseRecordConvert;
import com.xunhong.erp.turbo.biz.infrastructure.entity.*;
import com.xunhong.erp.turbo.biz.infrastructure.mapper.ExpenseCostMapper;
import com.xunhong.erp.turbo.biz.infrastructure.mapper.ExpenseProvisionMapper;
import com.xunhong.erp.turbo.biz.infrastructure.mapper.ExpenseRecordMapper;
import com.xunhong.erp.turbo.biz.infrastructure.mapper.OrderCostMapper;
import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;

/**
 * @author shenyifei
 */
@Repository
@RequiredArgsConstructor
public class ExpenseRecordGatewayImpl implements ExpenseRecordGateway {
	private final ExpenseRecordMapper expenseRecordMapper;
	private final ExpenseRecordConvert expenseRecordConvert;

	private final ExpenseProvisionMapper expenseProvisionMapper;
	private final ExpenseCostMapper expenseCostMapper;

	private final OrderCostMapper orderCostMapper;

	@NotNull
	private static ExpenseRecordDO getExpenseRecordDO(ExpenseRecordCreateCmd expenseRecordCreateCmd, LocalDate recordDate) {
		ExpenseRecordDO expenseRecordDO = new ExpenseRecordDO();
		expenseRecordDO.setRecordDate(recordDate);

		// 计算总计金额
		BigDecimal totalProvision = BigDecimal.ZERO;
		BigDecimal totalExpense = BigDecimal.ZERO;
		Integer totalVehicleCount = 0;

		// 处理计提明细
		if (expenseRecordCreateCmd.getExpenseProvisionList() != null) {
			totalVehicleCount = expenseRecordCreateCmd.getExpenseProvisionList().size();
			for (ExpenseProvision provision : expenseRecordCreateCmd.getExpenseProvisionList()) {
				if (provision.getProvisionAmount() != null) {
					totalProvision = totalProvision.add(provision.getProvisionAmount());
				}
			}
		}

		// 处理费用明细
		if (expenseRecordCreateCmd.getExpenseCostList() != null) {
			for (ExpenseCost cost : expenseRecordCreateCmd.getExpenseCostList()) {
				if (cost.getExpenseAmount() != null) {
					totalExpense = totalExpense.add(cost.getExpenseAmount());
				}
			}
		}

		expenseRecordDO.setTotalProvision(totalProvision);
		expenseRecordDO.setTotalExpense(totalExpense);
		expenseRecordDO.setDailyProfit(totalProvision.subtract(totalExpense));
		expenseRecordDO.setTotalVehicleCount(totalVehicleCount);
		return expenseRecordDO;
	}

	@Override
	@Transactional(rollbackFor = Exception.class)
	public ExpenseRecord save(ExpenseRecordCreateCmd expenseRecordCreateCmd) {
		// 1. 检查当日是否已存在费用记录
		LocalDate recordDate = expenseRecordCreateCmd.getRecordDate();
		if (recordDate == null) {
			recordDate = LocalDate.now();
		}

		ExpenseRecordDO existingRecord = expenseRecordMapper.selectOne(
			Wrappers.lambdaQuery(ExpenseRecordDO.class)
				.eq(ExpenseRecordDO::getRecordDate, recordDate)
				.last("limit 1")
		);

		// 2. 如果已存在记录，调用update方法
		if (existingRecord != null) {
			ExpenseRecordUpdateCmd updateCmd = new ExpenseRecordUpdateCmd();
			updateCmd.setExpenseRecordId(existingRecord.getExpenseRecordId());
			updateCmd.setRecordDate(recordDate);
			updateCmd.setExpenseCostList(expenseRecordCreateCmd.getExpenseCostList());
			updateCmd.setExpenseProvisionList(expenseRecordCreateCmd.getExpenseProvisionList());
			return update(updateCmd);
		}

		// 3. 创建新的费用记录
		ExpenseRecordDO expenseRecordDO = getExpenseRecordDO(expenseRecordCreateCmd, recordDate);

		// 插入主记录
		expenseRecordMapper.insert(expenseRecordDO);

		// 4. 插入计提明细
		if (expenseRecordCreateCmd.getExpenseProvisionList() != null) {
			for (ExpenseProvision provision : expenseRecordCreateCmd.getExpenseProvisionList()) {
				ExpenseProvisionDO provisionDO = convertToExpenseProvisionDO(provision);
				provisionDO.setExpenseRecordId(expenseRecordDO.getExpenseRecordId());
				expenseProvisionMapper.insert(provisionDO);
			}
		}

		// 5. 插入费用明细
		if (expenseRecordCreateCmd.getExpenseCostList() != null) {
			for (ExpenseCost cost : expenseRecordCreateCmd.getExpenseCostList()) {
				ExpenseCostDO costDO = convertToExpenseCostDO(cost);
				costDO.setExpenseRecordId(expenseRecordDO.getExpenseRecordId());
				costDO.setCreatedAt(LocalDateTime.now());
				expenseCostMapper.insert(costDO);
			}
		}

		return expenseRecordConvert.toExpenseRecord(expenseRecordDO);
	}

	@Override
	public List<ExpenseRecord> list(ExpenseRecordListQry expenseRecordListQry) {
		LambdaQueryWrapper<ExpenseRecordDO> queryWrapper = Wrappers.lambdaQuery(ExpenseRecordDO.class);
		queryWrapper.gt(Objects.nonNull(expenseRecordListQry.getStartDate()), ExpenseRecordDO::getRecordDate, expenseRecordListQry.getStartDate());
		queryWrapper.lt(Objects.nonNull(expenseRecordListQry.getEndDate()), ExpenseRecordDO::getRecordDate, expenseRecordListQry.getEndDate());

		List<ExpenseRecordDO> expenseRecordDOList = expenseRecordMapper.selectList(queryWrapper);
		List<Long> expenseRecordIdList = expenseRecordDOList.stream().map(ExpenseRecordDO::getExpenseRecordId).toList();
		if (CollUtil.isNotEmpty(expenseRecordIdList)) {
			LambdaQueryWrapper<ExpenseProvisionDO> queryWrapper1 = Wrappers.lambdaQuery(ExpenseProvisionDO.class);
			queryWrapper1.in(ExpenseProvisionDO::getExpenseRecordId, expenseRecordIdList);
			List<ExpenseProvisionDO> expenseProvisionDOList = expenseProvisionMapper.selectList(queryWrapper1);
			expenseRecordDOList.forEach(expenseRecordDO -> {
				expenseRecordDO.setExpenseProvisionDOList(expenseProvisionDOList.stream()
					.filter(expenseProvisionDO -> expenseProvisionDO.getExpenseRecordId().equals(expenseRecordDO.getExpenseRecordId()))
					.toList());
			});

			LambdaQueryWrapper<ExpenseCostDO> queryWrapper2 = Wrappers.lambdaQuery(ExpenseCostDO.class);
			queryWrapper2.in(ExpenseCostDO::getExpenseRecordId, expenseRecordIdList);
			List<ExpenseCostDO> expenseCostDOList = expenseCostMapper.selectList(queryWrapper2);
			expenseRecordDOList.forEach(expenseRecordDO -> {
				expenseRecordDO.setExpenseCostDOList(expenseCostDOList.stream()
					.filter(expenseCostDO -> expenseCostDO.getExpenseRecordId().equals(expenseRecordDO.getExpenseRecordId()))
					.toList());
			});
		}
		return expenseRecordDOList.stream().map(expenseRecordConvert::toExpenseRecord).toList();
	}

	@Override
	@Transactional(rollbackFor = Exception.class)
	public ExpenseRecord update(ExpenseRecordUpdateCmd expenseRecordUpdateCmd) {
		// 1. 查询现有记录
		ExpenseRecordDO expenseRecordDO = expenseRecordMapper.selectById(expenseRecordUpdateCmd.getExpenseRecordId());
		if (expenseRecordDO == null) {
			throw new RuntimeException("费用记录不存在，ID: " + expenseRecordUpdateCmd.getExpenseRecordId());
		}

		// 2. 获取现有的明细记录
		List<ExpenseProvisionDO> existingProvisions = expenseProvisionMapper.selectList(
			Wrappers.lambdaQuery(ExpenseProvisionDO.class)
				.eq(ExpenseProvisionDO::getExpenseRecordId, expenseRecordUpdateCmd.getExpenseRecordId())
		);

		List<ExpenseCostDO> existingCosts = expenseCostMapper.selectList(
			Wrappers.lambdaQuery(ExpenseCostDO.class)
				.eq(ExpenseCostDO::getExpenseRecordId, expenseRecordUpdateCmd.getExpenseRecordId())
		);

		// 3. 精细化更新计提明细
		if (expenseRecordUpdateCmd.getExpenseProvisionList() != null) {
			updateExpenseProvisions(expenseRecordUpdateCmd.getExpenseRecordId(),
				expenseRecordUpdateCmd.getExpenseProvisionList(), existingProvisions);
		}

		// 4. 精细化更新费用明细
		if (expenseRecordUpdateCmd.getExpenseCostList() != null) {
			updateExpenseCosts(expenseRecordUpdateCmd.getExpenseRecordId(),
				expenseRecordUpdateCmd.getExpenseCostList(), existingCosts);
		}

		// 5. 重新计算并更新主记录的总计金额
		recalculateAndUpdateExpenseRecord(expenseRecordUpdateCmd.getExpenseRecordId());

		// 6. 返回更新后的记录
		ExpenseRecordDO updatedRecord = expenseRecordMapper.selectById(expenseRecordUpdateCmd.getExpenseRecordId());
		return expenseRecordConvert.toExpenseRecord(updatedRecord);
	}

	@Override
	public ExpenseRecord show(ExpenseRecordShowQry expenseRecordShowQry) {
		LambdaQueryWrapper<ExpenseRecordDO> queryWrapper = Wrappers.lambdaQuery(ExpenseRecordDO.class);
		queryWrapper.eq(Objects.nonNull(expenseRecordShowQry.getExpenseRecordId()), ExpenseRecordDO::getExpenseRecordId, expenseRecordShowQry.getExpenseRecordId());
		queryWrapper.eq(Objects.nonNull(expenseRecordShowQry.getRecordDate()), ExpenseRecordDO::getRecordDate, expenseRecordShowQry.getRecordDate());

		queryWrapper.last("limit 1");

		ExpenseRecordDO expenseRecordDO = expenseRecordMapper.selectOne(queryWrapper);

		if (Objects.nonNull(expenseRecordDO)) {
			expenseRecordDO.setExpenseProvisionDOList(expenseProvisionMapper.selectList(
				Wrappers.lambdaQuery(ExpenseProvisionDO.class)
					.eq(ExpenseProvisionDO::getExpenseRecordId, expenseRecordDO.getExpenseRecordId())
			));

			expenseRecordDO.setExpenseCostDOList(expenseCostMapper.selectList(
				Wrappers.lambdaQuery(ExpenseCostDO.class)
					.eq(ExpenseCostDO::getExpenseRecordId, expenseRecordDO.getExpenseRecordId())
			));
		}

		return expenseRecordConvert.toExpenseRecord(expenseRecordDO);
	}

	@Override
	@Transactional(rollbackFor = Exception.class)
	public void addProvision(Long orderId) {
		// 1. 查询订单的计提费用信息
		OrderCostDO orderCostDO = orderCostMapper.selectByOrderId(orderId, "计提费");
		if (orderCostDO == null) {
			return;
		}

		// 2. 查询车辆信息获取车次号
		OrderVehicleDO orderVehicle = orderCostDO.getOrderVehicleDO();

		// 3. 创建或更新费用记录
		LocalDate today = orderVehicle.getDeliveryTime();
		ExpenseRecordDO expenseRecord = expenseRecordMapper.selectOne(
			Wrappers.lambdaQuery(ExpenseRecordDO.class)
				.eq(ExpenseRecordDO::getRecordDate, today)
				.last("limit 1")
		);

		if (expenseRecord == null) {
			// 创建新的费用记录
			expenseRecord = new ExpenseRecordDO();
			expenseRecord.setRecordDate(today);
			expenseRecord.setTotalProvision(orderCostDO.getPrice());
			expenseRecord.setTotalExpense(BigDecimal.ZERO);
			expenseRecord.setDailyProfit(orderCostDO.getPrice());
			expenseRecord.setCreatedAt(LocalDateTime.now());
			expenseRecordMapper.insert(expenseRecord);
		} else {
			// 更新现有费用记录
			BigDecimal newTotalProvision = expenseRecord.getTotalProvision().add(orderCostDO.getPrice());
			BigDecimal newDailyProfit = newTotalProvision.subtract(expenseRecord.getTotalExpense());
			expenseRecord.setTotalProvision(newTotalProvision);
			expenseRecord.setDailyProfit(newDailyProfit);
			expenseRecordMapper.updateById(expenseRecord);
		}

		// 4. 创建费用计提明细记录
		ExpenseProvisionDO expenseProvision = new ExpenseProvisionDO();
		expenseProvision.setExpenseRecordId(expenseRecord.getExpenseRecordId());
		expenseProvision.setOrderId(orderId);
		expenseProvision.setProvisionAmount(orderCostDO.getPrice());

		// 设置经销商名称
		expenseProvision.setDealerName(orderVehicle.getDealerName());

		// 设置车次号
		expenseProvision.setVehicleNo(orderVehicle.getVehicleNo());

		// 插入费用计提明细
		expenseProvisionMapper.insert(expenseProvision);
	}

	/**
	 * 精细化更新费用计提明细
	 */
	private void updateExpenseProvisions(Long expenseRecordId, List<ExpenseProvision> newProvisions,
										 List<ExpenseProvisionDO> existingProvisions) {
		// 记录需要保留的现有记录ID
		Set<Long> existingIdsToKeep = new HashSet<>();

		// 处理新的计提明细
		for (ExpenseProvision provision : newProvisions) {
			// 更新现有记录
			ExpenseProvisionDO existingDO = existingProvisions.stream()
				.filter(p -> p.getExpenseProvisionId().equals(provision.getExpenseProvisionId()))
				.findFirst()
				.orElse(null);

			if (existingDO != null) {
				// 更新现有记录
				updateExpenseProvisionDO(existingDO, provision);
				expenseProvisionMapper.updateById(existingDO);
				existingIdsToKeep.add(provision.getExpenseProvisionId());
			} else {
				// 插入新记录
				ExpenseProvisionDO newDO = convertToExpenseProvisionDO(provision);
				newDO.setExpenseRecordId(expenseRecordId);
				expenseProvisionMapper.insert(newDO);
			}
		}

		// 删除不再存在的记录
		for (ExpenseProvisionDO existing : existingProvisions) {
			if (!existingIdsToKeep.contains(existing.getExpenseProvisionId())) {
				expenseProvisionMapper.deleteById(existing.getExpenseProvisionId());
			}
		}
	}

	/**
	 * 精细化更新费用明细
	 */
	private void updateExpenseCosts(Long expenseRecordId, List<ExpenseCost> newCosts,
									List<ExpenseCostDO> existingCosts) {
		// 记录需要保留的现有记录ID
		Set<Long> existingIdsToKeep = new HashSet<>();

		// 处理新的费用明细
		for (ExpenseCost cost : newCosts) {
			// 更新现有记录
			ExpenseCostDO existingDO = existingCosts.stream()
				.filter(c -> c.getExpenseCostId().equals(cost.getExpenseCostId()))
				.findFirst()
				.orElse(null);

			if (existingDO != null) {
				// 更新现有记录
				updateExpenseCostDO(existingDO, cost);
				expenseCostMapper.updateById(existingDO);
				existingIdsToKeep.add(cost.getExpenseCostId());
			} else {
				// 插入新记录
				ExpenseCostDO newDO = convertToExpenseCostDO(cost);
				newDO.setExpenseRecordId(expenseRecordId);
				newDO.setCreatedAt(LocalDateTime.now());
				expenseCostMapper.insert(newDO);
			}
		}

		// 删除不再存在的记录
		for (ExpenseCostDO existing : existingCosts) {
			if (!existingIdsToKeep.contains(existing.getExpenseCostId())) {
				expenseCostMapper.deleteById(existing.getExpenseCostId());
			}
		}
	}

	/**
	 * 重新计算并更新费用记录的总计金额
	 */
	private void recalculateAndUpdateExpenseRecord(Long expenseRecordId) {
		// 重新计算计提总金额和车次数量
		List<ExpenseProvisionDO> provisions = expenseProvisionMapper.selectList(
			Wrappers.lambdaQuery(ExpenseProvisionDO.class)
				.eq(ExpenseProvisionDO::getExpenseRecordId, expenseRecordId)
		);

		BigDecimal totalProvision = provisions.stream()
			.map(ExpenseProvisionDO::getProvisionAmount)
			.filter(Objects::nonNull)
			.reduce(BigDecimal.ZERO, BigDecimal::add);

		int totalVehicleCount = provisions.size();

		// 重新计算费用总额
		List<ExpenseCostDO> costs = expenseCostMapper.selectList(
			Wrappers.lambdaQuery(ExpenseCostDO.class)
				.eq(ExpenseCostDO::getExpenseRecordId, expenseRecordId)
		);

		BigDecimal totalExpense = costs.stream()
			.map(ExpenseCostDO::getExpenseAmount)
			.filter(Objects::nonNull)
			.reduce(BigDecimal.ZERO, BigDecimal::add);

		// 更新主记录
		ExpenseRecordDO updateRecord = new ExpenseRecordDO();
		updateRecord.setExpenseRecordId(expenseRecordId);
		updateRecord.setTotalProvision(totalProvision);
		updateRecord.setTotalExpense(totalExpense);
		updateRecord.setDailyProfit(totalProvision.subtract(totalExpense));
		updateRecord.setTotalVehicleCount(totalVehicleCount);

		expenseRecordMapper.updateById(updateRecord);
	}

	/**
	 * 转换ExpenseProvision到ExpenseProvisionDO
	 */
	private ExpenseProvisionDO convertToExpenseProvisionDO(ExpenseProvision provision) {
		ExpenseProvisionDO provisionDO = new ExpenseProvisionDO();
		provisionDO.setDealerName(provision.getDealerName());
		provisionDO.setVehicleNo(provision.getVehicleNo());
		provisionDO.setProvisionAmount(provision.getProvisionAmount());
		provisionDO.setOrderId(provision.getOrderId());
		return provisionDO;
	}

	/**
	 * 转换ExpenseCost到ExpenseCostDO
	 */
	private ExpenseCostDO convertToExpenseCostDO(ExpenseCost cost) {
		ExpenseCostDO costDO = new ExpenseCostDO();
		costDO.setCostId(cost.getCostId());
		costDO.setCostName(cost.getCostName());
		costDO.setExpenseAmount(cost.getExpenseAmount());
		costDO.setRemark(cost.getRemark());
		return costDO;
	}

	/**
	 * 更新ExpenseProvisionDO
	 */
	private void updateExpenseProvisionDO(ExpenseProvisionDO existingDO, ExpenseProvision provision) {
		existingDO.setDealerName(provision.getDealerName());
		existingDO.setVehicleNo(provision.getVehicleNo());
		existingDO.setProvisionAmount(provision.getProvisionAmount());
		existingDO.setOrderId(provision.getOrderId());
	}

	/**
	 * 更新ExpenseCostDO
	 */
	private void updateExpenseCostDO(ExpenseCostDO existingDO, ExpenseCost cost) {
		existingDO.setCostId(cost.getCostId());
		existingDO.setCostName(cost.getCostName());
		existingDO.setExpenseAmount(cost.getExpenseAmount());
		existingDO.setRemark(cost.getRemark());
	}
}

