package com.red.circle.order.inner.service.impl;

import com.alibaba.nacos.shaded.com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.red.circle.common.business.core.amount.PayAmountArithmeticUtils;
import com.red.circle.framework.core.asserts.ResponseAssert;
import com.red.circle.framework.core.response.CommonErrorCode;
import com.red.circle.framework.core.response.ResponseErrorCode;
import com.red.circle.framework.dto.PageResult;
import com.red.circle.order.infra.database.rds.entity.pay.PayChannel;
import com.red.circle.order.infra.database.rds.entity.pay.PayChannelFactory;
import com.red.circle.order.infra.database.rds.entity.pay.PayCountry;
import com.red.circle.order.infra.database.rds.entity.pay.PayCountryChannel;
import com.red.circle.order.infra.database.rds.entity.pay.PayCountryChannelDetails;
import com.red.circle.order.infra.database.rds.entity.pay.PayFactory;
import com.red.circle.order.infra.database.rds.service.pay.PayChannelFactoryService;
import com.red.circle.order.infra.database.rds.service.pay.PayChannelService;
import com.red.circle.order.infra.database.rds.service.pay.PayCountryChannelDetailsService;
import com.red.circle.order.infra.database.rds.service.pay.PayCountryChannelService;
import com.red.circle.order.infra.database.rds.service.pay.PayCountryService;
import com.red.circle.order.infra.database.rds.service.pay.PayFactoryService;
import com.red.circle.order.inner.convertor.WebPayInnerConvertor;
import com.red.circle.order.inner.model.cmd.DelAssociatedChannelsCmd;
import com.red.circle.order.inner.model.cmd.SysPayChannelCmd;
import com.red.circle.order.inner.model.cmd.SysPayChannelFactoryCmd;
import com.red.circle.order.inner.model.cmd.SysPayChannelQryCmd;
import com.red.circle.order.inner.model.dto.FactoryAssociatedCountryChannelDTO;
import com.red.circle.order.inner.model.dto.PayCommodityPreviewDTO;
import com.red.circle.order.inner.model.dto.PayCountryAssociatedChannelsDTO;
import com.red.circle.order.inner.model.dto.SysPayChannelDTO;
import com.red.circle.order.inner.model.dto.SysPayChannelFactoryDTO;
import com.red.circle.order.inner.model.dto.SysPayCountryChannelDTO;
import com.red.circle.order.inner.model.dto.SysPayCountryChannelDetailsDTO;
import com.red.circle.order.inner.model.dto.SysPayFactoryAssociatedChannelsDTO;
import com.red.circle.order.inner.model.dto.SysPayFactoryDTO;
import com.red.circle.order.inner.service.PayChannelClientService;
import com.red.circle.tool.core.collection.CollectionUtils;
import com.red.circle.tool.core.date.TimestampUtils;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

/**
 * 支付渠道.
 *
 * @author pengliang on 2023/11/3
 */
@Service
@RequiredArgsConstructor
public class PayChannelClientServiceImpl implements PayChannelClientService {

  private final PayFactoryService payFactoryService;
  private final PayChannelService payChannelService;
  private final PayCountryService payCountryService;
  private final WebPayInnerConvertor webPayInnerConvertor;
  private final PayCountryChannelService payCountryChannelService;
  private final PayChannelFactoryService payChannelFactoryService;
  private final PayCountryChannelDetailsService payCountryChannelDetailsService;


  @Override
  public PageResult<SysPayChannelDTO> pageChannel(SysPayChannelQryCmd query) {
    return payChannelService.pageChannel(webPayInnerConvertor.toSysPayChannelQry(query))
        .convert(webPayInnerConvertor::toSysPayChannelDTO);
  }

  @Override
  public List<SysPayChannelDTO> listChannel() {
    return webPayInnerConvertor.toListSysPayChannelDTO(payChannelService.listChannel());
  }

  @Override
  public void addChannel(SysPayChannelCmd cmd) {
    payChannelService.addChannel(webPayInnerConvertor.toPayChannel(cmd));
  }

  @Override
  public void updateChannel(SysPayChannelCmd cmd) {
    payChannelService.updateChannel(webPayInnerConvertor.toPayChannel(cmd));
  }


