package com.bonus.core.marketing.v2.util;

import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.NumberUtil;
import com.bonus.core.bus.api.BusLineApi;
import com.bonus.core.common.utils.SpringContextHolder;
import com.bonus.core.common.utils.TenantContextHolder;
import com.bonus.core.device.api.DeviceApi;
import com.bonus.core.marketing.util.EnumUtil;
import com.bonus.core.marketing.v2.constants.MarketDeviceTreeEnum;
import com.bonus.core.marketing.v2.constants.MarketModuleTypeEnum;
import com.bonus.core.marketing.v2.rule.param.RuleRangeParam;
import com.bonus.core.marketing.v2.rule.price.constants.RulePriceLimitEnum;
import com.bonus.core.marketing.v2.rule.price.dto.RulePriceResultOrderDTO;
import com.bonus.core.marketing.v2.service.MarketRuleService;
import com.bonus.core.marketing.v2.vo.MarketRuleDetailVO;
import com.bonus.core.marketing.vo.MarketRuleVO;
import com.bonus.core.order.common.constants.OrderRefundStateEnum;
import com.bonus.core.order.common.constants.OrderTypeEnum;
import com.bonus.core.order.common.model.OrderInfo;
import com.bonus.core.pay.common.constants.PayStateEnum;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;

import java.time.LocalDate;
import java.time.temporal.TemporalAdjusters;
import java.util.*;
import java.util.stream.Collectors;

public final class MarketUtil {
   private static final Logger log = LoggerFactory.getLogger(MarketUtil.class);
   private static final MarketRuleService ruleService = (MarketRuleService) SpringContextHolder.getBean(MarketRuleService.class);
   private static final BusLineApi busLineApi = (BusLineApi)SpringContextHolder.getBean(BusLineApi.class);
   private static final DeviceApi deviceApi = (DeviceApi)SpringContextHolder.getBean(DeviceApi.class);

   public static String formatDeviceSn(Integer orderType, Long canteenId, Long stallId, String deviceSn) {
      OrderTypeEnum orderTypeEnum = OrderTypeEnum.getEnumByKey(orderType);
      if (Objects.isNull(orderTypeEnum)) {
         return deviceSn;
      } else {
         String finalDeviceSn;
         switch (orderTypeEnum) {
            case CURR_MEAL:
            case RESERVE_MEAL:
            case BOOK_MEAL:
            case BOOK_MEAL_3DAY:
            case BOOK_MEAL_7DAY:
            case MOBILE_SCAN:
               finalDeviceSn = formatMobile(canteenId, stallId);
               break;
            case SHOP:
            case RESERVE_SHOP:
            case MOBILE_SCAN_SHOP:
               finalDeviceSn = formatMarket(canteenId, deviceSn);
               break;
            case DEDUCT:
               finalDeviceSn = CharSequenceUtil.isBlank(deviceSn) ? formatMobile(canteenId, stallId) : formatDevice(canteenId, stallId, deviceSn);
               break;
            case BUFFET:
               finalDeviceSn = formatBuffet(canteenId, stallId);
               break;
            default:
               finalDeviceSn = formatDevice(canteenId, stallId, deviceSn);
         }

         return finalDeviceSn;
      }
   }

   public static String formatMarket(Long canteenId, String deviceSn) {
      return CharSequenceUtil.isBlank(deviceSn) ? CharSequenceUtil.concat(true, new CharSequence[]{NumberUtil.toStr(canteenId, ""), "&&", MarketDeviceTreeEnum.ONLINE_SUPERMARKET.getDesc()}) : CharSequenceUtil.concat(true, new CharSequence[]{NumberUtil.toStr(canteenId, ""), "&&", MarketDeviceTreeEnum.OFFLINE_SUPERMARKET.getDesc()});
   }

   public static String formatDevice(Long canteenId, Long stallId, String deviceSn) {
      return CharSequenceUtil.concat(true, new CharSequence[]{NumberUtil.toStr(canteenId, ""), "&", CharSequenceUtil.equals(NumberUtil.toStr(stallId, ""), "-1") ? "" : NumberUtil.toStr(stallId, ""), "&", deviceSn});
   }

   public static String formatMobile(Long canteenId, Long stallId) {
      return CharSequenceUtil.concat(true, new CharSequence[]{MarketDeviceTreeEnum.MOBILE.getDesc(), "&", NumberUtil.toStr(canteenId, ""), "&", NumberUtil.toStr(stallId, "")});
   }

