package service_test

import (
	"context"
	"testing"
	"time"

	roomv1 "hyapp.local/api/proto/room/v1"
	"hyapp/pkg/appcode"
	"hyapp/services/room-service/internal/integration"
	roomservice "hyapp/services/room-service/internal/room/service"
	"hyapp/services/room-service/internal/router"
	"hyapp/services/room-service/internal/testutil/mysqltest"
)

func TestListRoomsUsesCacheWhenEligible(t *testing.T) {
	ctx := context.Background()
	repository := mysqltest.NewRepository(t)
	cache := &fakeRoomListCache{entries: []roomservice.RoomListEntry{{
		AppCode:         appcode.Default,
		RoomID:          "room-cache-hit",
		RoomShortID:     "cache-hit",
		VisibleRegionID: 7001,
		OwnerUserID:     6001,
		Title:           "cache",
		Mode:            "voice",
		Status:          "active",
		SortScore:       99,
	}}}
	svc := roomservice.New(roomservice.Config{
		NodeID:         "node-list-cache-hit-test",
		LeaseTTL:       10 * time.Second,
		RankLimit:      20,
		SnapshotEveryN: 1,
		RoomListCache:  cache,
	}, router.NewMemoryDirectory(), repository, followTestWallet{}, integration.NewNoopRoomEventPublisher(), integration.NewNoopOutboxPublisher())

	resp, err := svc.ListRooms(ctx, &roomv1.ListRoomsRequest{
		Meta:            &roomv1.RequestMeta{AppCode: appcode.Default},
		ViewerUserId:    4001,
		VisibleRegionId: 7001,
		Tab:             "hot",
		Limit:           10,
	})
	if err != nil {
		t.Fatalf("list rooms failed: %v", err)
	}
	if cache.listCalls != 1 {
		t.Fatalf("eligible public list must query cache once, calls=%d", cache.listCalls)
	}
	if got := roomIDs(resp.GetRooms()); len(got) != 1 || got[0] != "room-cache-hit" {
		t.Fatalf("list response must come from cache, got=%v", got)
	}
}

func TestListRoomsFallsBackToMySQLWhenCacheMisses(t *testing.T) {
	ctx := context.Background()
	repository := mysqltest.NewRepository(t)
	cache := &fakeRoomListCache{ok: false}
	svc := roomservice.New(roomservice.Config{
		NodeID:         "node-list-cache-fallback-test",
		LeaseTTL:       10 * time.Second,
		RankLimit:      20,
		SnapshotEveryN: 1,
		RoomListCache:  cache,
	}, router.NewMemoryDirectory(), repository, followTestWallet{}, integration.NewNoopRoomEventPublisher(), integration.NewNoopOutboxPublisher())
	createPinnedListRoom(t, ctx, svc, "room-mysql-fallback", "mysql-fallback", 6101, 7101, "US")

	resp, err := svc.ListRooms(ctx, &roomv1.ListRoomsRequest{
		Meta:            &roomv1.RequestMeta{AppCode: appcode.Default},
		ViewerUserId:    4001,
		VisibleRegionId: 7101,
		Tab:             "new",
		Limit:           10,
	})
	if err != nil {
		t.Fatalf("list rooms fallback failed: %v", err)
	}
	if cache.listCalls == 0 {
		t.Fatalf("eligible public list should try cache before MySQL")
	}
	if got := roomIDs(resp.GetRooms()); len(got) != 1 || got[0] != "room-mysql-fallback" {
		t.Fatalf("fallback response must come from MySQL, got=%v", got)
	}
}

type fakeRoomListCache struct {
	entries   []roomservice.RoomListEntry
	ok        bool
	listCalls int
}

func (c *fakeRoomListCache) ListRoomListEntries(context.Context, roomservice.RoomListQuery) ([]roomservice.RoomListEntry, bool, error) {
	c.listCalls++
	if c.ok || c.entries != nil {
		return c.entries, true, nil
	}
	return nil, false, nil
}

func (c *fakeRoomListCache) UpsertRoomListEntry(context.Context, roomservice.RoomListEntry) error {
	return nil
}

func (c *fakeRoomListCache) DeleteRoomListEntry(context.Context, string, string) error {
	return nil
}

func (c *fakeRoomListCache) UpsertRoomPin(context.Context, roomservice.RoomListPinEntry) error {
	return nil
}

func (c *fakeRoomListCache) RebuildRoomList(context.Context, string, []roomservice.RoomListEntry, []roomservice.RoomListPinEntry, int64) error {
	return nil
}