  @Override
  public void addAssociatedChannels(List<SysPayChannelFactoryCmd> params) {
    List<PayChannelFactory> sysPayChannelFactories = toListSysPayChannelFactory(params);
    ResponseAssert.isTrue(ResponseErrorCode.REQUEST_PARAMETER_ERROR,
        CollectionUtils.isNotEmpty(sysPayChannelFactories));
    payChannelFactoryService.saveBatch(sysPayChannelFactories);
  }

  private List<PayChannelFactory> toListSysPayChannelFactory(
      List<SysPayChannelFactoryCmd> params) {
    return params.stream()
        .filter(Objects::nonNull)
        .map(param -> {
          PayChannelFactory sysPayChannelFactory = webPayInnerConvertor.toPayChannelFactory(param);
          sysPayChannelFactory.setCreateTime(TimestampUtils.now());
          sysPayChannelFactory.setUpdateTime(TimestampUtils.now());
          return sysPayChannelFactory;
        })
        .collect(Collectors.toList());
  }

  @Override
  public void updateAssociatedChannels(SysPayChannelFactoryCmd cmd) {
    payChannelFactoryService.updateSelectiveById(webPayInnerConvertor.toPayChannelFactory(cmd));
  }

  @Override
  public void delAssociatedChannels(DelAssociatedChannelsCmd cmd) {
    payChannelFactoryService.delete()
        .in(PayChannelFactory::getChannelCode, cmd.getChannelCodes())
        .eq(PayChannelFactory::getFactoryCode, cmd.getFactoryCode())
        .execute();
  }

  private Map<String, List<SysPayFactoryDTO>> mapByChannelCode(Set<String> channelCodes) {
    if (CollectionUtils.isEmpty(channelCodes)) {
      return Maps.newHashMap();
    }
    return Optional.ofNullable(listByChannelCodes(channelCodes))
        .map(sysPayChannelFactories -> {
          Map<String, PayFactory> stringSysPayFactoryMap = payFactoryService.mapByCodes(
              sysPayChannelFactories.stream().map(PayChannelFactory::getFactoryCode).collect(
                  Collectors.toSet()));

          Map<String, List<SysPayFactoryDTO>> resultMap = Maps.newHashMap();
          sysPayChannelFactories.forEach(item -> {
            resultMap.putIfAbsent(item.getChannelCode(), Lists.newArrayList());
            SysPayFactoryDTO sysPayFactory = webPayInnerConvertor.toSysPayFactoryDTO(
                stringSysPayFactoryMap.get(item.getFactoryCode()));
            if (Objects.nonNull(sysPayFactory)) {
              resultMap.get(item.getChannelCode()).add(sysPayFactory);
            }
          });
          return resultMap;
        })
        .orElseGet(Maps::newHashMap);
  }

  private List<PayChannelFactory> listByChannelCodes(Set<String> channelCodes) {
    return payChannelFactoryService.query()
        .in(PayChannelFactory::getChannelCode, channelCodes)
        .orderByDesc(PayChannelFactory::getCreateTime)
        .list();
  }

  @Override
  public List<SysPayChannelFactoryDTO> listChannelFactory() {

    List<PayChannel> channels = payChannelService.list();

    if (CollectionUtils.isEmpty(channels)) {
      return Lists.newArrayList();
    }

    List<PayChannelFactory> channelFactors = listByChannelCodes(
        channels.stream().map(PayChannel::getChannelCode).collect(Collectors.toSet()));

    if (CollectionUtils.isEmpty(channelFactors)) {
      return Lists.newArrayList();
    }

    Map<String, SysPayFactoryDTO> factoryMap = webPayInnerConvertor.toMapSysPayFactoryDTO(
        payFactoryService
            .mapByCodes(channelFactors.stream().map(PayChannelFactory::getFactoryCode).collect(
                Collectors.toSet()))
    );

    Map<String, List<PayChannelFactory>> channelFactorGroup = channelFactors.stream()
        .collect(Collectors.groupingBy(PayChannelFactory::getChannelCode));

    return channels.stream().map(channel -> new SysPayChannelFactoryDTO()
        .setPayChannel(webPayInnerConvertor.toSysPayChannelDTO(channel))
        .setPayFactorys(
            Optional.ofNullable(channelFactorGroup.get(channel.getChannelCode()))
                .map(channelFactorItems -> channelFactorItems.stream()
                    .map(channelFactor -> factoryMap.get(channelFactor.getFactoryCode()))
                    .filter(Objects::nonNull)
                    .collect(Collectors.toList())
                ).orElseGet(Lists::newArrayList)
        )
    ).collect(Collectors.toList());
  }

