package com.red.circle.console.app.service.app.activity;

import com.red.circle.console.app.dto.clienobject.app.activity.cp.ResidentCpConfigCO;
import com.red.circle.console.app.dto.clienobject.app.activity.cp.ResidentCpLevelConfigCO;
import com.red.circle.console.app.dto.cmd.app.activity.cp.ResidentCpConfigSaveCmd;
import com.red.circle.console.app.dto.cmd.app.activity.cp.ResidentCpLevelConfigSaveCmd;
import com.red.circle.console.app.service.app.sys.CpCabinConfigService;
import com.red.circle.console.app.service.app.sys.SysEnumConfigService;
import com.red.circle.framework.core.asserts.ResponseAssert;
import com.red.circle.framework.core.response.ResponseErrorCode;
import com.red.circle.framework.dto.PageQuery;
import com.red.circle.framework.dto.PageResult;
import com.red.circle.other.inner.enums.config.EnumConfigKey;
import com.red.circle.other.inner.model.cmd.sys.SysCpCabinConfigQryCmd;
import com.red.circle.other.inner.model.dto.sys.EnumConfigDTO;
import com.red.circle.other.inner.model.dto.sys.SysCpCabinConfigDTO;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;

/**
 * 常驻 CP 配置服务实现.
 */
@Service
@RequiredArgsConstructor
public class ResidentCpActivityServiceImpl implements ResidentCpActivityService {

  private static final int CP_LEVEL_COUNT = 6;
  private static final int CP_MIN_LEVEL = 0;
  private static final int QUERY_LIMIT = 50;
  private static final String CONFIG_GROUP_NAME = "CP";
  private static final String CONFIG_DATA_TYPE = "int";
  private static final String RELATION_TYPE_CP = "CP";
  private static final String RELATION_TYPE_BROTHER = "BROTHER";
  private static final String RELATION_TYPE_SISTERS = "SISTERS";

  private final CpCabinConfigService cpCabinConfigService;
  private final SysEnumConfigService sysEnumConfigService;

  @Override
  public ResidentCpConfigCO getConfig(String sysOrigin) {
    String normalizedSysOrigin = normalizeSysOrigin(sysOrigin);
    List<SysCpCabinConfigDTO> configs = listCpCabinConfigs(normalizedSysOrigin);
    List<ResidentCpLevelConfigCO> levels = new ArrayList<>(CP_LEVEL_COUNT);
    for (int level = CP_MIN_LEVEL; level < CP_LEVEL_COUNT; level++) {
      SysCpCabinConfigDTO config = level < configs.size() ? configs.get(level) : null;
      levels.add(toLevelCO(level, config));
    }
    return new ResidentCpConfigCO()
        .setConfigured(!configs.isEmpty())
        .setSysOrigin(normalizedSysOrigin)
        .setDismissConsumeGolds(getDismissConsumeGolds(normalizedSysOrigin))
        .setLevels(levels);
  }

  @Override
  public void saveConfig(ResidentCpConfigSaveCmd cmd) {
    validateConfig(cmd);
    String normalizedSysOrigin = normalizeSysOrigin(cmd.getSysOrigin());
    List<SysCpCabinConfigDTO> configs = listCpCabinConfigs(normalizedSysOrigin);
    Map<Long, SysCpCabinConfigDTO> configById = configs.stream()
        .filter(item -> Objects.nonNull(item.getId()))
        .collect(Collectors.toMap(SysCpCabinConfigDTO::getId, Function.identity(), (left, right) -> left));

    for (ResidentCpLevelConfigSaveCmd levelCmd : sortedLevels(cmd.getLevels())) {
      SysCpCabinConfigDTO config = Optional.ofNullable(levelCmd.getCabinConfigId())
          .map(configById::get)
          .orElseGet(() -> levelCmd.getLevel() < configs.size()
              ? configs.get(levelCmd.getLevel())
              : null);
      boolean exists = Objects.nonNull(config) && Objects.nonNull(config.getId());
      if (!exists) {
        config = new SysCpCabinConfigDTO();
      }
      config.setSysOrigin(normalizedSysOrigin)
          .setName(defaultLevelName(levelCmd.getLevel(), levelCmd.getLevelName()))
          .setCabinUnlockValue(defaultRequiredValue(levelCmd.getRequiredIntimacyValue()))
          .setSort(levelCmd.getLevel().longValue());

      if (exists) {
        cpCabinConfigService.updateCpCabin(config);
      } else {
        cpCabinConfigService.addCpCabin(config);
      }
    }
    saveDismissConsumeGolds(normalizedSysOrigin, cmd.getDismissConsumeGolds());
  }

