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

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.mongodb.BasicDBObject;
import com.red.circle.common.business.core.enums.OpUserType;
import com.red.circle.framework.mybatis.constant.PageConstant;
import com.red.circle.other.infra.database.mongo.entity.team.team.TeamMember;
import com.red.circle.other.infra.database.mongo.entity.team.team.TeamProfile;
import com.red.circle.other.infra.database.mongo.service.team.team.TeamProfileService;
import com.red.circle.other.inner.enums.team.TeamMemberRole;
import com.red.circle.other.inner.model.cmd.team.StatusTeamBatchCmd;
import com.red.circle.other.inner.model.cmd.team.TeamTableQryCmd;
import com.red.circle.other.inner.model.dto.agency.agency.TeamContact;
import com.red.circle.other.inner.model.dto.agency.agency.TeamCounter;
import com.red.circle.other.inner.model.dto.agency.agency.TeamRemark;
import com.red.circle.other.inner.model.dto.agency.agency.TeamSetting;
import com.red.circle.other.inner.model.dto.agency.agency.TeamStatus;
import com.red.circle.tool.core.collection.CollectionUtils;
import com.red.circle.tool.core.date.TimestampUtils;
import com.red.circle.tool.core.text.StringUtils;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;
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;

/**
 * 团队服务 实现.
 *
 * @author pengliang on 2022/12/6
 */
@Service
@RequiredArgsConstructor
public class TeamProfileServiceImpl implements TeamProfileService {

  private final MongoTemplate mongoTemplate;

  @Override
  public void createBatch(List<TeamProfile> teamProfiles) {
    mongoTemplate.insert(teamProfiles, TeamProfile.class);
  }

  @Override
  public void create(TeamProfile teamProfile) {
    mongoTemplate.save(teamProfile);
  }

  @Override
  public boolean existsOwnUserId(Long ownUserId) {
    return mongoTemplate.exists(Query.query(Criteria.where("ownUserId").is(ownUserId)),
        TeamProfile.class);
  }

  @Override
  public boolean existsAvailableOwnUserId(Long ownUserId) {
    return Objects.nonNull(getAvailableByOwnUserId(ownUserId));
  }

  @Override
  public void updateByIdSelective(TeamProfile teamProfile) {
    Update update = new Update()
        .set("updateTime", TimestampUtils.now())
        .set("updateUser", teamProfile.getUpdateUser())
        .set("originUpdateUser", teamProfile.getUpdateUserOrigin());

    if (StringUtils.isNotBlank(teamProfile.getRegion())) {
      update.set("region", teamProfile.getRegion());
    }

    if (Objects.nonNull(teamProfile.getOwnUserId())) {
      update.set("ownUserId", teamProfile.getOwnUserId());
    }

    if (Objects.nonNull(teamProfile.getStatus())) {
      update.set("status", teamProfile.getStatus());
    }

    if (Objects.nonNull(teamProfile.getWithdrawalEnabled())) {
      update.set("withdrawalEnabled", teamProfile.getWithdrawalEnabled());
    }

    if (Objects.nonNull(teamProfile.getSetting())) {
      TeamSetting setting = teamProfile.getSetting();
      if (Objects.nonNull(setting.getMaxMember())) {
        update.set("setting.maxMember", setting.getMaxMember());
      }
      if (Objects.nonNull(setting.getHostSalaryToAgent())) {
        update.set("setting.hostSalaryToAgent", setting.getHostSalaryToAgent());
      }
    }

    if (Objects.nonNull(teamProfile.getCountry())) {
      update.set("country", teamProfile.getCountry());
    }

    mongoTemplate.updateFirst(Query.query(Criteria.where("id").is(teamProfile.getId())),
        update,
        TeamProfile.class);
  }

  @Override
  public void updateTeamStatusBackBatch(StatusTeamBatchCmd param) {

    Update update = new Update()
        .set("status", param.getStatus())
        .set("updateTime", TimestampUtils.now())
        .set("updateUser", param.getUpdateUser())
        .set("originUpdateUser", 1);

    if (StringUtils.isNotBlank(param.getRemarkId()) && StringUtils.isNotBlank(
        param.getRemarkId())) {
      update.push("remarks", new TeamRemark()
          .setRemarkId(param.getRemarkId())
          .setRemark(param.getRemarks())
          .setCreateBackUser(param.getUpdateUser())
          .setCreateTime(TimestampUtils.now())
      );
    }
    mongoTemplate.updateMulti(Query.query(Criteria.where("id").in(param.getTeamIds()))
        , update, TeamProfile.class);
  }

  @Override
  public void addTeamContact(Long teamId, TeamContact contact) {
    mongoTemplate.updateFirst(Query.query(Criteria.where("id").is(teamId)),
        new Update().push("contacts", contact),
        TeamProfile.class);
  }

