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

import com.red.circle.common.business.enums.FemaleBackQueryEnum;
import com.red.circle.common.business.enums.MaleBackQueryEnum;
import com.red.circle.other.infra.database.mongo.dto.query.user.status.OnlineUserListQuery;
import com.red.circle.other.infra.database.mongo.dto.query.user.status.OnlineUserQuery;
import com.red.circle.other.infra.database.mongo.entity.user.status.OnlineUser;
import com.red.circle.other.infra.database.mongo.service.user.status.OnlineUserService;
import com.red.circle.tool.core.date.TimestampUtils;
import com.red.circle.tool.core.text.StringUtils;
import com.red.circle.other.inner.model.cmd.user.OnlineCountQryCmd;
import com.red.circle.other.inner.model.cmd.user.OnlineUserBackQryCmd;
import com.red.circle.other.inner.enums.user.UserOnlineStatusEnum;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
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 org.springframework.util.CollectionUtils;

/**
 * @author pengliang on 2020/8/14
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class OnlineUserServiceImpl implements OnlineUserService {

  private final MongoTemplate mongoTemplate;

  @Override
  public boolean exists(Long sessionId, Long userId) {
    return mongoTemplate.exists(Query.query(Criteria.where("sessionId").is(sessionId)
        .and("id").is(userId)), OnlineUser.class);
  }

  @Override
  public String getSessionId(Long userId) {
    Criteria criteria = Criteria.where("id").is(userId);
    return Optional.ofNullable(mongoTemplate.findOne(Query.query(criteria), OnlineUser.class))
        .map(OnlineUser::getSessionId)
        .map(Objects::toString)
        .orElse(null);
  }

  @Override
  public void updateStatusAndSessionId(Long userId, UserOnlineStatusEnum onlineStatus,
      Long sessionId) {
    mongoTemplate.updateFirst(Query.query(Criteria.where("id").is(userId)),
        new Update().set("status", onlineStatus).set("sessionId", sessionId), OnlineUser.class);
  }

  public void saveOrUpdate(OnlineUser onlineUser) {
    try {
      mongoTemplate.save(onlineUser);
    } catch (Exception e) {
      // ignore
    }
  }


//  @Override
//  public List<Long> getOnlineFemaleAnchor(List<Long> excludeUserIds, Integer size) {
//    Criteria criteria = Criteria.where("anchor").is(Boolean.TRUE)
//        .and("gender").is(0)
//        .and("level").gte(2);
//    if (!CollectionUtils.isEmpty(excludeUserIds)) {
//      criteria.and("id").nin(excludeUserIds);
//    }
//    if (Objects.isNull(size)) {
//      size = 200;
//    }
//    List<OnlineUser> onlineUsers = mongoTemplate
//        .find(Query.query(criteria).with(Sort.by(Sort.Order.desc("level")))
//            .limit(size), OnlineUser.class);
//    if (CollectionUtils.isEmpty(onlineUsers)) {
//      return null;
//    }
//
//    return onlineUsers.stream().map(OnlineUser::getId).collect(Collectors.toList());
//  }
//
//  @Override
//  public List<Long> getLatestNotMeetOnlineMale(List<Long> excludeUserIds, Integer size) {
//    Criteria criteria = Criteria.where("gender").is(1);
//    if (!CollectionUtils.isEmpty(excludeUserIds)) {
//      criteria.and("id").nin(excludeUserIds);
//    }
//    List<OnlineUser> onlineUsers = mongoTemplate
//        .find(Query.query(criteria).with(Sort.by(Sort.Order.desc("createTime")))
//            .limit(size), OnlineUser.class);
//    if (CollectionUtils.isEmpty(onlineUsers)) {
//      return new ArrayList<>(0);
//    }
//    return onlineUsers.stream().map(OnlineUser::getId).collect(Collectors.toList());
//  }
//
//  @Override
//  public Long getLatestNotMeetOnlineFemaleUserId(List<Long> excludeUserIds) {
//    Criteria criteria = Criteria.where("gender").is(0).and("anchor").is(Boolean.TRUE);
//    if (!CollectionUtils.isEmpty(excludeUserIds)) {
//      criteria.and("id").nin(excludeUserIds);
//    }
//    OnlineUser onlineUsers = mongoTemplate
//        .findOne(Query.query(criteria).with(Sort.by(Sort.Order.desc("createTime")))
//            , OnlineUser.class);
//
//    return Optional.ofNullable(onlineUsers)
//        .map(OnlineUser::getId)
//        .orElse(null);
//  }

  @Override
  public List<OnlineUser> getOnline(OnlineUserQuery query) {
    Criteria criteria = appendOnlineQueryCriteria(query);
    Query mongoQuery = Query.query(criteria)
        .with(Sort.by(Sort.Order.desc("createTime"), Sort.Order.desc("weights")));
//        if (Objects.nonNull(query.getLongitude()) && Objects.nonNull(query.getLatitude())) {
//            Point point = new Point(query.getLongitude(), query.getLatitude());
//            Pageable pageable = PageRequest.of(0, query.getSize());
//            NearQuery nearQuery = NearQuery.near(point, Metrics.KILOMETERS).with(pageable).query(mongoQuery);
//            GeoResults<OnlineUser> geoResult = mongoTemplate.geoNear(nearQuery, OnlineUser.class);
//            List<GeoResult<OnlineUser>> geoResults = geoResult.getContent();
//            if (CollectionUtils.isEmpty(geoResults)) {
//                return null;
//            }
//            return geoResults.stream().map(GeoResult::getContent).collect(Collectors.toList());
//        }
    return mongoTemplate.find(mongoQuery.limit(query.getSize()), OnlineUser.class);
  }

  private Criteria appendOnlineQueryCriteria(OnlineUserQuery query) {
    Criteria criteria = new Criteria();
    if (!CollectionUtils.isEmpty(query.getNeSysOrigins())) {
      criteria.and("sysOrigin").nin(query.getNeSysOrigins());
    }
    if (Objects.nonNull(query.getGender())) {
      criteria.and("gender").is(query.getGender());
    }
    if (Objects.isNull(query.getSize())) {
      query.setSize(200);
    }
    return criteria;
  }

  @Override
  public Long count(OnlineCountQryCmd query) {
    Criteria criteria = Criteria.where("id").gt(0);
    if (Objects.nonNull(query)) {
      if (Objects.nonNull(query.getPriority())) {
        criteria.and("priority").is(query.getPriority());
      }
      if (Objects.nonNull(query.getGender())) {
        criteria.and("gender").is(query.getGender());
      }
      if (Objects.nonNull(query.getAnchor())) {
        criteria.and("anchor").is(query.getAnchor());
      }
      if (Objects.equals(query.getVip(), Boolean.TRUE)) {
        criteria.and("maxVipTypeValue").gt(0);
      }
      if (Objects.equals(query.getVip(), Boolean.FALSE)) {
        criteria.and("maxVipTypeValue").lte(0);
      }
      if (Objects.nonNull(query.getCandy())) {
        criteria.and("candy").is(query.getCandy());
      }
      if (StringUtils.isNotBlank(query.getSysOrigin())) {
        criteria.and("sysOrigin").is(query.getSysOrigin());
      }
    }
    return mongoTemplate.count(Query.query(criteria), OnlineUser.class);
  }

  @Override
  public List<OnlineUser> listOnlineUser(OnlineUserListQuery query) {
    Criteria criteria = getListOnlineCriteria(query);
    return mongoTemplate.find(Query.query(criteria)
        , OnlineUser.class);
  }

//  @Override
//  public Map<Long, UserOnlineStatusEnum> mapUserOnlineStatusByUserIds(Set<Long> userIds) {
//    List<OnlineUser> onlineUsers = mongoTemplate
//        .find(Query.query(Criteria.where("id").in(userIds)), OnlineUser.class);
//    if (CollectionUtils.isEmpty(onlineUsers)) {
//      return new HashMap<>(1);
//    }
//    return onlineUsers.stream()
//        .collect(Collectors.toMap(OnlineUser::getId, OnlineUser::getStatus));
//  }

  @Override
  public List<OnlineUser> flowSessionOnlineUser(String sessionId, UserOnlineStatusEnum status,
      Long lastTimingId) {
    Criteria criteria = Criteria.where("sessionId").is(Long.parseLong(sessionId))
        .and("status").is(status);

    if (Objects.nonNull(lastTimingId)) {
      criteria.and("timingId").gt(lastTimingId);
    }
    return mongoTemplate.find(Query.query(criteria)
            .with(Sort.by(Sort.Order.asc("timingId")))
            .limit(200),
        OnlineUser.class);
  }

  @Override
  public List<OnlineUser> listTop(Integer topSize, List<String> inSysOrigins) {
    Criteria criteria = new Criteria();
    if (!CollectionUtils.isEmpty(inSysOrigins)) {
      criteria.and("sysOrigin").in(inSysOrigins);
    }
    return mongoTemplate.find(Query.query(criteria)
        .limit(topSize), OnlineUser.class);
  }

  @Override
  public Long countSessionId(Long sessionId) {
    return mongoTemplate.count(Query.query(Criteria.where("sessionId").is(sessionId)),
        OnlineUser.class);
  }

  @Override
  public void removeByUserId(Long userId) {
    mongoTemplate.remove(Query.query(Criteria.where("id").is(userId)),
        OnlineUser.class);
  }

  private Criteria getListOnlineCriteria(OnlineUserListQuery query) {
    Criteria criteria = Criteria.where("sysOrigin").is(query.getSysOrigin());

    if (!StringUtils.isEmpty(query.getNickname())) {
      criteria.and("userNickname").regex(".*" + query.getNickname() + ".*");
    }
    if (Objects.nonNull(query.getPriority())) {
      criteria.and("priority").is(query.getPriority());
    }
    if (Objects.nonNull(query.getUserId())) {
      criteria.and("id").is(query.getUserId());
    }
    if (Objects.nonNull(query.getLevel())) {
      criteria.and("level").is(query.getLevel());
    }
    if (Objects.isNull(query.getSize())) {
      query.setSize(200);
    }
    if (Objects.nonNull(query.getGender())) {
      criteria.and("gender").is(query.getGender());
    }

    return criteria;
  }


  @Override
  public List<OnlineUser> getBackOnline(OnlineUserBackQryCmd query) {
    Criteria criteria = getBackOnlineCriteria(query);
    return mongoTemplate.find(Query.query(criteria)
        , OnlineUser.class);
  }

  @Override
  public List<OnlineUser> flowLanguageHallTop200(Long roomId, Long lastTimingId) {
    Criteria criteria = Criteria.where("sessionId").is(roomId)
        .and("status").is(UserOnlineStatusEnum.LANGUAGE_HALL);

    if (Objects.nonNull(lastTimingId)) {
      criteria.and("timingId").gt(lastTimingId);
    }

    return mongoTemplate.find(Query.query(criteria)
        .with(Sort.by(Sort.Order.asc("timingId")))
        .limit(200), OnlineUser.class);
  }

  @Override
  public List<OnlineUser> userOnlineByUserIds(Set<Long> userIds) {
    Criteria criteria = Criteria.where("userId").in(userIds)
        .and("status").is(UserOnlineStatusEnum.LANGUAGE_HALL)
        .and("expiredTime").gte(TimestampUtils.now())
            ;
    return mongoTemplate.find(Query.query(criteria)
        .limit(200), OnlineUser.class);
  }

  private Criteria getBackOnlineCriteria(OnlineUserBackQryCmd query) {
    Criteria criteria = Criteria.where("gender").is(query.getGender());

    if (!org.springframework.util.StringUtils.isEmpty(query.getNickname())) {
      criteria.and("userNickname").regex(".*" + query.getNickname() + ".*");
    }
    if (Objects.nonNull(query.getPriority())) {
      criteria.and("priority").is(query.getPriority());
    }
    if (Objects.nonNull(query.getUserId())) {
      criteria.and("userId").is(query.getUserId());
    }
    if (Objects.nonNull(query.getLevel())) {
      criteria.and("level").is(query.getLevel());
    }
    if (Objects.isNull(query.getSize())) {
      query.setSize(200);
    }
    if (Objects.equals(query.getGender(), 0)) {
      if (Objects.equals(query.getFemaleBackQueryEnum(), FemaleBackQueryEnum.ORDINARY)) {
        criteria.and("anchor").is(Boolean.FALSE);
        criteria.and("priority").is(Boolean.FALSE);
      } else if (Objects.equals(query.getFemaleBackQueryEnum(), FemaleBackQueryEnum.PRIORITY)) {
        criteria.and("priority").is(Boolean.TRUE);
      } else if (Objects.equals(query.getFemaleBackQueryEnum(), FemaleBackQueryEnum.ANCHOR)) {
        criteria.and("anchor").is(Boolean.TRUE);
      } else if (Objects
          .equals(query.getFemaleBackQueryEnum(), FemaleBackQueryEnum.PRIORITY_AND_ANCHOR)) {
        criteria.and("anchor").is(Boolean.TRUE);
        criteria.and("priority").is(Boolean.TRUE);
      }
      return criteria;
    }

    if (Objects.equals(query.getGender(), 1)) {
      if (Objects.equals(query.getMaleBackQueryEnum(), MaleBackQueryEnum.ORDINARY)) {
        criteria.and("maxVipTypeValue").is(0);
        criteria.and("candy").is(Boolean.FALSE);
      } else if (Objects.equals(query.getMaleBackQueryEnum(), MaleBackQueryEnum.BUY_VIP)) {
        criteria.and("maxVipTypeValue").gt(0);
      } else if (Objects.equals(query.getMaleBackQueryEnum(), MaleBackQueryEnum.BUY_CANDY)) {
        criteria.and("anchor").is(Boolean.TRUE);
      } else if (Objects
          .equals(query.getMaleBackQueryEnum(), MaleBackQueryEnum.BUY_CANDY_AND_VIP)) {
        criteria.and("maxVipTypeValue").gt(0);
        criteria.and("candy").is(Boolean.TRUE);
      }
    }
    return criteria;
  }

  @Override
  public List<OnlineUser> flowLanguageHallByRoomId(Long roomId) {
    Criteria criteria = Criteria.where("sessionId").is(roomId)
        .and("status").is(UserOnlineStatusEnum.LANGUAGE_HALL);
    return mongoTemplate.find(Query.query(criteria)
        .with(Sort.by(Sort.Order.asc("timingId")))
        .limit(5), OnlineUser.class);
  }


}