   public static String formatBuffet(Long canteenId, Long stallId) {
      return Objects.isNull(canteenId) && Objects.isNull(stallId) ? MarketDeviceTreeEnum.BUFFET_CODE.getDesc() : CharSequenceUtil.concat(true, new CharSequence[]{MarketDeviceTreeEnum.BUFFET.getDesc(), "&", NumberUtil.toStr(canteenId, ""), "&", NumberUtil.toStr(stallId, "")});
   }

   public static String formatPayDeviceSn(Integer orderType, Long canteenId, String deviceSn) {
      OrderTypeEnum orderTypeEnum = OrderTypeEnum.getEnumByKey(orderType);
      if (Objects.isNull(orderTypeEnum)) {
         return deviceSn;
      } else {
         String finalDeviceSn;
         switch (orderTypeEnum) {
            case CURR_MEAL:
            case RESERVE_MEAL:
            case BOOK_MEAL:
            case BOOK_MEAL_3DAY:
            case BOOK_MEAL_7DAY:
               finalDeviceSn = CharSequenceUtil.concat(true, new CharSequence[]{MarketDeviceTreeEnum.MOBILE.getDesc(), "&", NumberUtil.toStr(canteenId, ""), "&"});
               break;
            case MOBILE_SCAN:
            case MOBILE_SCAN_SHOP:
            default:
               finalDeviceSn = deviceSn;
               break;
            case SHOP:
            case RESERVE_SHOP:
               finalDeviceSn = canteenId.toString();
               break;
            case DEDUCT:
               finalDeviceSn = CharSequenceUtil.isBlank(deviceSn) ? CharSequenceUtil.concat(true, new CharSequence[]{MarketDeviceTreeEnum.MOBILE.getDesc(), "&", NumberUtil.toStr(canteenId, ""), "&"}) : deviceSn;
         }

         return finalDeviceSn;
      }
   }

   public static List<String> formatEffectTimeValueToList(String effectTimeValueStr) {
      return CharSequenceUtil.isBlank(effectTimeValueStr) ? CollUtil.newArrayList(new String[0]) : new ArrayList(CharSequenceUtil.splitTrim(effectTimeValueStr, ","));
   }

   public static String formatEffectTimeValueToStr(List<String> effectTimeValuelList) {
      return CollUtil.isEmpty(effectTimeValuelList) ? null : CharSequenceUtil.concat(true, new CharSequence[]{",", CollUtil.join(effectTimeValuelList, ","), ","});
   }

   public static List<Integer> formatEffectPeriodValueToList(String effectPeriodValueStr) {
      return (List)(CharSequenceUtil.isBlank(effectPeriodValueStr) ? CollUtil.newArrayList(new Integer[0]) : (List)CharSequenceUtil.splitTrim(effectPeriodValueStr, ",").stream().map(Integer::valueOf).collect(Collectors.toList()));
   }

   public static String formatEffectPeriodValueToStr(List<Integer> effectPeriodValuelList) {
      return CollUtil.isEmpty(effectPeriodValuelList) ? null : CharSequenceUtil.concat(true, new CharSequence[]{",", CollUtil.join(effectPeriodValuelList, ","), ","});
   }

   public static CopyOptions getCopyOptions() {
      return CopyOptions.create().setFieldValueEditor((fieldName, fieldValue) -> {
         if (Objects.equals("id", fieldName) || Objects.equals("parentId", fieldName)) {
            fieldValue = fieldValue.toString();
         }

         return fieldValue;
      });
   }

   private MarketUtil() {
      throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
   }

   public static List<MarketRuleVO> listCacheSuitableRulesByRangeParam(RuleRangeParam param) {
      RedisTemplate<Object, Object> redisTemplate = (RedisTemplate)SpringContextHolder.getBean("redisTemplate");
      MarketModuleTypeEnum module = (MarketModuleTypeEnum) EnumUtil.getEnumByKey(MarketModuleTypeEnum.class, param.getModuleType());
      Long tenantId = TenantContextHolder.getTenantId();
      String key = String.format("yunshitang:%s:market:%s", tenantId, module.name());
      if (Boolean.FALSE.equals(redisTemplate.hasKey(key))) {
         return null;
      } else {
         List<Object> values = redisTemplate.boundHashOps(key).values();
         if (Objects.isNull(values)) {
            return null;
         } else {
            List<MarketRuleDetailVO> marketRuleDetails = (List)values.stream().map((e) -> {
               return (MarketRuleDetailVO)e;
            }).collect(Collectors.toList());
            if (Objects.nonNull(param.getSceneType())) {
               marketRuleDetails = (List)marketRuleDetails.stream().filter((e) -> {
                  return Objects.equals(e.getSceneType(), param.getSceneType());
               }).collect(Collectors.toList());
            }

            Comparator<MarketRuleVO> uptimeComparing = Comparator.comparing(MarketRuleVO::getUptime).reversed();
            return (List)marketRuleDetails.stream().filter((e) -> {
               return module.exec(param, e);
            }).sorted(Comparator.comparing(MarketRuleVO::getSort).thenComparing(uptimeComparing)).collect(Collectors.toList());
         }
      }
   }