  private List<SysCpCabinConfigDTO> listCpCabinConfigs(String sysOrigin) {
    SysCpCabinConfigQryCmd query = new SysCpCabinConfigQryCmd().setSysOrigin(sysOrigin);
    PageQuery pageQuery = new PageQuery();
    pageQuery.setCursor(1);
    pageQuery.setLimit(QUERY_LIMIT);
    pageQuery.setSearchCount(false);
    query.setPageQuery(pageQuery);
    PageResult<SysCpCabinConfigDTO> result = cpCabinConfigService.getCpCabin(query);
    return Optional.ofNullable(result.getRecords())
        .stream()
        .flatMap(java.util.Collection::stream)
        .sorted(Comparator
            .comparing((SysCpCabinConfigDTO item) -> Optional.ofNullable(item.getSort())
                .orElse(Long.MAX_VALUE))
            .thenComparing(item -> Optional.ofNullable(item.getCabinUnlockValue())
                .orElse(Long.MAX_VALUE))
            .thenComparing(item -> Optional.ofNullable(item.getId()).orElse(Long.MAX_VALUE)))
        .toList();
  }

  private ResidentCpLevelConfigCO toLevelCO(int level, SysCpCabinConfigDTO config) {
    return new ResidentCpLevelConfigCO()
        .setLevel(level)
        .setLevelName(Objects.isNull(config)
            ? defaultLevelName(level, null)
            : defaultLevelName(level, config.getName()))
        .setRequiredIntimacyValue(Objects.isNull(config)
            ? 0L
            : defaultRequiredValue(config.getCabinUnlockValue()))
        .setCabinConfigId(Objects.isNull(config) ? null : config.getId());
  }

  private void validateConfig(ResidentCpConfigSaveCmd cmd) {
    ResponseAssert.notBlank(ResponseErrorCode.REQUEST_PARAMETER_ERROR, cmd.getSysOrigin());
    List<ResidentCpLevelConfigSaveCmd> levels = sortedLevels(cmd.getLevels());
    Set<Integer> levelSet = new HashSet<>();
    long previousRequiredValue = -1L;
    for (ResidentCpLevelConfigSaveCmd level : levels) {
      ResponseAssert.isTrue(ResponseErrorCode.REQUEST_PARAMETER_ERROR,
          "CP level must be between 0 and 5.",
          Objects.nonNull(level.getLevel()) && level.getLevel() >= CP_MIN_LEVEL
              && level.getLevel() < CP_LEVEL_COUNT && levelSet.add(level.getLevel()));
      long requiredValue = defaultRequiredValue(level.getRequiredIntimacyValue());
      ResponseAssert.isTrue(ResponseErrorCode.REQUEST_PARAMETER_ERROR,
          "requiredIntimacyValue must be greater than or equal to zero.", requiredValue >= 0);
      ResponseAssert.isTrue(ResponseErrorCode.REQUEST_PARAMETER_ERROR,
          "requiredIntimacyValue must be ascending by level.",
          requiredValue >= previousRequiredValue);
      previousRequiredValue = requiredValue;
    }
    validateDismissConsumeGolds(cmd.getDismissConsumeGolds());
  }

  private Map<String, Long> getDismissConsumeGolds(String sysOrigin) {
    Map<String, Long> result = defaultDismissConsumeGolds();
    dismissConsumeGoldKeys().forEach((relationType, key) -> result.put(relationType,
        enumConfigGoldValue(sysEnumConfigService.getByCode(key.name(), sysOrigin))));
    return result;
  }

  private void saveDismissConsumeGolds(String sysOrigin, Map<String, Long> dismissConsumeGolds) {
    if (Objects.isNull(dismissConsumeGolds)) {
      return;
    }
    dismissConsumeGoldKeys().forEach((relationType, key) -> {
      if (dismissConsumeGolds.containsKey(relationType)) {
        saveDismissConsumeGold(sysOrigin, key, dismissConsumeGolds.get(relationType));
      }
    });
  }

