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.common.business.enums.OperationUserOrigin;
import com.red.circle.framework.mybatis.constant.PageConstant;
import com.red.circle.other.infra.database.mongo.dto.team.TeamMemberCount;
import com.red.circle.other.infra.database.mongo.entity.team.team.TeamMember;
import com.red.circle.other.infra.database.mongo.entity.team.team.TeamMemberTarget;
import com.red.circle.other.infra.database.mongo.service.team.team.TeamMemberService;
import com.red.circle.other.inner.model.cmd.team.TeamMemberBackQryCmd;
import com.red.circle.other.inner.model.dto.agency.agency.TeamRemark;
import com.red.circle.other.inner.enums.team.TeamMemberRole;
import com.red.circle.tool.core.collection.CollectionUtils;
import com.red.circle.tool.core.date.TimestampUtils;
import com.red.circle.tool.core.date.ZonedDateTimeAsiaRiyadhUtils;
import com.red.circle.tool.core.text.StringUtils;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
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.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
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 TeamMemberServiceImpl implements TeamMemberService {

  private final MongoTemplate mongoTemplate;

  @Override
  public void addMemberBatch(List<TeamMember> teamMembers) {
    mongoTemplate.insert(teamMembers, TeamMember.class);
  }

  @Override
  public void addMember(TeamMember teamMember) {
    teamMember.setId(teamMember.getTeamId() + "_" + teamMember.getMemberId());
    if (Objects.isNull(teamMember.getCreateTime())) {
      teamMember.setCreateTime(TimestampUtils.now());
    }
    if (Objects.isNull(teamMember.getUpdateTime())) {
      teamMember.setUpdateTime(TimestampUtils.now());
    }
    if (Objects.isNull(teamMember.getActiveTime())) {
      teamMember.setActiveTime(TimestampUtils.now());
    }
    if (Objects.isNull(teamMember.getWalletWithdrawal())) {
      teamMember.setWalletWithdrawal(true);
    }
    mongoTemplate.save(teamMember);
  }

  @Override
  public void updateActiveTimeNow(Long memberId) {
    mongoTemplate.updateFirst(Query.query(Criteria.where("memberId").is(memberId)),
        new Update().set("activeTime", TimestampUtils.now()),
        TeamMember.class);
  }

  @Override
  public void updateMemberRemarks(Long sortId, String remarks) {
    mongoTemplate.updateFirst(Query.query(Criteria.where("sortId").is(sortId)),
        new Update().set("remarks", remarks)
            .set("updateTime", TimestampUtils.now()),
        TeamMember.class);
  }

  @Override
  public boolean existsMember(Long memberId) {
    return mongoTemplate.exists(Query.query(Criteria.where("memberId").is(memberId)),
        TeamMember.class);
  }

  @Override
  public boolean existsTeamAdmin(Long teamId) {
    return mongoTemplate.exists(Query.query(Criteria.where("teamId").is(teamId)
            .and("role").is(TeamMemberRole.ADMIN)),
        TeamMember.class);
  }

  @Override
  public boolean existsTeamOwn(Long memberId) {
    return mongoTemplate.exists(Query.query(Criteria.where("memberId").is(memberId)
            .and("role").is(TeamMemberRole.OWN)),
        TeamMember.class);
  }

  @Override
  public void updateMemberRole(Long memberId, TeamMemberRole role, Long operationAppUser) {
    mongoTemplate.updateFirst(Query.query(Criteria.where("memberId").is(memberId)),
        new Update().set("role", role)
            .set("updateTime", TimestampUtils.now())
            .set("updateUser", operationAppUser)
            .set("updateUserOrigin", OpUserType.APP.getType()),
        TeamMember.class);
  }

  @Override
  public void updateWalletWithdrawal(Long memberId, boolean walletWithdrawal,
      Long operationAppUser) {
    mongoTemplate.updateFirst(Query.query(Criteria.where("memberId").is(memberId)),
        new Update().set("walletWithdrawal", walletWithdrawal)
            .set("updateTime", TimestampUtils.now())
            .set("updateUser", operationAppUser)
            .set("updateUserOrigin", OpUserType.APP.getType()),
        TeamMember.class);
  }

  @Override
  public void updateMemberRoleBack(Long memberId, TeamMemberRole role, Long operationBackUser) {
    mongoTemplate.updateFirst(Query.query(Criteria.where("memberId").is(memberId)),
        new Update().set("role", role)
            .set("updateTime", TimestampUtils.now())
            .set("updateUser", operationBackUser)
            .set("updateUserOrigin", OperationUserOrigin.BACK.getValue()),
        TeamMember.class);
  }

  @Override
  public TeamMember getByMemberId(Long memberId) {
    return mongoTemplate.findOne(Query.query(Criteria.where("memberId").is(memberId)),
        TeamMember.class);
  }

  @Override
  public TeamMember getTeamOwn(Long teamId) {
    return mongoTemplate.findOne(Query.query(Criteria.where("teamId").is(teamId)
            .and("role").is(TeamMemberRole.OWN)),
        TeamMember.class);
  }

  @Override
  public TeamMember getMember(Long teamId, Long memberId) {
    return mongoTemplate.findOne(Query.query(Criteria.where("memberId").is(memberId)
            .and("teamId").is(teamId)),
        TeamMember.class);
  }

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

  @Override
  public List<TeamMember> listBySortIds(Set<Long> sortIds) {
    return mongoTemplate.find(Query.query(Criteria.where("sortId").in(sortIds)),
        TeamMember.class);
  }

  @Override
  public TeamMember getBySortId(Long sortId) {
    return mongoTemplate.findOne(Query.query(Criteria.where("sortId").is(sortId)),
        TeamMember.class);
  }

  @Override
  public TeamMember removeMember(Long sortId) {
    return mongoTemplate.findAndRemove(Query.query(Criteria.where("memberId").in(sortId)),
        TeamMember.class);
  }

  @Override
  public void removeBatchByTeamId(List<Long> teamIds) {
    mongoTemplate.remove(Query.query(Criteria.where("teamId").in(teamIds)),
        TeamMember.class);
  }

  @Override
  public void removeMemberByMemberId(Long memberId) {
    mongoTemplate.remove(Query.query(Criteria.where("memberId").is(memberId)),
        TeamMember.class);
  }


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

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

  @Override
  public List<TeamMember> listTeamMember(Long teamId, Integer limit) {
    return mongoTemplate.find(Query.query(Criteria.where("teamId").is(teamId))
            .with(Sort.by(Sort.Order.desc("sortId")))
            .limit(limit)
        , TeamMember.class);
  }

  @Override
  public List<TeamMember> listTeamMember(Long teamId, Long lastId, Integer limit) {
    Criteria criteria = Criteria.where("teamId").is(teamId);

    if (Objects.nonNull(lastId)) {
      criteria.and("sortId").lt(lastId);
    }
    return mongoTemplate.find(Query.query(criteria)
            .with(Sort.by(Sort.Order.desc("sortId")))
            .limit(limit)
        , TeamMember.class);
  }

  @Override
  public List<TeamMember> listTeamMember(TeamMemberBackQryCmd query) {

    Criteria criteria = new Criteria();

    if (Objects.nonNull(query.getTeamId())) {
      criteria.and("teamId").is(query.getTeamId());
    }

    if (Objects.nonNull(query.getMemberId())) {
      criteria.and("memberId").is(query.getMemberId());
    }

    if (Objects.nonNull(query.getRole())) {
      criteria.and("role").is(query.getRole());
    }

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

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

    if (Objects.nonNull(query.getStartTime())
        && Objects.nonNull(query.getEndTime())) {
      criteria.andOperator(
          Criteria.where("createTime").gte(query.startTimeToLocalDateTime()),
          Criteria.where("createTime").lte(query.endTimeToLocalDateTime())
      );
    }

    if (Objects.nonNull(query.getNotActiveDays())) {
      criteria.and("activeTime").lt(LocalDateTime.now().minusDays(query.getNotActiveDays()));
    }

    return mongoTemplate.find(Query.query(criteria)
            .with(Sort.by(Sort.Order.desc("sortId")))
            .limit(Objects.nonNull(query.getLimit()) ?
                query.getLimit() : PageConstant.DEFAULT_LIMIT_SIZE),
        TeamMember.class);
  }

  @Override
  public List<TeamMember> listTeamMembers(Set<Long> memberIds, TeamMemberRole role) {
    return mongoTemplate.find(
        Query.query(Criteria.where("memberId").in(memberIds)
                .and("role").is(role))
            .with(Sort.by((Sort.Order.desc("createTime")))),
        TeamMember.class);
  }

  @Override
  public List<Long> listMemberIds(Set<Long> memberIds) {
    List<TeamMember> teamMembers = listTeamMembers(memberIds);

    if (CollectionUtils.isEmpty(teamMembers)) {
      return Lists.newArrayList();
    }
    return teamMembers.stream().map(TeamMember::getMemberId).collect(Collectors.toList());
  }

  @Override
  public List<Long> listTeamIdByMemberIds(Set<Long> memberIds) {
    List<TeamMember> teamMembers = listTeamMembers(memberIds);

    if (CollectionUtils.isEmpty(teamMembers)) {
      return Lists.newArrayList();
    }
    return teamMembers.stream().map(TeamMember::getTeamId).collect(Collectors.toList());
  }

  @Override
  public List<Long> listTeamIds(Set<Long> memberIds) {
    List<TeamMember> teamMembers = listTeamMembers(memberIds);

    if (CollectionUtils.isEmpty(teamMembers)) {
      return Lists.newArrayList();
    }
    return teamMembers.stream().map(TeamMember::getTeamId).collect(Collectors.toList());
  }

  @Override
  public List<TeamMember> listByMemberIds(Set<Long> memberIds) {
    return listTeamMembers(memberIds);
  }

  @Override
  public List<TeamMember> list() {
    return mongoTemplate.find(Query.query(new Criteria()), TeamMember.class);
  }

  private List<TeamMember> listTeamMembers(Collection<Long> memberIds) {
    return mongoTemplate.find(
        Query.query(Criteria.where("memberId").in(memberIds)),
        TeamMember.class);
  }

  @Override
  public Map<Long, TeamMemberCount> countTeamMember(Set<Long> teamIds) {

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

    Aggregation aggregation = Aggregation.newAggregation(
        Aggregation.match(Criteria.where("teamId").in(teamIds)),
        Aggregation.group("teamId", "role").count().as("quantity")
    );
    AggregationResults<BasicDBObject> aggregationResults = mongoTemplate.aggregate(aggregation,
        TeamMember.class,
        BasicDBObject.class);

    List<BasicDBObject> basicDBObjects = aggregationResults.getMappedResults();
    if (CollectionUtils.isEmpty(basicDBObjects)) {
      return Maps.newHashMap();
    }

    Map<Long, TeamMemberCount> result = Maps.newHashMap();
    basicDBObjects.forEach(obj -> {
      Long teamId = obj.getLong("teamId");
      Long quantity = obj.getLong("quantity");
      String role = obj.getString("role");

      if (!result.containsKey(teamId)) {
        result.put(teamId, new TeamMemberCount().
            setTeamId(teamId)
            .setMemberQuantity(0L)
            .setAdminQuantity(0L)
            .setOwnQuantity(0L)
        );
      }
      if (TeamMemberRole.MEMBER.eq(role)) {
        result.get(teamId).setMemberQuantity(quantity);
      }
      if (TeamMemberRole.ADMIN.eq(role)) {
        result.get(teamId).setAdminQuantity(quantity);
      }
      if (TeamMemberRole.OWN.eq(role)) {
        result.get(teamId).setOwnQuantity(quantity);
      }
    });
    return result;
  }

  @Override
  public void updateTeamOwn(Long teamId, Long ownUserId, Long updateBackUser) {
    mongoTemplate.updateFirst(Query.query(Criteria.where("teamId").is(teamId)),
        new Update().set("memberId", ownUserId)
            .set("role", TeamMemberRole.OWN)
            .set("updateTime", TimestampUtils.now())
            .set("updateUser", updateBackUser)
            .set("updateUserOrigin", OpUserType.BACK.getType()),
        TeamMember.class);
  }

  @Override
  public Map<Long, TeamMember> mapByMemberIds(Collection<Long> memberIds) {

    if (CollectionUtils.isEmpty(memberIds)) {
      return Maps.newHashMap();
    }
    List<TeamMember> members = listTeamMembers(memberIds);

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

    return members.stream()
        .collect(Collectors.toMap(TeamMember::getMemberId, Function.identity(), (v0, v1) -> v1));
  }

  @Override
  public Map<Long, List<TeamMember>> mapByTeamIds(Set<Long> teamIds) {

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

    List<TeamMember> members = mongoTemplate
        .find(Query.query(Criteria.where("teamId").in(teamIds)), TeamMember.class);

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

    return members.stream().collect(Collectors.groupingBy(TeamMember::getTeamId));
  }

  @Override
  public List<TeamMember> listByTeamId(Long teamId) {

    List<TeamMember> members = mongoTemplate
        .find(Query.query(Criteria.where("teamId").is(teamId)), TeamMember.class);
    if (CollectionUtils.isEmpty(members)) {
      return Lists.newArrayList();
    }
    return members;
  }

  @Override
  public Set<Long> getMemberIdsByTeamId(Long teamId) {

    List<TeamMember> members = mongoTemplate
        .find(Query.query(Criteria.where("teamId").is(teamId)), TeamMember.class);
    if (CollectionUtils.isEmpty(members)) {
      return Sets.newHashSet();
    }

    return members.stream().map(TeamMember::getMemberId).collect(Collectors.toSet());
  }

  @Override
  public void updateMemberTeamIdAndRoleOwn(Long newTeamId, Long memberId, Long updateBackUser) {

    mongoTemplate.updateFirst(Query.query(Criteria.where("memberId").is(memberId)),
        new Update().set("teamId", newTeamId)
            .set("role", TeamMemberRole.OWN)
            .set("updateTime", TimestampUtils.now())
            .set("updateUser", updateBackUser)
            .set("updateUserOrigin", OperationUserOrigin.BACK.getValue()),
        TeamMember.class);

  }

  @Override
  public void updateThisMonthTargetTeamIdBack(Long oldTeamId, Long newTeamId, Long memberId,
      Long backUpdateUserId) {

    mongoTemplate.updateFirst(Query.query(Criteria.where("billBelong")
            .is(ZonedDateTimeAsiaRiyadhUtils.nowYearMonthToInt())
            .and("history").is(Boolean.FALSE)
            .and("userId").is(memberId)),
        new Update()
            .set("updateTime", TimestampUtils.now())
            .set("teamId", newTeamId)
            .push("remarks",
                "由于团队变更所以修改本期生效中的账单归属. 老团队ID:" + oldTeamId + "  新团队ID:"
                    + newTeamId + "  后台操作id:" + backUpdateUserId),
        TeamMemberTarget.class);

  }
}
