package com.fjhx.account.service.account.impl;

import cn.hutool.core.date.DateException;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import com.fjhx.account.entity.account.bo.CapitalDailyBo;
import com.fjhx.account.entity.account.dto.CapitalDailyDto;
import com.fjhx.account.entity.account.po.AccountManagement;
import com.fjhx.account.entity.account.po.AccountRemainder;
import com.fjhx.account.entity.account.po.AccountRunningWater;
import com.fjhx.account.entity.account.vo.CapitalDailyVo;
import com.fjhx.account.service.account.AccountManagementService;
import com.fjhx.account.service.account.AccountRemainderService;
import com.fjhx.account.service.account.AccountRunningWaterService;
import com.fjhx.account.service.account.StatementService;
import com.fjhx.common.entity.corporation.po.Corporation;
import com.fjhx.common.service.corporation.CorporationService;
import com.ruoyi.common.core.domain.BaseIdPo;
import com.ruoyi.common.core.domain.BasePo;
import com.ruoyi.common.exception.ServiceException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

@Service
public class StatementServiceImpl implements StatementService {

    @Autowired
    private CorporationService corporationService;

    @Autowired
    private AccountManagementService accountManagementService;

    @Autowired
    private AccountRemainderService accountRemainderService;

    @Autowired
    private AccountRunningWaterService accountRunningWaterService;

    @Override
    public List<CapitalDailyVo> getCapitalDaily(CapitalDailyDto dto) {

        Date beginDate, endDate;

        Date date = new Date();
        try {
            Date parse = DateUtil.parse(dto.getDateBetween(), "yyyy-MM");
            if (parse.after(DateUtil.endOfMonth(date))) {
                throw new ServiceException("统计日期不能大于本月");
            }

            boolean sameMonth = DateUtil.isSameMonth(parse, date);

            beginDate = DateUtil.beginOfMonth(parse);
            endDate = sameMonth ? DateUtil.endOfDay(date) : DateUtil.endOfMonth(parse);
        } catch (DateException dateException) {
            throw new ServiceException("范围期间日期格式错误,应为 'yyyy-MM' 格式");
        }

        // 获取归属公司
        List<Corporation> corporationList = getCorporationList(dto.getCorporationId());
        if (corporationList.size() == 0) {
            return new ArrayList<>();
        }

        // 获取账户
        List<AccountManagement> managementList = getManagementList(dto.getManagementId(), corporationList);
        if (managementList.size() == 0) {
            return new ArrayList<>();
        }

        // 获取账户余额
        List<AccountRemainder> remainderList = getRemainderList(managementList);
        if (remainderList.size() == 0) {
            return new ArrayList<>();
        }

        // 合并数据成bo对象
        List<CapitalDailyBo> capitalDailyBoList = mergeBo(corporationList, managementList, remainderList);
        if (capitalDailyBoList.size() == 0) {
            return new ArrayList<>();
        }

        // 查询流水记录
        List<AccountRunningWater> runningWaterList = getRunningWaterList(beginDate, managementList);

        // 计算出结束时间余额
        if (!DateUtil.isSameMonth(endDate, date)) {
            calculationEndDateBalance(endDate, capitalDailyBoList, runningWaterList);
        }

        // 赋值收支流水明细
        setRunningWaterDetail(capitalDailyBoList, endDate, runningWaterList);

        // 格式化成vo
        return formatVo(capitalDailyBoList);
    }

    /**
     * 获取归属公司列表
     */
    private List<Corporation> getCorporationList(Long corporationId) {
        return corporationService.list(q -> q
                .select(BaseIdPo::getId, Corporation::getName)
                .eq(ObjectUtil.isNotEmpty(corporationId), Corporation::getId, corporationId));
    }

    /**
     * 获取账户
     */
    private List<AccountManagement> getManagementList(Long managementId, List<Corporation> corporationList) {
        List<Long> corporationIdList = corporationList.stream().map(BaseIdPo::getId).collect(Collectors.toList());

        return accountManagementService.list(q -> q
                .select(BaseIdPo::getId, AccountManagement::getAlias, AccountManagement::getCorporationId)
                .in(AccountManagement::getCorporationId, corporationIdList)
                .eq(ObjectUtil.isNotEmpty(managementId), BaseIdPo::getId, managementId)
        );
    }

