package service

import (
	"context"
	"time"

	"hyapp/pkg/appcode"
	"hyapp/pkg/idgen"
	"hyapp/services/room-service/internal/room/command"
	"hyapp/services/room-service/internal/room/state"
)

const micDownReasonPublishTimeout = "publish_timeout"

// RunMicPublishTimeoutWorker 周期性释放超过 deadline 仍未确认 RTC 发流的麦位。
func (s *Service) RunMicPublishTimeoutWorker(ctx context.Context, interval time.Duration) {
	if interval <= 0 || s.micPublishTimeout <= 0 {
		// 非正数表示显式关闭，便于测试或临时止血。
		return
	}

	ticker := time.NewTicker(interval)
	defer ticker.Stop()

	for {
		select {
		case <-ctx.Done():
			return
		case <-ticker.C:
			_ = s.SweepMicPublishTimeouts(ctx)
		}
	}
}

// SweepMicPublishTimeouts 扫描本节点已装载 Room Cell，自动下掉未确认发流的 pending_publish 麦位。
func (s *Service) SweepMicPublishTimeouts(ctx context.Context) error {
	now := s.clock.Now()
	nowMs := now.UnixMilli()
	for _, roomRef := range s.loadedRoomRefs() {
		// 后台扫描只处理本进程已装载的 Cell，未装载房间由下一次用户命令恢复后再处理。
		roomCtx := appcode.WithContext(ctx, roomRef.AppCode)
		lease, owned, err := s.loadedRoomLease(roomCtx, roomRef, now)
		if err != nil {
			return err
		}
		if !owned {
			// lease 已被其他节点持有时，旧 owner 的内存状态不能再驱动下麦命令。
			continue
		}

		roomCell := s.loadCellForLease(roomCtx, roomRef.RoomID, lease)
		if roomCell == nil {
			// loadedRoomRefs 和 loadCell 之间允许并发变化，缺失时跳过即可。
			continue
		}

		// 先在命令边界读取快照，再根据快照筛出候选超时麦位。
		currentState, _, err := roomCell.Snapshot(roomCtx)
		if err != nil {
			return err
		}
		owned, err = s.verifyLoadedRoomLease(roomCtx, roomRef, lease, s.clock.Now())
		if err != nil {
			return err
		}
		if !owned {
			continue
		}

		for _, seat := range pendingPublishTimedOutSeats(currentState, nowMs) {
			// 每个候选执行前再次校验 lease，避免长批次期间 owner 已切换还继续写旧状态。
			owned, err = s.verifyLoadedRoomLease(roomCtx, roomRef, lease, s.clock.Now())
			if err != nil {
				return err
			}
			if !owned {
				break
			}

			// 通过标准 MicDown 命令链路释放麦位，复用 command log、outbox、快照和幂等保护。
			cmd := command.MicDown{
				Base: command.Base{
					RequestID:     idgen.New("req_mic_publish_timeout"),
					CommandID:     idgen.New("cmd_mic_publish_timeout"),
					ActorID:       seat.UserID,
					Room:          roomRef.RoomID,
					GatewayNodeID: s.nodeID,
					SessionID:     "mic-publish-timeout",
					SentAtMS:      nowMs,
				},
				TargetUserID: seat.UserID,
				MicSessionID: seat.MicSessionID,
				Reason:       micDownReasonPublishTimeout,
			}
			if _, err := s.micDown(roomCtx, cmd); err != nil {
				return err
			}
		}
	}

	return nil
}

// pendingPublishTimedOutSeats 返回已经超过确认 deadline 的 pending_publish 麦位快照。
func pendingPublishTimedOutSeats(current *state.RoomState, nowMs int64) []state.MicSeat {
	if current == nil {
		return nil
	}

	seats := make([]state.MicSeat, 0)
	for _, seat := range current.MicSeats {
		if seat.UserID <= 0 || seat.MicSessionID == "" {
			// 空麦位或没有 mic_session 的历史状态不属于自动下麦目标。
			continue
		}
		if seat.PublishState == state.MicPublishPending && seat.PublishDeadlineMS > 0 && seat.PublishDeadlineMS <= nowMs {
			// 返回值拷贝，后续 MicDown 还会用 mic_session_id 在当前 Cell 状态中二次校验。
			seats = append(seats, seat)
		}
	}

	return seats
}