  @Override
  public void removeTeamContact(Long teamId, String contactId) {
    mongoTemplate.updateFirst(Query.query(Criteria.where("id").is(teamId)),
        new Update()
            .pull("contacts", new BasicDBObject("contactId", new BasicDBObject("$eq", contactId))),
        TeamProfile.class);
  }

  @Override
  public void addTeamRemarks(Long teamId, TeamRemark remarks) {
    mongoTemplate.updateFirst(Query.query(Criteria.where("id").is(teamId)),
        new Update().push("remarks", remarks),
        TeamProfile.class);
  }

  @Override
  public void removeTeamRemarks(Long teamId, String remarkId) {
    mongoTemplate.updateFirst(Query.query(Criteria.where("id").is(teamId)),
        new Update().pull("remarks",
            new BasicDBObject("remarkId", new BasicDBObject("$eq", remarkId))),
        TeamProfile.class);
  }

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

    @Override
    public List<TeamProfile> getByIds(Set<Long> id) {
        return mongoTemplate.find(Query.query(Criteria.where("id").in(id)), TeamProfile.class);
    }

  @Override
  public boolean checkStatusAvailable(Long id) {
    return mongoTemplate.exists(Query.query(Criteria.where("id").is(id)
            .and("status").is(TeamStatus.AVAILABLE)),
        TeamProfile.class);
  }

  @Override
  public List<TeamProfile> list(TeamTableQryCmd query) {
    Criteria criteria = new Criteria();

    if (Objects.nonNull(query.getId())) {
      criteria.and("id").is(query.getId());
      query.setLastId(null);
    }

    if (Objects.nonNull(query.getLastId())) {
      criteria.and("id").lt(query.getLastId());
    }

    if (StringUtils.isNotBlank(query.getRegion())) {
      criteria.and("region").is(query.getRegion());
    }

    if (StringUtils.isNotBlank(query.getSysOrigin())) {
      criteria.and("sysOrigin").is(query.getSysOrigin());
    }

    if (StringUtils.isNotBlank(query.getAccount())) {
      criteria.and("account").is(query.getAccount());
    }

    if (Objects.nonNull(query.getOwnUserId())) {
      criteria.and("ownUserId").is(query.getOwnUserId());
    }

    if (StringUtils.isNotBlank(query.getStatus())) {
      criteria.and("status").is(query.getStatus());
    }

    if (Objects.nonNull(query.getCreateUser())) {
      criteria.and("createUser").is(query.getCreateUser());
    }

    List<Criteria> andOperator = Lists.newArrayList();

    if (Objects.nonNull(query.getStartTime())
        && Objects.nonNull(query.getEndTime())) {

      andOperator.add(Criteria.where("createTime").gte(query.startTimeToLocalDateTime()));
      andOperator.add(Criteria.where("createTime").lt(query.endTimeToLocalDateTime()));
    }

    if (StringUtils.isNotBlank(query.getMemberQuantityRange())) {
      String[] ranges = StringUtils.split(query.getMemberQuantityRange(), "~");
      if (ranges.length == 2) {

        andOperator.add(Criteria.where("counter.memberQuantity").gte(Long.parseLong(ranges[0])));
        andOperator.add(Criteria.where("counter.memberQuantity").lt(Long.parseLong(ranges[1])));
      }
    }

    if (CollectionUtils.isNotEmpty(andOperator)) {
      criteria.andOperator(andOperator);
    }

    List<TeamProfile> teamProfiles = mongoTemplate.find(Query.query(criteria)
            .with(Sort.by(Sort.Order.desc("id")))
            .limit(
                Objects.nonNull(query.getLimit()) ? query.getLimit() : PageConstant.DEFAULT_LIMIT_SIZE),
        TeamProfile.class);

    return filterCurrentAvailableOwnProfiles(teamProfiles);
  }

  @Override
  public List<TeamProfile> listRegion(String region, Integer limit) {
    return mongoTemplate.find(Query.query(Criteria.where("region").is(region))
            .with(Sort.by(Sort.Order.desc("id")))
            .limit(limit),
        TeamProfile.class);
  }

  @Override
  public List<TeamProfile> listAll(String sysOrigin) {
    return mongoTemplate.find(Query.query(Criteria.where("sysOrigin").is(sysOrigin)),
        TeamProfile.class);
  }

  @Override
  public List<TeamProfile> listRegionAll(String region) {
    return mongoTemplate.find(Query.query(Criteria.where("region").is(region)),
        TeamProfile.class);
  }