    /**
     * 获取货币并合并获取归属公司和账户数据
     */
    private List<AccountRemainder> getRemainderList(List<AccountManagement> accountManagementList) {

        // 账户id
        List<Long> accountManagementIdList =
                accountManagementList.stream().map(BaseIdPo::getId).collect(Collectors.toList());

        return accountRemainderService.list(q -> q
                .select(AccountRemainder::getCurrency, AccountRemainder::getRemainder, AccountRemainder::getAccountManagementId)
                .in(AccountRemainder::getAccountManagementId, accountManagementIdList)
        );
    }

    /**
     * 合并数据成bo对象
     */
    private List<CapitalDailyBo> mergeBo(List<Corporation> corporationList,
                                         List<AccountManagement> accountManagementList,
                                         List<AccountRemainder> remainderList) {

        Map<Long, List<AccountManagement>> accountManagementMap = accountManagementList.stream()
                .collect(Collectors.groupingBy(AccountManagement::getCorporationId));

        Map<Long, List<AccountRemainder>> AccountRemainderMap = remainderList.stream()
                .collect(Collectors.groupingBy(AccountRemainder::getAccountManagementId));

        List<CapitalDailyBo> capitalDailyBoList = new ArrayList<>();

        for (Corporation corporation : corporationList) {

            List<AccountManagement> accountManagementItemList = accountManagementMap.get(corporation.getId());
            if (accountManagementItemList == null) {
                continue;
            }

            for (AccountManagement accountManagement : accountManagementItemList) {

                List<AccountRemainder> accountRemainderList = AccountRemainderMap.get(accountManagement.getId());
                if (accountRemainderList == null) {
                    continue;
                }

                List<CapitalDailyBo> itemCapitalDailyBoList = accountRemainderList.stream()
                        .map(item -> {
                            CapitalDailyBo capitalDailyBo = new CapitalDailyBo();
                            capitalDailyBo.setCorporationId(corporation.getId());
                            capitalDailyBo.setCorporationName(corporation.getName());
                            capitalDailyBo.setAccountManagementId(accountManagement.getId());
                            capitalDailyBo.setAccountManagementAlias(accountManagement.getAlias());
                            capitalDailyBo.setCurrencyType(item.getCurrency());
                            capitalDailyBo.setRemainder(item.getRemainder());
                            return capitalDailyBo;
                        }).collect(Collectors.toList());

                capitalDailyBoList.addAll(itemCapitalDailyBoList);
            }

        }

        return capitalDailyBoList;
    }

    /**
     * 查询流水记录
     */
    private List<AccountRunningWater> getRunningWaterList(Date beginDate, List<AccountManagement> managementList) {

        List<Long> managementIdList = managementList.stream().map(BaseIdPo::getId).collect(Collectors.toList());

        return accountRunningWaterService.list(q -> q
                .ge(BasePo::getCreateTime, beginDate)
                .in(AccountRunningWater::getAccountManagementId, managementIdList)
        );

    }

    /**
     * 计算出结束时间余额
     */
    private void calculationEndDateBalance(Date endDate,
                                           List<CapitalDailyBo> capitalDailyBoList,
                                           List<AccountRunningWater> runningWaterList) {

        Map<Long, Map<String, List<AccountRunningWater>>> managementIdCurrencyRunningWaterMap =
                runningWaterList.stream().collect(Collectors.groupingBy(
                        AccountRunningWater::getAccountManagementId,
                        Collectors.groupingBy(AccountRunningWater::getCurrency)
                ));

        for (CapitalDailyBo capitalDailyBo : capitalDailyBoList) {
            Long accountManagementId = capitalDailyBo.getAccountManagementId();
            Map<String, List<AccountRunningWater>> currencyRunningWaterMap = managementIdCurrencyRunningWaterMap.get(accountManagementId);

            if (ObjectUtil.isEmpty(currencyRunningWaterMap)) {
                continue;
            }

            List<AccountRunningWater> itemRunningWaterList = currencyRunningWaterMap.get(capitalDailyBo.getCurrencyType());

            if (ObjectUtil.isEmpty(itemRunningWaterList)) {
                continue;
            }

            // 计算结束时间至今金额改变值
            BigDecimal reduce = itemRunningWaterList.stream()
                    .filter(item -> item.getCreateTime().after(endDate))
                    .map(item -> Objects.equals(item.getStatus(), "10") ? BigDecimal.ZERO.subtract(item.getAmount()) : item.getAmount())
                    .reduce(BigDecimal.ZERO, BigDecimal::add);


            // 赋值结束时间余额
            capitalDailyBo.setRemainder(capitalDailyBo.getRemainder().subtract(reduce));

        }

    }

