package com.red.circle.other.infra.database.mongo.service.team.team.impl;

import com.google.common.collect.Maps;
import com.mongodb.client.result.UpdateResult;
import com.red.circle.other.infra.common.sys.CountryCodeAliasSupport;
import com.red.circle.other.infra.database.mongo.dto.team.TeamPolicy;
import com.red.circle.other.infra.database.mongo.entity.team.team.TeamPolicyManager;
import com.red.circle.other.infra.database.mongo.service.team.team.TeamPolicyManagerService;
import com.red.circle.other.inner.enums.team.TeamPolicyStatusEnum;
import com.red.circle.other.inner.enums.team.TeamPolicyTypeEnum;
import com.red.circle.tool.core.collection.CollectionUtils;
import com.red.circle.tool.core.text.StringUtils;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Service;

import java.sql.Timestamp;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 团队策略.
 *
 * @author pengshigang on 2022/12/12
 */
@Service
@RequiredArgsConstructor
public class TeamPolicyManagerServiceImpl implements TeamPolicyManagerService {

  private static final ZoneId RIYADH_ZONE = ZoneId.of("Asia/Riyadh");
  private static final DateTimeFormatter EFFECTIVE_MONTH_FORMATTER =
      DateTimeFormatter.ofPattern("yyyyMM");

  private final MongoTemplate mongoTemplate;
  private final CountryCodeAliasSupport countryCodeAliasSupport;

  @Override
  public List<TeamPolicyManager> listSysOriginRegion(String sysOrigin, String region) {
    return mongoTemplate
        .find(Query.query(Criteria.where("region").is(region)
                .and("sysOrigin").is(sysOrigin)).with(Sort.by(Sort.Order.desc("createTime"))),
            TeamPolicyManager.class);
  }

  @Override
  public List<TeamPolicyManager> listTeamPolicy(String sysOrigin, String region) {
    return mongoTemplate
        .find(Query.query(Criteria.where("region").is(region)
                    .and("sysOrigin").is(sysOrigin))
                .with(Sort.by(Sort.Order.desc("createTime"))),
            TeamPolicyManager.class);
  }

  @Override
  public List<TeamPolicyManager> listTeamPolicyAndTypePolicy(String sysOrigin, String region,
                                                             String policyType, String countryCode) {
    Criteria criteria = new Criteria();
    criteria.and("region").is(region);
    criteria.and("sysOrigin").is(sysOrigin);
    if (StringUtils.isNotBlank(policyType) && Objects.equals(policyType, TeamPolicyTypeEnum.SALARY_DIAMOND.name())) {
      criteria.and("policyType").is(policyType);
    }else {
      criteria.and("policyType").ne(TeamPolicyTypeEnum.SALARY_DIAMOND.name());
    }
    if (StringUtils.isNotBlank(countryCode)) {
      criteria.and("countryCode").in(countryCodeAliasSupport.getMatchCodes(countryCode));
    }

    return mongoTemplate.find( Query.query(criteria).with(Sort.by(Sort.Order.desc("createTime"))), TeamPolicyManager.class);
  }

  @Override
  public List<TeamPolicyManager> listReleaseTeamPolicy(String sysOrigin, String region) {
    return mongoTemplate
        .find(Query.query(Criteria.where("region").is(region)
                    .and("release").is(Boolean.TRUE)
                    .and("sysOrigin").is(sysOrigin))
                .with(activePolicySort()),
            TeamPolicyManager.class);
  }


  @Override
  public TeamPolicyManager getReleaseByRegionAndType(String sysOrigin, String region) {
    return mongoTemplate.findOne(Query.query(
        Criteria.where("region").is(region)
            .and("release").is(Boolean.TRUE)
            .and("sysOrigin").is(sysOrigin)
    ).with(activePolicySort()), TeamPolicyManager.class);
  }

  @Override
  public TeamPolicyManager getReleaseByRegionAndType(String sysOrigin, String region, String policyType,  String countryCode) {

    Criteria criteria = new Criteria();
    criteria.and("region").is(region);
    criteria.and("release").is(Boolean.TRUE);
    criteria.and("sysOrigin").is(sysOrigin);
    if (StringUtils.isNotBlank(policyType) && Objects.equals(policyType, TeamPolicyTypeEnum.SALARY_DIAMOND.name())) {
      criteria.and("policyType").is(policyType);
    }else {
      criteria.and("policyType").ne(TeamPolicyTypeEnum.SALARY_DIAMOND.name());
    }
    if (StringUtils.isNotBlank(countryCode)) {
      criteria.and("countryCode").in(countryCodeAliasSupport.getMatchCodes(countryCode));
    }

    return mongoTemplate.findOne(Query.query(criteria).with(activePolicySort()), TeamPolicyManager.class);
  }

