package service

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

	"hyapp/pkg/appcode"
	"hyapp/pkg/logx"
)

const (
	roomListCacheRefreshBatchSize = 500
	roomListCacheRefreshMaxRows   = 200000
)

// RunRoomListCacheRefreshWorker 周期性用 MySQL owner 表重建 Redis 发现页展示读模型。
func (s *Service) RunRoomListCacheRefreshWorker(ctx context.Context, interval time.Duration) {
	if s == nil || s.roomListCache == nil || interval <= 0 {
		return
	}
	s.refreshRoomListCacheOnce(ctx)
	ticker := time.NewTicker(interval)
	defer ticker.Stop()
	for {
		select {
		case <-ctx.Done():
			return
		case <-ticker.C:
			s.refreshRoomListCacheOnce(ctx)
		}
	}
}

func (s *Service) refreshRoomListCacheOnce(ctx context.Context) {
	if err := s.RefreshRoomListCache(ctx); err != nil {
		logx.Warn(ctx, "room_list_cache_refresh_failed", slog.String("error", err.Error()))
	}
}

// RefreshRoomListCache 从 MySQL 扫描 active 房间和有效置顶后按 App 重建缓存。
func (s *Service) RefreshRoomListCache(ctx context.Context) error {
	if s == nil || s.repository == nil || s.roomListCache == nil {
		return nil
	}
	nowMS := s.clock.Now().UTC().UnixMilli()
	entriesByApp := make(map[string][]RoomListEntry)
	pinsByApp := make(map[string][]RoomListPinEntry)
	apps := make(map[string]struct{})

	var afterAppCode string
	var afterRoomID string
	scannedEntries := 0
	for {
		entries, err := s.repository.ListRoomListCacheEntries(ctx, RoomListCacheEntryScanQuery{
			AfterAppCode: afterAppCode,
			AfterRoomID:  afterRoomID,
			Limit:        roomListCacheRefreshBatchSize,
		})
		if err != nil {
			return err
		}
		if len(entries) == 0 {
			break
		}
		for _, entry := range entries {
			app := appcode.Normalize(entry.AppCode)
			if app == "" {
				app = appcode.Default
			}
			entry.AppCode = app
			entriesByApp[app] = append(entriesByApp[app], entry)
			apps[app] = struct{}{}
			afterAppCode = entry.AppCode
			afterRoomID = entry.RoomID
		}
		scannedEntries += len(entries)
		if scannedEntries > roomListCacheRefreshMaxRows {
			// 刷新异常放大时不能用半截数据标记 ready；保留旧缓存或 MySQL 兜底更安全。
			return fmt.Errorf("room list cache entry refresh exceeded %d rows", roomListCacheRefreshMaxRows)
		}
		if len(entries) < roomListCacheRefreshBatchSize {
			break
		}
	}

	var afterPinID int64
	scannedPins := 0
	for {
		pins, err := s.repository.ListRoomListCachePins(ctx, RoomListPinScanQuery{
			AfterID: afterPinID,
			Limit:   roomListCacheRefreshBatchSize,
			NowMS:   nowMS,
		})
		if err != nil {
			return err
		}
		if len(pins) == 0 {
			break
		}
		for _, pin := range pins {
			app := appcode.Normalize(pin.AppCode)
			if app == "" {
				app = appcode.Default
			}
			pin.AppCode = app
			pinsByApp[app] = append(pinsByApp[app], pin)
			apps[app] = struct{}{}
			afterPinID = pin.ID
		}
		scannedPins += len(pins)
		if scannedPins > roomListCacheRefreshMaxRows {
			return fmt.Errorf("room list cache pin refresh exceeded %d rows", roomListCacheRefreshMaxRows)
		}
		if len(pins) < roomListCacheRefreshBatchSize {
			break
		}
	}

	for app := range apps {
		if err := s.roomListCache.RebuildRoomList(ctx, app, entriesByApp[app], pinsByApp[app], nowMS); err != nil {
			return err
		}
	}
	logx.Info(ctx, "room_list_cache_refresh_completed", slog.Int("app_count", len(apps)), slog.Int("entry_count", scannedEntries), slog.Int("pin_count", scannedPins))
	return nil
}