  @Override
  public Set<Long> listIdByRegion(String regionId) {

    List<TeamProfile> teamProfiles = mongoTemplate
        .find(Query.query(Criteria.where("region").is(regionId)), TeamProfile.class);
    if (CollectionUtils.isEmpty(teamProfiles)) {
      return Sets.newHashSet();
    }

    return teamProfiles.stream().map(TeamProfile::getId).collect(Collectors.toSet());

  }


  @Override
  public TeamProfile getByOwnUserId(Long ownUserId) {
    return mongoTemplate
        .findOne(Query.query(Criteria.where("ownUserId").is(ownUserId)), TeamProfile.class);
  }

  @Override
  public TeamProfile getAvailableByOwnUserId(Long ownUserId) {
    List<TeamProfile> teamProfiles = mongoTemplate.find(
        Query.query(Criteria.where("ownUserId").is(ownUserId)
                .and("status").is(TeamStatus.AVAILABLE))
            .with(Sort.by(Sort.Order.desc("id"))),
        TeamProfile.class);

    return filterCurrentAvailableOwnProfiles(teamProfiles).stream().findFirst().orElse(null);
  }

  private List<TeamProfile> filterCurrentAvailableOwnProfiles(List<TeamProfile> teamProfiles) {
    if (CollectionUtils.isEmpty(teamProfiles)) {
      return teamProfiles;
    }

    List<TeamProfile> availableProfiles = teamProfiles.stream()
        .filter(teamProfile -> TeamStatus.AVAILABLE.equals(teamProfile.getStatus()))
        .collect(Collectors.toList());

    if (CollectionUtils.isEmpty(availableProfiles)) {
      return teamProfiles;
    }

    Set<Long> teamIds = availableProfiles.stream()
        .map(TeamProfile::getId)
        .filter(Objects::nonNull)
        .collect(Collectors.toSet());
    Set<Long> ownUserIds = availableProfiles.stream()
        .map(TeamProfile::getOwnUserId)
        .filter(Objects::nonNull)
        .collect(Collectors.toSet());

    if (CollectionUtils.isEmpty(teamIds) || CollectionUtils.isEmpty(ownUserIds)) {
      return teamProfiles.stream()
          .filter(teamProfile -> !TeamStatus.AVAILABLE.equals(teamProfile.getStatus()))
          .collect(Collectors.toList());
    }

    Set<String> currentOwnerKeys = mongoTemplate.find(Query.query(Criteria.where("teamId").in(teamIds)
                .and("memberId").in(ownUserIds)
                .and("role").is(TeamMemberRole.OWN)),
            TeamMember.class)
        .stream()
        .map(teamMember -> ownerKey(teamMember.getTeamId(), teamMember.getMemberId()))
        .collect(Collectors.toSet());

    return teamProfiles.stream()
        .filter(teamProfile -> !TeamStatus.AVAILABLE.equals(teamProfile.getStatus())
            || currentOwnerKeys.contains(ownerKey(teamProfile.getId(), teamProfile.getOwnUserId())))
        .collect(Collectors.toList());
  }

  private String ownerKey(Long teamId, Long ownUserId) {
    return teamId + "_" + ownUserId;
  }

  @Override
  public List<TeamProfile> getByOwnUserIds(Set<Long> ownUserIds) {
    return mongoTemplate.find(Query.query(Criteria.where("ownUserId").in(ownUserIds)), TeamProfile.class);
  }

  @Override
  public List<TeamProfile> listByIds(Set<Long> ids) {
    return mongoTemplate.find(Query.query(Criteria.where("id").in(ids)), TeamProfile.class);
  }

  @Override
  public Map<Long, TeamProfile> mapProfileByIds(Set<Long> ids) {

    if (CollectionUtils.isEmpty(ids)) {
      return Maps.newHashMap();
    }
    List<TeamProfile> teamProfiles = listByIds(ids);
    if (CollectionUtils.isEmpty(teamProfiles)) {
      return Maps.newHashMap();
    }
    return teamProfiles.stream().collect(Collectors.toMap(TeamProfile::getId, v -> v));
  }

  @Override
  public void incrCounterMember(Long id) {
    mongoTemplate.updateFirst(Query.query(Criteria.where("id").is(id)),
        new Update().inc("counter.memberQuantity", 1),
        TeamProfile.class);
  }

  @Override
  public void incrCounterAdmin(Long id) {
    mongoTemplate.updateFirst(Query.query(Criteria.where("id").is(id)),
        new Update().inc("counter.adminQuantity", 1),
        TeamProfile.class);
  }

  @Override
  public void decrCounterMember(Long id) {
    mongoTemplate.updateFirst(Query.query(Criteria.where("id").is(id)),
        new Update()
            .inc("counter.memberQuantity", -1),
        TeamProfile.class);
  }

