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 TestRegionalPinOrdersPublicRoomList(t *testing.T) {
	ctx := context.Background()
	repository := mysqltest.NewRepository(t)
	svc := roomservice.New(roomservice.Config{
		NodeID:         "node-pin-test",
		LeaseTTL:       10 * time.Second,
		RankLimit:      20,
		SnapshotEveryN: 1,
	}, router.NewMemoryDirectory(), repository, followTestWallet{}, integration.NewNoopRoomEventPublisher(), integration.NewNoopOutboxPublisher())

	createPinnedListRoom(t, ctx, svc, "room-pin-low", "pin-low", 3001, 9001)
	createPinnedListRoom(t, ctx, svc, "room-hot-high", "hot-high", 3002, 9001)
	createPinnedListRoom(t, ctx, svc, "room-hot-mid", "hot-mid", 3003, 9001)
	repository.SetRoomSortScore("room-pin-low", 10)
	repository.SetRoomSortScore("room-hot-high", 300)
	repository.SetRoomSortScore("room-hot-mid", 200)
	repository.PinRoom("room-pin-low", 9001, 99, time.Now().UTC().Add(24*time.Hour).UnixMilli())

	firstPage, err := svc.ListRooms(ctx, &roomv1.ListRoomsRequest{
		Meta:            &roomv1.RequestMeta{AppCode: appcode.Default},
		ViewerUserId:    4001,
		VisibleRegionId: 9001,
		Tab:             "hot",
		Limit:           1,
	})
	if err != nil {
		t.Fatalf("list pinned first page failed: %v", err)
	}
	if len(firstPage.GetRooms()) != 1 || firstPage.GetRooms()[0].GetRoomId() != "room-pin-low" {
		t.Fatalf("regional pin must be first even with lower hot score: %+v", firstPage.GetRooms())
	}
	if firstPage.GetNextCursor() == "" {
		t.Fatalf("first page should return cursor")
	}

	secondPage, err := svc.ListRooms(ctx, &roomv1.ListRoomsRequest{
		Meta:            &roomv1.RequestMeta{AppCode: appcode.Default},
		ViewerUserId:    4001,
		VisibleRegionId: 9001,
		Tab:             "hot",
		Limit:           2,
		Cursor:          firstPage.GetNextCursor(),
	})
	if err != nil {
		t.Fatalf("list pinned second page failed: %v", err)
	}
	if got := roomIDs(secondPage.GetRooms()); len(got) != 2 || got[0] != "room-hot-high" || got[1] != "room-hot-mid" {
		t.Fatalf("second page should continue with regular hot order, got %+v", got)
	}
}