  @Override
  public Map<String, TeamPolicyManager> mapRegionRelease(Set<String> regions) {

    if (CollectionUtils.isEmpty(regions)) {
      return Maps.newHashMap();
    }

    List<TeamPolicyManager> teamPolicyManagers = mongoTemplate.find(
        Query.query(Criteria.where("region").in(regions).and("release").is(Boolean.TRUE))
            .with(activePolicySort()),
        TeamPolicyManager.class);

    if (CollectionUtils.isEmpty(teamPolicyManagers)) {
      return Maps.newHashMap();
    }

    return teamPolicyManagers.stream()
        .collect(Collectors.toMap(TeamPolicyManager::getRegion, v -> v, (existing, replacement) -> existing));
  }

  @Override
  public Map<String, TeamPolicyManager> mapRegionReleaseWithCountry(Set<String> regions) {

    if (CollectionUtils.isEmpty(regions)) {
      return Maps.newHashMap();
    }

    // 查询所有 region 的已发布政策（所有 countryCode）
    List<TeamPolicyManager> policies = mongoTemplate.find(
        Query.query(Criteria.where("region").in(regions)
            .and("release").is(Boolean.TRUE))
            .with(activePolicySort()),
        TeamPolicyManager.class);

    if (CollectionUtils.isEmpty(policies)) {
      return Maps.newHashMap();
    }

    // 构建Map，key = region + "_" + countryCode
    return policies.stream()
        .collect(Collectors.toMap(
            policy -> buildPolicyMapKey(policy.getRegion(), policy.getCountryCode()),
            policy -> policy,
            (existing, replacement) -> existing
        ));
  }

  /**
   * 构建policy map的key.
   */
  private String buildPolicyMapKey(String region, String countryCode) {
    String delimiter = "_";
    String defaultCountry = "DEFAULT";
    return region + delimiter +
        (StringUtils.isNotBlank(countryCode) ? countryCode : defaultCountry);
  }

  @Override
  public TeamPolicyManager getById(Long id) {
    return mongoTemplate.findOne(Query.query(Criteria.where("id").is(id)), TeamPolicyManager.class);
  }

  @Override
  public void add(TeamPolicyManager policyManager) {
    Timestamp now = Optional.ofNullable(policyManager.getCreateTime())
        .orElseGet(() -> new Timestamp(System.currentTimeMillis()));
    normalizePolicyState(policyManager, now);

    if (Boolean.TRUE.equals(policyManager.getRelease()) && StringUtils.isNotBlank(policyManager.getRegion())
        && StringUtils.isNotBlank(policyManager.getSysOrigin())) {
      deactivateActivePolicies(policyManager, now);
    }

    if (CollectionUtils.isNotEmpty(policyManager.getPolicy())) {
      policyManager.setPolicy(policyManager.getPolicy().stream()
          .sorted(Comparator.comparing(TeamPolicy::getLevel))
          .collect(Collectors.toList()));
    }
    mongoTemplate.save(policyManager);
  }

  @Override
  public List<TeamPolicyManager> listDueScheduledPolicies(Timestamp now) {
    return mongoTemplate.find(Query.query(Criteria.where("policyStatus")
            .is(TeamPolicyStatusEnum.SCHEDULED.name())
            .and("release").is(Boolean.FALSE)
            .and("effectiveStartTime").lte(now))
        .with(Sort.by(Sort.Order.asc("effectiveStartTime"), Sort.Order.asc("createTime"))),
        TeamPolicyManager.class);
  }

  @Override
  public TeamPolicyManager activateScheduledPolicy(Long id, Timestamp now) {
    TeamPolicyManager scheduled = mongoTemplate.findOne(Query.query(Criteria.where("id").is(id)
            .and("policyStatus").is(TeamPolicyStatusEnum.SCHEDULED.name())
            .and("release").is(Boolean.FALSE)
            .and("effectiveStartTime").lte(now)),
        TeamPolicyManager.class);
    if (Objects.isNull(scheduled)) {
      return null;
    }

    deactivateActivePolicies(scheduled, now);

    Update update = new Update()
        .set("release", Boolean.TRUE)
        .set("historyRelease", Boolean.TRUE)
        .set("policyStatus", TeamPolicyStatusEnum.ACTIVE.name())
        .set("effectiveMonth", resolveEffectiveMonth(scheduled.getEffectiveStartTime()))
        .set("activatedTime", now)
        .set("updateTime", now);

    UpdateResult result = mongoTemplate.updateFirst(Query.query(Criteria.where("id").is(id)
            .and("policyStatus").is(TeamPolicyStatusEnum.SCHEDULED.name())
            .and("release").is(Boolean.FALSE)),
        update,
        TeamPolicyManager.class);

    if (result.getModifiedCount() <= 0) {
      return null;
    }
    return getById(id);
  }

  @Override
  public void deleteById(Long id) {

    mongoTemplate.remove(Query.query(Criteria.where("id").is(id)), TeamPolicyManager.class);
  }

