package service

import (
	"context"
	"log/slog"
	"time"

	"hyapp/pkg/appcode"
	"hyapp/pkg/logx"
	"hyapp/services/room-service/internal/room/outbox"
)

const (
	// robotDisplayPublishTimeout 是机器人展示直发 IM 的单条上限；超时或失败只记日志，也绝不能补写数据库或 MQ。
	robotDisplayPublishTimeout = 3 * time.Second
	// robotDisplayMaxConcurrentPublishes 给高频机器人展示设置进程级硬上限，避免腾讯 IM 变慢时无界堆积 goroutine。
	robotDisplayMaxConcurrentPublishes = 32
)

func (s *Service) publishRobotDisplayRecordsBestEffort(ctx context.Context, records []outbox.Record) {
	if s == nil || len(records) == 0 {
		return
	}
	if s.robotDisplayPublisher == nil {
		// 缺少腾讯 IM publisher 时只把整批纯展示事件视为 best-effort 投递失败，不创建持久化补偿事实。
		logx.Warn(ctx, "robot_display_im_publish_skipped",
			slog.String("node_id", s.nodeID),
			slog.Int("record_count", len(records)),
			slog.String("reason", "publisher_not_configured"),
		)
		return
	}
	scopedApp := appcode.FromContext(ctx)
	copied := append([]outbox.Record(nil), records...)
	for index := range copied {
		copied[index].AppCode = appcode.Normalize(firstNonEmpty(copied[index].AppCode, scopedApp))
		if copied[index].Envelope != nil {
			copied[index].Envelope.AppCode = copied[index].AppCode
		}
	}
	sortOutboxRecords(copied)
	s.robotDisplayPublishOnce.Do(func() {
		s.robotDisplayPublishSlots = make(chan struct{}, robotDisplayMaxConcurrentPublishes)
	})
	select {
	case s.robotDisplayPublishSlots <- struct{}{}:
	case <-ctx.Done():
		return
	default:
		// 机器人消息没有业务事实语义；发布槽位耗尽时只丢当前展示，不能排队到 DB、MQ 或无界内存。
		logx.Warn(ctx, "robot_display_im_publish_skipped",
			slog.String("node_id", s.nodeID),
			slog.Int("record_count", len(copied)),
			slog.String("reason", "publisher_busy"),
		)
		return
	}

	go func(records []outbox.Record) {
		defer func() { <-s.robotDisplayPublishSlots }()
		for _, record := range records {
			if record.Envelope == nil {
				logx.Warn(ctx, "robot_display_im_publish_skipped",
					slog.String("node_id", s.nodeID),
					slog.String("event_id", record.EventID),
					slog.String("event_type", record.EventType),
					slog.String("room_id", record.RoomID),
					slog.String("reason", "nil_envelope"),
				)
				continue
			}
			publishCtx, cancel := context.WithTimeout(appcode.WithContext(context.Background(), record.AppCode), robotDisplayPublishTimeout)
			err := s.robotDisplayPublisher.PublishOutboxEvent(publishCtx, record.Envelope)
			cancel()
			if err != nil {
				// 机器人展示事件允许极小概率丢失；这里只有日志且不补偿，房间快照只保证真实房态不被这次失败污染。
				logx.Warn(ctx, "robot_display_im_publish_failed",
					slog.String("node_id", s.nodeID),
					slog.String("event_id", record.EventID),
					slog.String("event_type", record.EventType),
					slog.String("room_id", record.RoomID),
					slog.String("error", trimOutboxError(err.Error())),
				)
			}
		}
	}(copied)
}
