package com.red.circle.live.app.scheduler;

import com.red.circle.component.redis.service.RedisService;
import com.red.circle.external.inner.model.dto.AgoraChannelStateDTO;
import com.red.circle.live.app.service.AgoraMicMissingConfirmService;
import com.red.circle.live.app.service.AgoraMicStateCleanupService;
import com.red.circle.live.app.service.AgoraRoomStateService;
import com.red.circle.live.domain.live.LiveMicrophone;
import com.red.circle.live.infra.database.cache.service.LiveMicCacheService;
import com.red.circle.tool.core.collection.CollectionUtils;
import com.red.circle.tool.core.parse.DataTypeUtils;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

/**
 * 声网真实状态和业务麦位状态补偿对账.
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class AgoraMicStateReconcileTask {

  private static final String LOCK_KEY = "agora:mic:reconcile:lock";
  private static final String SEAT_KEY_PREFIX = "LiveMick:SEAT:";

  private final RedisService redisService;
  private final RedisTemplate<String, Object> redisTemplate;
  private final LiveMicCacheService liveMicCacheService;
  private final AgoraRoomStateService agoraRoomStateService;
  private final AgoraMicMissingConfirmService agoraMicMissingConfirmService;
  private final AgoraMicStateCleanupService agoraMicStateCleanupService;

  @Scheduled(fixedDelayString = "${red-circle.live.agora-mic-reconcile-delay-ms:30000}")
  public void reconcileAgoraMicState() {
    if (!redisService.setIfAbsent(LOCK_KEY, 1, 25, TimeUnit.SECONDS)) {
      return;
    }
    try {
      for (Long roomId : scanSeatRoomIds()) {
        reconcileRoom(roomId);
      }
    } finally {
      redisService.delete(LOCK_KEY);
    }
  }

  private void reconcileRoom(Long roomId) {
    if (Objects.isNull(roomId)) {
      return;
    }

    AgoraChannelStateDTO state = agoraRoomStateService.getChannelState(roomId);
    if (!state.querySuccess()) {
      log.warn("声网麦位对账跳过，查询失败 roomId={}, response={}", roomId, state.getRawResponse());
      return;
    }

    List<LiveMicrophone> microphones = liveMicCacheService.listLiveMicrophone(roomId);
    if (CollectionUtils.isEmpty(microphones)) {
      return;
    }

    if (!state.channelExist()) {
      for (LiveMicrophone microphone : microphones) {
        cleanupMissingMicrophoneUser(roomId, microphone, state, "reconcile agora channel not exist");
      }
      return;
    }

    Set<Long> channelUids = new HashSet<>();
    if (CollectionUtils.isNotEmpty(state.getBroadcasters())) {
      channelUids.addAll(state.getBroadcasters());
    }
    if (CollectionUtils.isNotEmpty(state.getAudience())) {
      channelUids.addAll(state.getAudience());
    }
    for (LiveMicrophone microphone : microphones) {
      if (Objects.isNull(microphone) || Objects.isNull(microphone.getUser())) {
        continue;
      }
      Long agoraUid = DataTypeUtils.toLong(microphone.getUser().getAccount());
      if (Objects.isNull(agoraUid) || !channelUids.contains(agoraUid)) {
        cleanupMissingMicrophoneUser(roomId, microphone, state, "reconcile agora user missing");
      } else {
        agoraMicMissingConfirmService.clearMissing(roomId, microphone.getUser().getId());
      }
    }
  }

  private void cleanupMissingMicrophoneUser(Long roomId,
      LiveMicrophone microphone,
      AgoraChannelStateDTO state,
      String reason) {
    if (Objects.isNull(microphone)
        || Objects.isNull(microphone.getUser())
        || Objects.isNull(microphone.getUser().getId())) {
      return;
    }
    Long userId = microphone.getUser().getId();
    if (!agoraMicMissingConfirmService.markMissingAndShouldCleanup(roomId, userId,
        reason + ", channelExist=" + state.channelExist())) {
      return;
    }
    agoraMicStateCleanupService.cleanupUser(roomId, userId, reason);
  }

  private Set<Long> scanSeatRoomIds() {
    Set<Long> roomIds = new HashSet<>();
    ScanOptions options = ScanOptions.scanOptions().match(SEAT_KEY_PREFIX + "*").count(200).build();
    try (var cursor = redisTemplate.scan(options)) {
      while (cursor.hasNext()) {
        Object key = cursor.next();
        Long roomId = parseRoomId(Objects.toString(key, null));
        if (Objects.nonNull(roomId)) {
          roomIds.add(roomId);
        }
      }
    } catch (Exception e) {
      log.error("扫描声网麦位对账 Redis key 失败", e);
    }
    return roomIds;
  }

  private Long parseRoomId(String key) {
    if (Objects.isNull(key) || !key.startsWith(SEAT_KEY_PREFIX)) {
      return null;
    }
    return DataTypeUtils.toLong(key.substring(SEAT_KEY_PREFIX.length()));
  }
}