    /**
     * 收支流水明细
     */
    private void setRunningWaterDetail(List<CapitalDailyBo> capitalDailyBoList, Date endDate,
                                       List<AccountRunningWater> runningWaterList) {


        // 账户id 币种 天数 收支类型 收支金额
        Map<Long, Map<String, Map<Integer, Map<String, BigDecimal>>>> collect = runningWaterList.stream()
                .filter(item -> !item.getCreateTime().after(endDate))
                .collect(
                        Collectors.groupingBy(AccountRunningWater::getAccountManagementId,
                                Collectors.groupingBy(AccountRunningWater::getCurrency,
                                        Collectors.groupingBy(item -> DateUtil.dayOfMonth(item.getCreateTime()),
                                                Collectors.toMap(
                                                        AccountRunningWater::getStatus,
                                                        AccountRunningWater::getAmount,
                                                        BigDecimal::add
                                                )
                                        )
                                )
                        )
                );


        int day = DateUtil.dayOfMonth(endDate);

        for (CapitalDailyBo capitalDailyBo : capitalDailyBoList) {
            Map<String, Map<Integer, Map<String, BigDecimal>>> item1 = collect.get(capitalDailyBo.getAccountManagementId());
            Map<Integer, Map<String, BigDecimal>> item2 = null;
            if (item1 != null) {
                item2 = item1.get(capitalDailyBo.getCurrencyType());
            }

            List<Map<String, Object>> flowingWaterList = new ArrayList<>();

            for (int i = day; i > 0; i--) {
                Map<String, BigDecimal> item3 = null;
                if (item2 != null) {
                    item3 = item2.get(i);
                }

                Map<String, Object> flowingWaterMap = new LinkedHashMap<>();
                if (item3 != null) {
                    flowingWaterMap.put("income", ObjectUtil.defaultIfNull(item3.get("10"), BigDecimal.ZERO));
                    flowingWaterMap.put("expenditure", ObjectUtil.defaultIfNull(item3.get("20"), BigDecimal.ZERO));
                } else {
                    flowingWaterMap.put("income", BigDecimal.ZERO);
                    flowingWaterMap.put("expenditure", BigDecimal.ZERO);
                }
                BigDecimal remainder = capitalDailyBo.getRemainder();
                flowingWaterMap.put("balance", remainder);
                flowingWaterMap.put("day", i);

                capitalDailyBo.setRemainder(
                        remainder.subtract((BigDecimal) flowingWaterMap.get("income"))
                                .add((BigDecimal) flowingWaterMap.get("expenditure")));

                flowingWaterList.add(flowingWaterMap);
            }


            capitalDailyBo.setFlowingWaterList(flowingWaterList);
        }

    }


    private List<CapitalDailyVo> formatVo(List<CapitalDailyBo> capitalDailyBoList) {

        Map<Long, CapitalDailyVo> collect = capitalDailyBoList.stream().map(item -> {

            List<CapitalDailyVo.Management> managementList = new ArrayList<>();
            List<CapitalDailyVo.Currency> currencyList = new ArrayList<>();

            CapitalDailyVo capitalDailyVo = new CapitalDailyVo();
            capitalDailyVo.setCorporationId(item.getCorporationId());
            capitalDailyVo.setCorporationName(item.getCorporationName());
            capitalDailyVo.setManagementList(managementList);

            CapitalDailyVo.Management management = new CapitalDailyVo.Management();
            management.setAccountManagementId(item.getAccountManagementId());
            management.setAccountManagementAlias(item.getAccountManagementAlias());
            management.setCurrencyList(currencyList);
            managementList.add(management);

            CapitalDailyVo.Currency currency = new CapitalDailyVo.Currency();
            currency.setCurrencyType(item.getCurrencyType());
            currency.setFlowingWaterList(item.getFlowingWaterList());
            currencyList.add(currency);

            return capitalDailyVo;
        }).collect(Collectors.toMap(
                CapitalDailyVo::getCorporationId,
                Function.identity(),
                (v1, v2) -> {
                    List<CapitalDailyVo.Management> managementList = v1.getManagementList();
                    CapitalDailyVo.Management management = v2.getManagementList().get(0);

                    boolean flag = false;

                    for (CapitalDailyVo.Management item : managementList) {
                        if (item.getAccountManagementId().equals(management.getAccountManagementId())) {
                            flag = true;
                            List<CapitalDailyVo.Currency> currencyList = item.getCurrencyList();
                            currencyList.add(management.getCurrencyList().get(0));
                            break;
                        }
                    }

                    if (!flag) {
                        managementList.add(management);
                    }
                    return v1;
                }

        ));

        return new ArrayList<>(collect.values());
    }


}