func TestPinnedRoomListOrdersGlobalRegionLocalThenOtherForHot(t *testing.T) {
	ctx := context.Background()
	repository := mysqltest.NewRepository(t)
	svc := roomservice.New(roomservice.Config{
		NodeID:         "node-pin-hot-test",
		LeaseTTL:       10 * time.Second,
		RankLimit:      20,
		SnapshotEveryN: 1,
	}, router.NewMemoryDirectory(), repository, followTestWallet{}, integration.NewNoopRoomEventPublisher(), integration.NewNoopOutboxPublisher())

	createPinnedListRoom(t, ctx, svc, "room-global-pin", "global-pin", 3101, 9002)
	createPinnedListRoom(t, ctx, svc, "room-region-pin", "region-pin", 3102, 9001)
	createPinnedListRoom(t, ctx, svc, "room-local-online", "local-online", 3103, 9001)
	createPinnedListRoom(t, ctx, svc, "room-other-online", "other-online", 3104, 9002)
	createPinnedListRoom(t, ctx, svc, "room-local-empty", "local-empty", 3105, 9001)
	createPinnedListRoom(t, ctx, svc, "room-other-empty", "other-empty", 3106, 9002)
	repository.SetRoomSortScore("room-global-pin", 1)
	repository.SetRoomSortScore("room-region-pin", 2)
	repository.SetRoomSortScore("room-local-online", 100)
	repository.SetRoomSortScore("room-other-online", 10000)
	repository.SetRoomSortScore("room-local-empty", 100000)
	repository.SetRoomSortScore("room-other-empty", 200000)
	repository.SetRoomPresence("room-local-online", 1, 0)
	repository.SetRoomPresence("room-other-online", 1, 0)
	repository.SetRoomPresence("room-local-empty", 0, 0)
	repository.SetRoomPresence("room-other-empty", 0, 0)
	expiresAtMS := time.Now().UTC().Add(24 * time.Hour).UnixMilli()
	repository.PinRoomWithType("room-global-pin", "global", 0, 1, expiresAtMS)
	repository.PinRoom("room-region-pin", 9001, 99, expiresAtMS)

	page, err := svc.ListRooms(ctx, &roomv1.ListRoomsRequest{
		Meta:            &roomv1.RequestMeta{AppCode: appcode.Default},
		ViewerUserId:    4001,
		VisibleRegionId: 9001,
		Tab:             "hot",
		Limit:           6,
	})
	if err != nil {
		t.Fatalf("list hot rooms failed: %v", err)
	}
	if got := roomIDs(page.GetRooms()); len(got) != 6 || got[0] != "room-global-pin" || got[1] != "room-region-pin" || got[2] != "room-local-online" || got[3] != "room-other-online" || got[4] != "room-local-empty" || got[5] != "room-other-empty" {
		t.Fatalf("hot order mismatch: %+v", got)
	}
}

func TestPinnedRoomListOrdersGlobalRegionLocalThenOtherForNew(t *testing.T) {
	ctx := context.Background()
	repository := mysqltest.NewRepository(t)
	svc := roomservice.New(roomservice.Config{
		NodeID:         "node-pin-new-test",
		LeaseTTL:       10 * time.Second,
		RankLimit:      20,
		SnapshotEveryN: 1,
	}, router.NewMemoryDirectory(), repository, followTestWallet{}, integration.NewNoopRoomEventPublisher(), integration.NewNoopOutboxPublisher())

	createPinnedListRoom(t, ctx, svc, "room-global-new", "global-new", 3201, 9002)
	createPinnedListRoom(t, ctx, svc, "room-region-new", "region-new", 3202, 9001)
	createPinnedListRoom(t, ctx, svc, "room-local-newer", "local-newer", 3203, 9001)
	createPinnedListRoom(t, ctx, svc, "room-local-older", "local-older", 3204, 9001)
	createPinnedListRoom(t, ctx, svc, "room-other-newest", "other-newest", 3205, 9002)
	repository.SetRoomCreatedAt("room-global-new", 10)
	repository.SetRoomCreatedAt("room-region-new", 20)
	repository.SetRoomCreatedAt("room-local-newer", 300)
	repository.SetRoomCreatedAt("room-local-older", 100)
	repository.SetRoomCreatedAt("room-other-newest", 1000)
	expiresAtMS := time.Now().UTC().Add(24 * time.Hour).UnixMilli()
	repository.PinRoomWithType("room-global-new", "global", 0, 1, expiresAtMS)
	repository.PinRoom("room-region-new", 9001, 99, expiresAtMS)

	page, err := svc.ListRooms(ctx, &roomv1.ListRoomsRequest{
		Meta:            &roomv1.RequestMeta{AppCode: appcode.Default},
		ViewerUserId:    4001,
		VisibleRegionId: 9001,
		Tab:             "new",
		Limit:           5,
	})
	if err != nil {
		t.Fatalf("list new rooms failed: %v", err)
	}
	if got := roomIDs(page.GetRooms()); len(got) != 5 || got[0] != "room-global-new" || got[1] != "room-region-new" || got[2] != "room-local-newer" || got[3] != "room-local-older" || got[4] != "room-other-newest" {
		t.Fatalf("new order mismatch: %+v", got)
	}
}