  @Override
  public List<SysPayFactoryAssociatedChannelsDTO> listChannelByFactoryCode(String factoryCode) {
    List<PayChannelFactory> channels = payChannelFactoryService.query()
        .eq(PayChannelFactory::getFactoryCode, factoryCode)
        .orderByDesc(PayChannelFactory::getCreateTime)
        .list();
    if (CollectionUtils.isEmpty(channels)) {
      return Lists.newArrayList();
    }
    Map<String, SysPayChannelDTO> stringSysPayChannelMap = webPayInnerConvertor.toMapSysPayChannelDTO(
        payChannelService.mapByCodes(
            channels.stream().map(PayChannelFactory::getChannelCode).collect(Collectors.toSet()))
    );

    return channels.stream()
        .map(sysPayChannelFactory -> {
          SysPayFactoryAssociatedChannelsDTO vo = webPayInnerConvertor.toSysPayFactoryAssociatedChannelsDTO(
              sysPayChannelFactory);
          vo.setPayChannel(stringSysPayChannelMap.get(vo.getChannelCode()));
          return vo;
        })
        .collect(Collectors.toList());
  }

  @Override
  public Set<String> listFactoryCodeByChannelCode(Set<String> channelCodes) {
    if (CollectionUtils.isEmpty(channelCodes)) {
      return Sets.newHashSet();
    }
    return Optional.ofNullable(payChannelFactoryService.query()
            .select(PayChannelFactory::getFactoryCode)
            .in(PayChannelFactory::getChannelCode, channelCodes)
            .list())
        .map(sysPayChannelFactories -> sysPayChannelFactories.stream()
            .map(PayChannelFactory::getFactoryCode).collect(
                Collectors.toSet()))
        .orElseGet(Sets::newHashSet);
  }

  @Override
  public List<PayCountryAssociatedChannelsDTO> listByPayCountry(Long payCountryId) {
    List<PayCountryChannel> countryChannels = payCountryChannelService
        .listByPayCountryId(payCountryId);

    if (CollectionUtils.isEmpty(countryChannels)) {
      return Lists.newArrayList();
    }

    // 渠道-国家-关联详情
    Map<Long, List<PayCountryChannelDetails>> detailsMap = payCountryChannelDetailsService
        .mapByCountryChannelId(
            countryChannels.stream().map(PayCountryChannel::getId).collect(Collectors.toSet())
        );

    // 渠道详细资料
    Map<String, PayChannel> channelMap = payChannelService
        .mapByCodes(getChannelCodes(countryChannels));

    if (CollectionUtils.isEmpty(channelMap)) {
      return Lists.newArrayList();
    }

    // 渠道关联厂商
    Map<String, List<SysPayFactoryDTO>> channelFactoryMap = mapByChannelCode(
        getChannelCodes(countryChannels));

    return countryChannels.stream()
        .map(sysPayChannel -> {
          SysPayCountryChannelDTO associated = webPayInnerConvertor.toSysPayCountryChannelDTO(
              sysPayChannel);

          List<SysPayCountryChannelDetailsDTO> associatedDetails = webPayInnerConvertor.toListSysPayCountryChannelDetailsDTO(
              detailsMap.get(associated.getId())
          );

          List<SysPayFactoryDTO> sysPayFactorys = channelFactoryMap
              .get(sysPayChannel.getChannelCode());

          return new PayCountryAssociatedChannelsDTO()
              .setAssociated(associated)
              .setChannel(webPayInnerConvertor.toSysPayChannelDTO(
                  channelMap.get(sysPayChannel.getChannelCode())))
              .setCountryChannels(
                  convertCountryChannels(sysPayChannel, associatedDetails, sysPayFactorys));
        }).collect(Collectors.toList());
  }