  private void normalizePolicyState(TeamPolicyManager policyManager, Timestamp now) {
    String policyStatus = policyManager.getPolicyStatus();
    if (StringUtils.isBlank(policyStatus)) {
      policyStatus = Boolean.TRUE.equals(policyManager.getRelease())
          ? TeamPolicyStatusEnum.ACTIVE.name()
          : TeamPolicyStatusEnum.DRAFT.name();
    }

    if (Objects.equals(policyStatus, TeamPolicyStatusEnum.SCHEDULED.name())) {
      Timestamp effectiveStartTime = Optional.ofNullable(policyManager.getEffectiveStartTime())
          .orElseGet(this::nextMonthStartTime);
      policyManager.setPolicyStatus(TeamPolicyStatusEnum.SCHEDULED.name());
      policyManager.setRelease(Boolean.FALSE);
      policyManager.setHistoryRelease(Boolean.FALSE);
      policyManager.setEffectiveStartTime(effectiveStartTime);
      policyManager.setEffectiveMonth(resolveEffectiveMonth(effectiveStartTime));
      policyManager.setActivatedTime(null);
      return;
    }

    if (Boolean.TRUE.equals(policyManager.getRelease())
        || Objects.equals(policyStatus, TeamPolicyStatusEnum.ACTIVE.name())) {
      policyManager.setPolicyStatus(TeamPolicyStatusEnum.ACTIVE.name());
      policyManager.setRelease(Boolean.TRUE);
      policyManager.setHistoryRelease(Boolean.TRUE);
      policyManager.setActivatedTime(Optional.ofNullable(policyManager.getActivatedTime()).orElse(now));
      policyManager.setEffectiveStartTime(Optional.ofNullable(policyManager.getEffectiveStartTime()).orElse(now));
      policyManager.setEffectiveMonth(Optional.ofNullable(policyManager.getEffectiveMonth())
          .orElseGet(() -> resolveEffectiveMonth(policyManager.getEffectiveStartTime())));
      return;
    }

    policyManager.setPolicyStatus(policyStatus);
    policyManager.setRelease(Boolean.FALSE);
    policyManager.setHistoryRelease(Boolean.TRUE.equals(policyManager.getHistoryRelease()));
  }

  private void deactivateActivePolicies(TeamPolicyManager policyManager, Timestamp now) {
    Criteria criteria = samePolicyScopeCriteria(policyManager)
        .and("release").is(Boolean.TRUE);
    if (Objects.nonNull(policyManager.getId())) {
      criteria.and("id").ne(policyManager.getId());
    }

    mongoTemplate.updateMulti(Query.query(criteria), new Update()
            .set("release", Boolean.FALSE)
            .set("historyRelease", Boolean.TRUE)
            .set("policyStatus", TeamPolicyStatusEnum.HISTORY.name())
            .set("updateTime", now),
        TeamPolicyManager.class);
  }

  private Criteria samePolicyScopeCriteria(TeamPolicyManager policyManager) {
    Criteria criteria = Criteria.where("region").is(policyManager.getRegion())
        .and("sysOrigin").is(policyManager.getSysOrigin());
    appendPolicyTypeCriteria(criteria, policyManager.getPolicyType());
    appendCountryScopeCriteria(criteria, policyManager.getCountryCode());
    return criteria;
  }

  private void appendPolicyTypeCriteria(Criteria criteria, String policyType) {
    if (StringUtils.isNotBlank(policyType)
        && Objects.equals(policyType, TeamPolicyTypeEnum.SALARY_DIAMOND.name())) {
      criteria.and("policyType").is(policyType);
      return;
    }
    criteria.and("policyType").ne(TeamPolicyTypeEnum.SALARY_DIAMOND.name());
  }

  private void appendCountryScopeCriteria(Criteria criteria, String countryCode) {
    if (StringUtils.isNotBlank(countryCode)) {
      criteria.and("countryCode").in(countryCodeAliasSupport.getMatchCodes(countryCode));
      return;
    }
    criteria.orOperator(
        Criteria.where("countryCode").exists(false),
        Criteria.where("countryCode").is(null),
        Criteria.where("countryCode").is(""));
  }

  private Timestamp nextMonthStartTime() {
    LocalDate nextMonthFirstDay = LocalDate.now(RIYADH_ZONE).plusMonths(1).withDayOfMonth(1);
    return Timestamp.from(nextMonthFirstDay.atStartOfDay(RIYADH_ZONE).toInstant());
  }

  private Integer resolveEffectiveMonth(Timestamp effectiveStartTime) {
    if (Objects.isNull(effectiveStartTime)) {
      return null;
    }
    return Integer.valueOf(effectiveStartTime.toInstant()
        .atZone(RIYADH_ZONE)
        .format(EFFECTIVE_MONTH_FORMATTER));
  }

  private Sort activePolicySort() {
    return Sort.by(
        Sort.Order.desc("effectiveStartTime"),
        Sort.Order.desc("activatedTime"),
        Sort.Order.desc("createTime"));
  }

}