  @Override
  public void decrCounter(Long id) {
    mongoTemplate.updateFirst(Query.query(Criteria.where("id").is(id)),
        new Update()
            .inc("counter.memberQuantity", -1)
            .inc("counter.adminQuantity", -1),
        TeamProfile.class);
  }

  @Override
  public void decrCounterAdmin(Long id) {
    mongoTemplate.updateFirst(Query.query(Criteria.where("id").is(id)),
        new Update()
            .inc("counter.adminQuantity", -1),
        TeamProfile.class);
  }

  @Override
  public void updateCounterMemberQuantity(Long id, Long memberQuantity) {
    mongoTemplate.updateFirst(Query.query(Criteria.where("id").is(id)),
        new Update()
            .set("counter.memberQuantity", memberQuantity),
        TeamProfile.class);
  }

  @Override
  public void updateCounter(Long id, TeamCounter counter) {
    mongoTemplate.updateFirst(Query.query(Criteria.where("id").is(id)),
        new Update()
            .set("counter", counter),
        TeamProfile.class);
  }

  @Override
  public void scanStatusAvailable(Consumer<List<TeamProfile>> execute, Integer limit) {
    Long lastId = null;
    while (true) {
      Criteria criteria = new Criteria();
      if (Objects.nonNull(lastId)) {
        criteria.and("id").lt(lastId);
      }

      List<TeamProfile> profiles = mongoTemplate.find(Query.query(criteria)
              .with(Sort.by(Sort.Order.desc("id")))
              .limit(limit),
          TeamProfile.class);

      if (CollectionUtils.isEmpty(profiles)) {
        return;
      }

      execute.accept(profiles);
      TeamProfile profile = profiles.get(profiles.size() - 1);
      if (Objects.isNull(profile) || Objects.isNull(profile.getId())) {
        return;
      }
      lastId = profile.getId();
    }

  }

  @Override
  public List<TeamProfile> listStatusAvailable(Set<Long> teamIds) {
    return mongoTemplate.find(Query.query(Criteria.where("id").in(teamIds)
        .and("status").is(TeamStatus.AVAILABLE)), TeamProfile.class);
  }


  @Override
  public void updateTeamNickNameOrAvatar(TeamProfile teamProfile) {

    Update update = new Update()
        .set("updateTime", TimestampUtils.now())
        .set("updateUser", teamProfile.getUpdateUser())
        .set("originUpdateUser", teamProfile.getUpdateUserOrigin());

//
//    if (teamProfile.getAvatar() != null) {
//      update.set("avatar", teamProfile.getAvatar());
//    }

    mongoTemplate.updateFirst(Query.query(Criteria.where("id").is(teamProfile.getId())),
        update, TeamProfile.class);

  }

  @Override
  public void updateTeamOwn(Long teamId, Long newOwnUserId, Long backUpdateUserId) {
    Update update = new Update()
        .set("updateTime", TimestampUtils.now())
        .set("updateUser", backUpdateUserId)
        .set("originUpdateUser", OpUserType.BACK.getType())
        .set("ownUserId", newOwnUserId);

    mongoTemplate.updateFirst(Query.query(Criteria.where("id").is(teamId)),
        update, TeamProfile.class);
  }

  @Override
  public void removeBatchById(List<Long> ids) {
    mongoTemplate.remove(Query.query(Criteria.where("id").in(ids)), TeamProfile.class);
  }

  @Override
  public Map<Long, Long> mapOwnUserIdsByIds(Set<Long> ids) {

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

    List<TeamProfile> list = mongoTemplate
        .find(Query.query(Criteria.where("id").in(ids)
            .and("status").is(TeamStatus.AVAILABLE)), TeamProfile.class);
    if (CollectionUtils.isEmpty(list)) {
      return Maps.newHashMap();
    }

    return list.stream().collect(Collectors.toMap(TeamProfile::getId, TeamProfile::getOwnUserId));

  }

  @Override
  public List<TeamProfile> listStatusAvailable() {

    return mongoTemplate
        .find(Query.query(Criteria.where("status").is(TeamStatus.AVAILABLE)), TeamProfile.class);

  }

  @Override
  public String getTeamRegionId(Long id) {

    if (Objects.isNull(id)) {
      return null;
    }

    TeamProfile teamProfile = mongoTemplate
        .findOne(Query.query(Criteria.where("id").is(id)), TeamProfile.class);
    if (Objects.isNull(teamProfile)) {
      return null;
    }

    return teamProfile.getRegion();
  }

  @Override
  public Long getOwnUserIdById(Long id) {

    if (Objects.isNull(id)) {
      return null;
    }

    TeamProfile teamProfile = mongoTemplate
        .findOne(Query.query(Criteria.where("id").is(id)), TeamProfile.class);
    if (Objects.isNull(teamProfile)) {
      return null;
    }

    return teamProfile.getOwnUserId();
  }


}
