package service

import (
	"context"
	"fmt"
	"strings"
	"time"

	"google.golang.org/protobuf/proto"
	roomeventsv1 "hyapp.local/api/proto/events/room/v1"
	"hyapp/pkg/appcode"
)

const (
	// SetUserBlockedByStrRoomId 官方限频为 20 QPS；全进程每 50ms 最多发起一次。
	rtcMuteProjectionInterval = 50 * time.Millisecond
	// 一次入口最多追赶三个并发状态版本；持续颠簸时返回可重试错误，
	// 交给新命令的 post-commit 调用或 durable outbox 继续收敛，不无界占用 worker。
	rtcMuteProjectionMaxAttempts = 3
	// REST client 单次默认上限为 5s；总预算只多留 1s 给门闩、限速和快照复核。
	rtcMuteProjectionTimeout = 6 * time.Second
)

type rtcMuteProjectionKey struct {
	appCode string
	roomID  string
	userID  int64
}

// rtcMuteProjectionGate 用 channel 而不是 sync.Mutex，让等待同一用户投影的请求
// 能遵守 context deadline；refs 由 Service.rtcMuteGateMu 保护，最后一个使用者退出即删除门闩。
type rtcMuteProjectionGate struct {
	token chan struct{}
	refs  int
}

// syncRTCUserMuteFromOutbox 把持久化的房间禁言事实补偿到腾讯 RTC 媒体平面。
// 重试时必须读取 Room Cell 的最新禁言集合，而不能直接重放旧事件里的 muted；否则旧 mute
// 在 newer unmute 之后重试会重新关掉用户音频，形成跨系统状态回退。
func (s *Service) syncRTCUserMuteFromOutbox(ctx context.Context, envelope *roomeventsv1.EventEnvelope) error {
	if s == nil || s.rtcUserAudioBlocker == nil || envelope == nil || envelope.GetEventType() != "RoomUserMuted" {
		return nil
	}

	var event roomeventsv1.RoomUserMuted
	if err := proto.Unmarshal(envelope.GetBody(), &event); err != nil {
		return fmt.Errorf("decode rtc mute outbox event: %w", err)
	}
	if event.GetTargetUserId() <= 0 || envelope.GetRoomId() == "" {
		return fmt.Errorf("rtc mute outbox event is incomplete")
	}

	return s.reconcileRTCUserAudio(ctx, envelope.GetRoomId(), event.GetTargetUserId(), false)
}

// reconcileRTCUserAudio 只把调用时的最新 Room Cell 状态投影到 RTC，从不信任历史
// command result 或 outbox payload 中的 muted 快照。onlyIfBlocked 只用于 audio_started
// webhook：用户本来未被禁言时无需多发一次 unmute，但如果本轮已经 block 后
// 又观测到新 unmute，仍必须追赶并发出 unblock，否则会留下本轮自己的旧副作用。
func (s *Service) reconcileRTCUserAudio(parent context.Context, roomID string, userID int64, onlyIfBlocked bool) error {
	if s == nil || s.rtcUserAudioBlocker == nil {
		return nil
	}
	roomID = strings.TrimSpace(roomID)
	if roomID == "" || userID <= 0 {
		return fmt.Errorf("rtc mute reconcile target is incomplete")
	}

	ctx, cancel := context.WithTimeout(parent, rtcMuteProjectionTimeout)
	defer cancel()
	release, err := s.acquireRTCMuteProjection(ctx, rtcMuteProjectionKey{
		appCode: appcode.FromContext(ctx),
		roomID:  roomID,
		userID:  userID,
	})
	if err != nil {
		return err
	}
	defer release()

	projected := false
	for attempt := 1; attempt <= rtcMuteProjectionMaxAttempts; attempt++ {
		before, err := s.currentSnapshot(ctx, roomID)
		if err != nil {
			return err
		}
		if before == nil {
			// 房间已被物理清理时 RTC 连接也不再存在，历史禁言投影可直接收敛。
			return nil
		}
		desiredBlocked := containsID(before.GetMuteUserIds(), userID)
		if onlyIfBlocked && !projected && !desiredBlocked {
			return nil
		}
		if err := s.waitRTCMuteProjectionRate(ctx); err != nil {
			return err
		}
		if err := s.rtcUserAudioBlocker.SetUserAudioBlockedByStrRoomID(ctx, roomID, userID, desiredBlocked); err != nil {
			return err
		}
		projected = true

		after, err := s.currentSnapshot(ctx, roomID)
		if err != nil {
			return err
		}
		if after == nil || containsID(after.GetMuteUserIds(), userID) == desiredBlocked {
			return nil
		}
		// 状态在云 API 请求期间变化；保持同一 keyed gate 并重读最新快照，
		// 让本轮亲自纠正可能已落地的旧云端状态。
	}
	return fmt.Errorf("rtc mute state kept changing during %d reconcile attempts", rtcMuteProjectionMaxAttempts)
}

func (s *Service) acquireRTCMuteProjection(ctx context.Context, key rtcMuteProjectionKey) (func(), error) {
	s.rtcMuteGateMu.Lock()
	if s.rtcMuteGates == nil {
		s.rtcMuteGates = make(map[rtcMuteProjectionKey]*rtcMuteProjectionGate)
	}
	gate := s.rtcMuteGates[key]
	if gate == nil {
		gate = &rtcMuteProjectionGate{token: make(chan struct{}, 1)}
		gate.token <- struct{}{}
		s.rtcMuteGates[key] = gate
	}
	gate.refs++
	s.rtcMuteGateMu.Unlock()

	select {
	case <-ctx.Done():
		s.releaseRTCMuteProjectionRef(key, gate)
		return nil, ctx.Err()
	case <-gate.token:
		return func() {
			gate.token <- struct{}{}
			s.releaseRTCMuteProjectionRef(key, gate)
		}, nil
	}
}

func (s *Service) releaseRTCMuteProjectionRef(key rtcMuteProjectionKey, gate *rtcMuteProjectionGate) {
	s.rtcMuteGateMu.Lock()
	defer s.rtcMuteGateMu.Unlock()
	gate.refs--
	if gate.refs == 0 && s.rtcMuteGates[key] == gate {
		delete(s.rtcMuteGates, key)
	}
}

func (s *Service) waitRTCMuteProjectionRate(ctx context.Context) error {
	s.rtcMuteRateMu.Lock()
	now := time.Now()
	slot := now
	if s.rtcMuteNextCall.After(slot) {
		slot = s.rtcMuteNextCall
	}
	s.rtcMuteNextCall = slot.Add(rtcMuteProjectionInterval)
	s.rtcMuteRateMu.Unlock()

	wait := time.Until(slot)
	if wait <= 0 {
		return nil
	}
	timer := time.NewTimer(wait)
	defer timer.Stop()
	select {
	case <-ctx.Done():
		return ctx.Err()
	case <-timer.C:
		return nil
	}
}