  @Override
  public PayCommodityPreviewDTO listSupportAmountUsd(Long payCountryId,
      BigDecimal amountUsd) {

    PayCountry sysPayCountry = payCountryService.getById(payCountryId);
    ResponseAssert.notNull(CommonErrorCode.NOT_FOUND_RECORD_INFO, sysPayCountry);
    List<PayCountryChannelDetails> supports = payCountryChannelDetailsService
        .listSupportAmountUsd(payCountryId, amountUsd);

    if (CollectionUtils.isEmpty(supports)) {
      return null;
    }

    List<SysPayChannelDTO> channels = webPayInnerConvertor.toListSysPayChannelDTO(
        payChannelService.listByCodes(
            supports.stream().map(PayCountryChannelDetails::getChannelCode)
                .collect(Collectors.toSet()))
    );

    Map<String, SysPayFactoryDTO> factoryMap = webPayInnerConvertor.toMapSysPayFactoryDTO(
        payFactoryService.mapByCodes(
            supports.stream().map(PayCountryChannelDetails::getFactoryCode)
                .collect(Collectors.toSet()))
    );

    Map<String, List<FactoryAssociatedCountryChannelDTO>> associatedMap = supports.stream()
        .map(support -> new FactoryAssociatedCountryChannelDTO()
            .setPayFactory(factoryMap.get(support.getFactoryCode()))
            .setCountryChannelDetails(webPayInnerConvertor.toSysPayCountryChannelDetailsDTO(support)
                .computedLimit(sysPayCountry.getUsdExchangeRate()))
        )
        .filter(associated -> Objects.nonNull(associated.getPayFactory()))
        .collect(Collectors
            .groupingBy(associated -> associated.getCountryChannelDetails().getChannelCode()));

    return new PayCommodityPreviewDTO()
        .setChannels(channels.stream()
            .map(channel -> new PayCountryAssociatedChannelsDTO()
                .setChannel(channel)
                .setCountryChannels(associatedMap.get(channel.getChannelCode()))
            ).filter(channel -> Objects.nonNull(channel.getCountryChannels()))
            .collect(Collectors.toList()))
        .setAmountUsd(amountUsd)
        .setComputedAmount(PayAmountArithmeticUtils.multiplyUpTwoPoint(amountUsd,
            sysPayCountry.getUsdExchangeRate()));
  }

  private List<FactoryAssociatedCountryChannelDTO> convertCountryChannels(
      PayCountryChannel sysPayCountryChannel,
      List<SysPayCountryChannelDetailsDTO> associatedDetails,
      List<SysPayFactoryDTO> sysPayFactorys) {
    if (CollectionUtils.isEmpty(sysPayFactorys)) {
      return Lists.newArrayList();
    }

    Map<String, SysPayCountryChannelDetailsDTO> channelDetails = Optional
        .ofNullable(associatedDetails)
        .map(details -> details.stream()
            .collect(Collectors.toMap(SysPayCountryChannelDetailsDTO::getFactoryCode, v -> v, (v1, v2) -> v1))
        ).orElseGet(Maps::newHashMap);

    return sysPayFactorys.stream().map(sysPayFactory -> {
          SysPayCountryChannelDetailsDTO details = channelDetails.get(sysPayFactory.getFactoryCode());
          if (Objects.isNull(details)) {
            details = new SysPayCountryChannelDetailsDTO()
                .setPayCountryId(sysPayCountryChannel.getPayCountryId())
                .setCountryChannelId(sysPayCountryChannel.getId())
                .setChannelCode(sysPayCountryChannel.getChannelCode())
                .setFactoryCode(sysPayFactory.getFactoryCode())
                .setShelf(Boolean.FALSE);
          }
          return new FactoryAssociatedCountryChannelDTO()
              .setPayFactory(sysPayFactory)
              .setCountryChannelDetails(details);
        })
        .collect(Collectors.toList());
  }

  private Set<String> getChannelCodes(List<PayCountryChannel> sysPayCountryChannels) {
    return sysPayCountryChannels.stream().map(PayCountryChannel::getChannelCode).collect(
        Collectors.toSet());
  }

}
