package service

import (
	"sort"
	"testing"
)

func TestRoomListCacheCandidateOrderMatchesPublicHotBuckets(t *testing.T) {
	query := RoomListQuery{Tab: roomListTabHot, ViewerCountryCode: "US"}
	candidates := []roomListCacheCandidate{
		{Entry: roomListCacheTestEntry("other-empty", "AE", 0, 200000, 0), SortValue: 200000},
		{Entry: roomListCacheTestEntry("viewer-online", "US", 1, 100, 0), SortValue: 100},
		{Entry: roomListCachePinnedTestEntry("country-pin", 1, 88, 9000, 2), SortValue: 2},
		{Entry: roomListCacheTestEntry("other-online", "AE", 1, 10000, 0), SortValue: 10000},
		{Entry: roomListCachePinnedTestEntry("region-pin", 0, 99, 10000, 1), SortValue: 1},
		{Entry: roomListCacheTestEntry("viewer-empty", "US", 0, 100000, 0), SortValue: 100000},
	}
	for index := range candidates {
		if !candidates[index].Entry.IsPinned {
			candidates[index].Entry.PinListRank = roomListCacheNormalRank(query, candidates[index].Entry)
		}
	}
	sort.SliceStable(candidates, func(i, j int) bool {
		return roomListCacheCandidateBefore(candidates[i], candidates[j])
	})

	got := make([]string, 0, len(candidates))
	for _, candidate := range candidates {
		got = append(got, candidate.Entry.RoomID)
	}
	want := []string{"region-pin", "country-pin", "viewer-online", "other-online", "viewer-empty", "other-empty"}
	if len(got) != len(want) {
		t.Fatalf("order length mismatch: got=%v want=%v", got, want)
	}
	for index := range want {
		if got[index] != want[index] {
			t.Fatalf("order mismatch: got=%v want=%v", got, want)
		}
	}
}

func TestRoomListCacheCursorContinuesAfterPinnedBoundary(t *testing.T) {
	query := RoomListQuery{
		Tab:                 roomListTabHot,
		CursorRoomID:        "region-pin",
		CursorPinListRank:   0,
		CursorPinWeight:     99,
		CursorPinnedUntilMS: 10000,
		CursorSortScore:     1,
	}
	before := roomListCachePinnedTestEntry("region-pin", 0, 99, 10000, 1)
	after := roomListCachePinnedTestEntry("country-pin", 1, 88, 9000, 2)
	if roomListCacheAfterCursor(query, before, before.SortScore) {
		t.Fatalf("cursor row itself must not be returned again")
	}
	if !roomListCacheAfterCursor(query, after, after.SortScore) {
		t.Fatalf("next pin rank must continue after cursor")
	}
}

func TestRoomListCacheCountryPinOnlyAppliesToSelectedCountry(t *testing.T) {
	query := RoomListQuery{VisibleRegionID: 9001, CountryCode: "AE", ViewerCountryCode: "US", NowMS: 1000}
	pin := RoomListPinEntry{PinType: roomPinTypeCountry, VisibleRegionID: 9001, RoomID: "room-us", Status: "active", PinnedAtMS: 1, ExpiresAtMS: 2000}
	usRoom := roomListCacheTestEntry("room-us", "US", 1, 10, 0)
	aeRoom := roomListCacheTestEntry("room-ae", "AE", 1, 10, 0)
	if roomListCachePinApplies(query, pin, usRoom) {
		t.Fatalf("country pin must not apply to a room outside selected country")
	}
	if !roomListCachePinApplies(query, pin, aeRoom) {
		t.Fatalf("country pin must apply to matching selected country")
	}
}

func roomListCacheTestEntry(roomID string, country string, online int32, sortScore int64, createdAtMS int64) RoomListEntry {
	return RoomListEntry{
		AppCode:          "lalu",
		RoomID:           roomID,
		VisibleRegionID:  9001,
		OwnerCountryCode: country,
		Status:           "active",
		OnlineCount:      online,
		SortScore:        sortScore,
		CreatedAtMS:      createdAtMS,
	}
}

func roomListCachePinnedTestEntry(roomID string, rank int64, weight int64, expiresAtMS int64, sortScore int64) RoomListEntry {
	entry := roomListCacheTestEntry(roomID, "US", 1, sortScore, 0)
	entry.IsPinned = true
	entry.PinListRank = rank
	entry.PinWeight = weight
	entry.PinnedUntilMS = expiresAtMS
	return entry
}