   public static List<MarketRuleDetailVO> saveOrUpdateRuleCache(Integer moduleType) {
      RedisTemplate<Object, Object> redisTemplate = (RedisTemplate)SpringContextHolder.getBean("redisTemplate");
      MarketModuleTypeEnum module = (MarketModuleTypeEnum)EnumUtil.getEnumByKey(MarketModuleTypeEnum.class, moduleType);
      if (Objects.isNull(module)) {
         return null;
      } else {
         Long tenantId = TenantContextHolder.getTenantId();
         String key = String.format("yunshitang:%s:market:%s", tenantId, module.name());
         redisTemplate.delete(key);
         List<MarketRuleDetailVO> marketRuleDetails = ruleService.listRulesByModuleType(moduleType);
         log.info("规则重建缓存条数：{}", CollUtil.size(marketRuleDetails));
         Map<String, MarketRuleDetailVO> map = (Map)marketRuleDetails.stream().collect(Collectors.toMap((a) -> {
            return a.getRuleId().toString();
         }, (a) -> {
            return a;
         }));
         redisTemplate.boundHashOps(key).putAll(map);
         return marketRuleDetails;
      }
   }

   public static List<RulePriceResultOrderDTO> getNowTrade(RulePriceResultOrderDTO order, List<Integer> mealtimeTypeList, List<String> customDeviceList, Integer limitType, List<RulePriceResultOrderDTO> orderResults) {
      List<RulePriceResultOrderDTO> result = new ArrayList();
      RulePriceLimitEnum limitEnum = (RulePriceLimitEnum)EnumUtil.getEnumByKey(RulePriceLimitEnum.class, limitType);
      if (Objects.isNull(limitEnum)) {
         return result;
      } else {
         switch (limitEnum) {
            case LIMIT_DAY:
               result = orderResults.stream().filter((e) -> {
                  return e.getOrderDate().isEqual(order.getOrderDate()) && (CollUtil.isEmpty(mealtimeTypeList) || CollUtil.contains(mealtimeTypeList, e.getMealtimeType())) && (CollUtil.isEmpty(customDeviceList) || CollUtil.contains(customDeviceList, formatDeviceSn(e.getOrderType(), e.getCanteenId(), e.getStallId(), e.getDeviceSn())));
               }).collect(Collectors.toList());
               break;
            case LIMIT_MONTH:
               LocalDate beginDate = order.getOrderDate().with(TemporalAdjusters.firstDayOfMonth());
               LocalDate endDate = order.getOrderDate().with(TemporalAdjusters.lastDayOfMonth());
               result = orderResults.stream().filter((e) -> {
                  return e.getOrderDate().compareTo(beginDate) >= 0 && e.getOrderDate().compareTo(endDate) <= 0 && (CollUtil.isEmpty(mealtimeTypeList) || CollUtil.contains(mealtimeTypeList, e.getMealtimeType())) && (CollUtil.isEmpty(customDeviceList) || CollUtil.contains(customDeviceList, formatDeviceSn(e.getOrderType(), e.getCanteenId(), e.getStallId(), e.getDeviceSn())));
               }).collect(Collectors.toList());
               break;
            default:
               result = orderResults.stream().filter((e) -> {
                  return e.getOrderDate().isEqual(order.getOrderDate()) && Objects.equals(e.getMealtimeType(), order.getMealtimeType()) && (CollUtil.isEmpty(customDeviceList) || CollUtil.contains(customDeviceList, formatDeviceSn(e.getOrderType(), e.getCanteenId(), e.getStallId(), e.getDeviceSn())));
               }).collect(Collectors.toList());
         }

         return result;
      }
   }

   public static boolean isValidOrder(List<String> customDeviceList, OrderInfo e) {
      return (Objects.equals(e.getPayState(), PayStateEnum.PAY_SUCC.getKey()) || Objects.equals(e.getPayState(), PayStateEnum.PART_PAY.getKey())) && !OrderRefundStateEnum.FINISH.getKey().equals(e.getOrderRefundState()) && (CollUtil.isEmpty(customDeviceList) || CollUtil.contains(customDeviceList, formatDeviceSn(e.getOrderType(), e.getCanteenId(), e.getStallId(), e.getMachineSn())));
   }

}
