package service_test

import (
	"context"
	"encoding/json"
	"testing"
	"time"

	roomv1 "hyapp.local/api/proto/room/v1"
	"hyapp/pkg/appcode"
	"hyapp/pkg/usermq"
	"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 TestHandleUserRegionChangedUpdatesOwnerRoomRegionIdempotently(t *testing.T) {
	ctx := appcode.WithContext(context.Background(), appcode.Default)
	repository := mysqltest.NewRepository(t)
	svc := roomservice.New(roomservice.Config{
		NodeID:         "node-user-region-sync-test",
		LeaseTTL:       10 * time.Second,
		RankLimit:      20,
		SnapshotEveryN: 1,
	}, router.NewMemoryDirectory(), repository, followTestWallet{}, integration.NewNoopRoomEventPublisher(), integration.NewNoopOutboxPublisher())

	roomID := "room-user-region-sync"
	ownerID := int64(163212)
	if _, err := svc.CreateRoom(ctx, &roomv1.CreateRoomRequest{
		Meta:             roomservice.NewRequestMeta(roomID, ownerID),
		SeatCount:        10,
		Mode:             "voice",
		VisibleRegionId:  3,
		OwnerCountryCode: "AE",
		RoomName:         "Region Sync Room",
		RoomAvatar:       testRoomCoverURL,
		RoomShortId:      "163212",
	}); err != nil {
		t.Fatalf("create room failed: %v", err)
	}

	payload, err := json.Marshal(map[string]any{
		"user_id":       ownerID,
		"old_country":   "AE",
		"new_country":   "BD",
		"old_region_id": 3,
		"new_region_id": 2,
		"source":        "admin",
		"request_id":    "req-user-region-sync",
		"changed_at_ms": int64(1_700_000_789_000),
	})
	if err != nil {
		t.Fatalf("marshal payload failed: %v", err)
	}
	message := usermq.UserOutboxMessage{
		AppCode:       appcode.Default,
		EventID:       "UserRegionChanged:admin:163212:3:2:1700000789000",
		EventType:     "UserRegionChanged",
		AggregateType: "user",
		AggregateID:   ownerID,
		PayloadJSON:   string(payload),
		OccurredAtMS:  1_700_000_789_000,
	}
	if err := svc.HandleUserOutboxMessage(ctx, message); err != nil {
		t.Fatalf("HandleUserOutboxMessage failed: %v", err)
	}
	if err := svc.HandleUserOutboxMessage(ctx, message); err != nil {
		t.Fatalf("replayed HandleUserOutboxMessage failed: %v", err)
	}

	meta, exists, err := repository.GetRoomMeta(ctx, roomID)
	if err != nil || !exists {
		t.Fatalf("room meta missing after sync: exists=%t err=%v", exists, err)
	}
	if meta.VisibleRegionID != 2 {
		t.Fatalf("rooms visible region mismatch: %+v", meta)
	}
	newRegionEntries, err := repository.ListRoomListEntries(ctx, roomservice.RoomListQuery{VisibleRegionID: 2, Tab: "new", Limit: 10})
	if err != nil {
		t.Fatalf("list new region entries failed: %v", err)
	}
	if len(newRegionEntries) != 1 || newRegionEntries[0].RoomID != roomID || newRegionEntries[0].VisibleRegionID != 2 || newRegionEntries[0].OwnerCountryCode != "BD" {
		t.Fatalf("new region list projection mismatch: %+v", newRegionEntries)
	}
	countryEntries, err := repository.ListRoomListEntries(ctx, roomservice.RoomListQuery{VisibleRegionID: 2, CountryCode: "BD", Tab: "new", Limit: 10})
	if err != nil {
		t.Fatalf("list new country entries failed: %v", err)
	}
	if len(countryEntries) != 1 || countryEntries[0].RoomID != roomID || countryEntries[0].OwnerCountryCode != "BD" {
		t.Fatalf("new country list projection mismatch: %+v", countryEntries)
	}
	oldRegionEntries, err := repository.ListRoomListEntries(ctx, roomservice.RoomListQuery{VisibleRegionID: 3, Tab: "new", Limit: 10})
	if err != nil {
		t.Fatalf("list old region entries failed: %v", err)
	}
	if len(oldRegionEntries) != 0 {
		t.Fatalf("old region list must not contain migrated room: %+v", oldRegionEntries)
	}

	// 同一区域内再次换国家时 visible_region_id 不变，但国家 tab 仍必须从旧国家迁到新国家。
	sameRegionPayload, err := json.Marshal(map[string]any{
		"user_id":       ownerID,
		"old_country":   "BD",
		"new_country":   "PK",
		"old_region_id": 2,
		"new_region_id": 2,
		"source":        "app",
		"request_id":    "req-user-country-sync-same-region",
		"changed_at_ms": int64(1_700_000_790_000),
	})
	if err != nil {
		t.Fatalf("marshal same-region payload failed: %v", err)
	}
	if err := svc.HandleUserOutboxMessage(ctx, usermq.UserOutboxMessage{
		AppCode:       appcode.Default,
		EventID:       "UserRegionChanged:app:163212:BD:PK:2:2:1700000790000",
		EventType:     "UserRegionChanged",
		AggregateType: "user",
		AggregateID:   ownerID,
		PayloadJSON:   string(sameRegionPayload),
		OccurredAtMS:  1_700_000_790_000,
	}); err != nil {
		t.Fatalf("same-region HandleUserOutboxMessage failed: %v", err)
	}
	oldCountryEntries, err := repository.ListRoomListEntries(ctx, roomservice.RoomListQuery{VisibleRegionID: 2, CountryCode: "BD", Tab: "new", Limit: 10})
	if err != nil {
		t.Fatalf("list old country entries failed: %v", err)
	}
	newCountryEntries, err := repository.ListRoomListEntries(ctx, roomservice.RoomListQuery{VisibleRegionID: 2, CountryCode: "PK", Tab: "new", Limit: 10})
	if err != nil {
		t.Fatalf("list same-region new country entries failed: %v", err)
	}
	if len(oldCountryEntries) != 0 || len(newCountryEntries) != 1 || newCountryEntries[0].RoomID != roomID || newCountryEntries[0].OwnerCountryCode != "PK" {
		t.Fatalf("same-region country projection mismatch: old=%+v new=%+v", oldCountryEntries, newCountryEntries)
	}
}