func TestRoomListFiltersByOwnerCountry(t *testing.T) {
	ctx := context.Background()
	repository := mysqltest.NewRepository(t)
	svc := roomservice.New(roomservice.Config{
		NodeID:         "node-country-filter-test",
		LeaseTTL:       10 * time.Second,
		RankLimit:      20,
		SnapshotEveryN: 1,
	}, router.NewMemoryDirectory(), repository, followTestWallet{}, integration.NewNoopRoomEventPublisher(), integration.NewNoopOutboxPublisher())

	createPinnedListRoom(t, ctx, svc, "room-us-low", "us-low", 3301, 9001, "US")
	createPinnedListRoom(t, ctx, svc, "room-ae", "ae", 3302, 9001, "AE")
	createPinnedListRoom(t, ctx, svc, "room-us-high", "us-high", 3303, 9001, "US")
	repository.SetRoomSortScore("room-us-low", 100)
	repository.SetRoomSortScore("room-ae", 200)
	repository.SetRoomSortScore("room-us-high", 300)

	firstPage, err := svc.ListRooms(ctx, &roomv1.ListRoomsRequest{
		Meta:            &roomv1.RequestMeta{AppCode: appcode.Default},
		ViewerUserId:    4001,
		VisibleRegionId: 9001,
		Tab:             "hot",
		Limit:           1,
		CountryCode:     "us",
	})
	if err != nil {
		t.Fatalf("list country filtered first page failed: %v", err)
	}
	if len(firstPage.GetRooms()) != 1 || firstPage.GetRooms()[0].GetRoomId() != "room-us-high" || firstPage.GetRooms()[0].GetCountryCode() != "US" {
		t.Fatalf("country filter must return only matching owner country in hot order: %+v", firstPage.GetRooms())
	}
	if firstPage.GetNextCursor() == "" {
		t.Fatalf("first country page should return cursor")
	}

	if _, err := svc.ListRooms(ctx, &roomv1.ListRoomsRequest{
		Meta:            &roomv1.RequestMeta{AppCode: appcode.Default},
		ViewerUserId:    4001,
		VisibleRegionId: 9001,
		Tab:             "hot",
		Limit:           1,
		CountryCode:     "AE",
		Cursor:          firstPage.GetNextCursor(),
	}); err == nil {
		t.Fatalf("country cursor must not be reusable across country tabs")
	}

	secondPage, err := svc.ListRooms(ctx, &roomv1.ListRoomsRequest{
		Meta:            &roomv1.RequestMeta{AppCode: appcode.Default},
		ViewerUserId:    4001,
		VisibleRegionId: 9001,
		Tab:             "hot",
		Limit:           1,
		CountryCode:     "US",
		Cursor:          firstPage.GetNextCursor(),
	})
	if err != nil {
		t.Fatalf("list country filtered second page failed: %v", err)
	}
	if len(secondPage.GetRooms()) != 1 || secondPage.GetRooms()[0].GetRoomId() != "room-us-low" || secondPage.GetRooms()[0].GetCountryCode() != "US" {
		t.Fatalf("second country page mismatch: %+v", secondPage.GetRooms())
	}
}

func createPinnedListRoom(t *testing.T, ctx context.Context, svc *roomservice.Service, roomID string, shortID string, ownerID int64, regionID int64, countryCodes ...string) {
	t.Helper()

	ownerCountryCode := ""
	if len(countryCodes) > 0 {
		ownerCountryCode = countryCodes[0]
	}
	if _, err := svc.CreateRoom(ctx, &roomv1.CreateRoomRequest{
		Meta:             roomservice.NewRequestMeta(roomID, ownerID),
		SeatCount:        10,
		Mode:             "voice",
		VisibleRegionId:  regionID,
		OwnerCountryCode: ownerCountryCode,
		RoomName:         roomID,
		RoomAvatar:       testRoomCoverURL,
		RoomShortId:      shortID,
	}); err != nil {
		t.Fatalf("create room %s failed: %v", roomID, err)
	}
}

func roomIDs(rooms []*roomv1.RoomListItem) []string {
	ids := make([]string, 0, len(rooms))
	for _, room := range rooms {
		ids = append(ids, room.GetRoomId())
	}
	return ids
}
