package service_test

import (
	"context"
	"testing"
	"time"

	roomv1 "hyapp.local/api/proto/room/v1"
	walletv1 "hyapp.local/api/proto/wallet/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"
)

type followTestWallet struct{}

func (followTestWallet) DebitGift(context.Context, *walletv1.DebitGiftRequest) (*walletv1.DebitGiftResponse, error) {
	return &walletv1.DebitGiftResponse{BillingReceiptId: "receipt-follow-test"}, nil
}

func (followTestWallet) BatchDebitGift(context.Context, *walletv1.BatchDebitGiftRequest) (*walletv1.BatchDebitGiftResponse, error) {
	return &walletv1.BatchDebitGiftResponse{Aggregate: &walletv1.DebitGiftResponse{BillingReceiptId: "receipt-follow-test"}}, nil
}

func (followTestWallet) DebitRobotGift(context.Context, *walletv1.DebitRobotGiftRequest) (*walletv1.DebitGiftResponse, error) {
	return &walletv1.DebitGiftResponse{BillingReceiptId: "receipt-robot-follow-test"}, nil
}

func (followTestWallet) GrantResourceGroup(context.Context, *walletv1.GrantResourceGroupRequest) (*walletv1.ResourceGrantResponse, error) {
	return &walletv1.ResourceGrantResponse{Grant: &walletv1.ResourceGrant{GrantId: "grant-follow-test"}}, nil
}

func (followTestWallet) GetMyVip(_ context.Context, req *walletv1.GetMyVipRequest) (*walletv1.GetMyVipResponse, error) {
	return legacyVIPResponse(req.GetUserId()), nil
}

func legacyVIPResponse(userID int64) *walletv1.GetMyVipResponse {
	// 大多数既有 room-service 测试运行在 Lalu；显式返回 legacy program，验证新增门禁不会暗改旧 App 行为。
	return &walletv1.GetMyVipResponse{
		State: &walletv1.VipState{
			ProgramConfig: &walletv1.VipProgramConfig{AppCode: appcode.Default, ProgramType: "legacy_timed", Status: "active"},
			EffectiveVip:  &walletv1.UserVip{UserId: userID},
		},
	}
}

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

	roomID := "room-follow-flow"
	ownerID := int64(1001)
	viewerID := int64(2002)
	if _, err := svc.CreateRoom(ctx, &roomv1.CreateRoomRequest{
		Meta:            roomservice.NewRequestMeta(roomID, ownerID),
		SeatCount:       10,
		Mode:            "voice",
		VisibleRegionId: 1001,
		RoomName:        "Follow Flow",
		RoomAvatar:      testRoomCoverURL,
		RoomShortId:     "follow-flow",
	}); err != nil {
		t.Fatalf("create room failed: %v", err)
	}
	if _, err := svc.JoinRoom(ctx, &roomv1.JoinRoomRequest{
		Meta: roomservice.NewRequestMeta(roomID, viewerID),
		Role: "audience",
	}); err != nil {
		t.Fatalf("join room failed: %v", err)
	}

	beforeFollow, err := svc.GetRoomSnapshot(ctx, &roomv1.GetRoomSnapshotRequest{
		Meta:         &roomv1.RequestMeta{AppCode: appcode.Default, RoomId: roomID},
		RoomId:       roomID,
		ViewerUserId: viewerID,
	})
	if err != nil {
		t.Fatalf("snapshot before follow failed: %v", err)
	}
	if beforeFollow.GetIsFollowed() {
		t.Fatalf("new viewer must not be followed before FollowRoom")
	}

	followResp, err := svc.FollowRoom(ctx, &roomv1.FollowRoomRequest{
		Meta:   &roomv1.RequestMeta{AppCode: appcode.Default, RoomId: roomID},
		RoomId: roomID,
		UserId: viewerID,
	})
	if err != nil {
		t.Fatalf("follow room failed: %v", err)
	}
	if !followResp.GetFollowed() || followResp.GetFollowedAtMs() <= 0 {
		t.Fatalf("follow response mismatch: %+v", followResp)
	}

	afterFollow, err := svc.GetRoomSnapshot(ctx, &roomv1.GetRoomSnapshotRequest{
		Meta:         &roomv1.RequestMeta{AppCode: appcode.Default, RoomId: roomID},
		RoomId:       roomID,
		ViewerUserId: viewerID,
	})
	if err != nil {
		t.Fatalf("snapshot after follow failed: %v", err)
	}
	if !afterFollow.GetIsFollowed() {
		t.Fatalf("snapshot must expose is_followed=true after FollowRoom")
	}

	feedResp, err := svc.ListRoomFeeds(ctx, &roomv1.ListRoomFeedsRequest{
		Meta:            &roomv1.RequestMeta{AppCode: appcode.Default},
		ViewerUserId:    viewerID,
		VisibleRegionId: 1001,
		Tab:             "followed",
		Limit:           20,
	})
	if err != nil {
		t.Fatalf("followed feed failed: %v", err)
	}
	if len(feedResp.GetRooms()) != 1 || feedResp.GetRooms()[0].GetRoomId() != roomID {
		t.Fatalf("followed feed must return followed room: %+v", feedResp.GetRooms())
	}

	unfollowResp, err := svc.UnfollowRoom(ctx, &roomv1.UnfollowRoomRequest{
		Meta:   &roomv1.RequestMeta{AppCode: appcode.Default, RoomId: roomID},
		RoomId: roomID,
		UserId: viewerID,
	})
	if err != nil {
		t.Fatalf("unfollow room failed: %v", err)
	}
	if unfollowResp.GetFollowed() {
		t.Fatalf("unfollow response mismatch: %+v", unfollowResp)
	}

	emptyFeedResp, err := svc.ListRoomFeeds(ctx, &roomv1.ListRoomFeedsRequest{
		Meta:            &roomv1.RequestMeta{AppCode: appcode.Default},
		ViewerUserId:    viewerID,
		VisibleRegionId: 1001,
		Tab:             "followed",
		Limit:           20,
	})
	if err != nil {
		t.Fatalf("followed feed after unfollow failed: %v", err)
	}
	if len(emptyFeedResp.GetRooms()) != 0 {
		t.Fatalf("followed feed must be empty after UnfollowRoom: %+v", emptyFeedResp.GetRooms())
	}
}