  private void saveDismissConsumeGold(String sysOrigin, EnumConfigKey key, Long value) {
    Long normalizedValue = defaultGold(value);
    EnumConfigDTO enumConfig = sysEnumConfigService.getByCode(key.name(), sysOrigin);
    boolean exists = Objects.nonNull(enumConfig) && Objects.nonNull(enumConfig.getId());
    if (!exists) {
      enumConfig = new EnumConfigDTO();
    }
    enumConfig.setSysOrigin(sysOrigin)
        .setName(key.name())
        .setTitle(dismissConsumeGoldTitle(key))
        .setVal(String.valueOf(normalizedValue))
        .setDescription(dismissConsumeGoldTitle(key))
        .setDataType(CONFIG_DATA_TYPE)
        .setReturnApp(Boolean.FALSE)
        .setGroupName(CONFIG_GROUP_NAME);
    if (exists) {
      sysEnumConfigService.updateSysEnumConfig(enumConfig);
    } else {
      sysEnumConfigService.saveSysEnumConfig(enumConfig);
    }
  }

  private void validateDismissConsumeGolds(Map<String, Long> dismissConsumeGolds) {
    if (Objects.isNull(dismissConsumeGolds)) {
      return;
    }
    dismissConsumeGolds.forEach((relationType, gold) -> {
      ResponseAssert.isTrue(ResponseErrorCode.REQUEST_PARAMETER_ERROR,
          "relationType is not supported.",
          dismissConsumeGoldKeys().containsKey(normalizeRelationType(relationType)));
      ResponseAssert.isTrue(ResponseErrorCode.REQUEST_PARAMETER_ERROR,
          "dismissConsumeGold must be greater than or equal to zero.",
          defaultGold(gold) >= 0);
    });
  }

  private Long enumConfigGoldValue(EnumConfigDTO enumConfig) {
    if (Objects.isNull(enumConfig) || StringUtils.isBlank(enumConfig.getVal())) {
      return 0L;
    }
    try {
      return new BigDecimal(enumConfig.getVal()).longValue();
    } catch (NumberFormatException ex) {
      return 0L;
    }
  }

  private Map<String, Long> defaultDismissConsumeGolds() {
    Map<String, Long> result = new LinkedHashMap<>();
    dismissConsumeGoldKeys().keySet().forEach(relationType -> result.put(relationType, 0L));
    return result;
  }

  private Map<String, EnumConfigKey> dismissConsumeGoldKeys() {
    Map<String, EnumConfigKey> result = new LinkedHashMap<>();
    result.put(RELATION_TYPE_CP, EnumConfigKey.CP_DISMISS_CONSUME_GOLD);
    result.put(RELATION_TYPE_BROTHER, EnumConfigKey.BROTHER_DISMISS_CONSUME_GOLD);
    result.put(RELATION_TYPE_SISTERS, EnumConfigKey.SISTERS_DISMISS_CONSUME_GOLD);
    return result;
  }

  private String dismissConsumeGoldTitle(EnumConfigKey key) {
    return switch (key) {
      case CP_DISMISS_CONSUME_GOLD -> "CP 解绑金币";
      case BROTHER_DISMISS_CONSUME_GOLD -> "Brother 解绑金币";
      case SISTERS_DISMISS_CONSUME_GOLD -> "Sisters 解绑金币";
      default -> "关系解绑金币";
    };
  }

  private List<ResidentCpLevelConfigSaveCmd> sortedLevels(
      List<ResidentCpLevelConfigSaveCmd> levels) {
    if (Objects.isNull(levels)) {
      return List.of();
    }
    return levels.stream()
        .filter(Objects::nonNull)
        .sorted(Comparator.comparing(ResidentCpLevelConfigSaveCmd::getLevel,
            Comparator.nullsLast(Integer::compareTo)))
        .toList();
  }

  private String normalizeSysOrigin(String sysOrigin) {
    return Objects.toString(sysOrigin, "").trim().toUpperCase();
  }

  private Long defaultRequiredValue(Long requiredValue) {
    return requiredValue == null ? 0L : requiredValue;
  }

  private Long defaultGold(Long gold) {
    return gold == null ? 0L : gold;
  }

  private String normalizeRelationType(String relationType) {
    if (StringUtils.isBlank(relationType)) {
      return "";
    }
    String normalized = relationType.trim().toUpperCase();
    if (Objects.equals(normalized, "BROTHERS")) {
      return RELATION_TYPE_BROTHER;
    }
    if (Objects.equals(normalized, "SISTER")) {
      return RELATION_TYPE_SISTERS;
    }
    return normalized;
  }

  private String defaultLevelName(Integer level, String levelName) {
    if (StringUtils.isNotBlank(levelName)) {
      return levelName.trim();
    }
    return "CP " + level + "级";
  }
}
