package command

import (
	"bytes"
	"testing"
)

func TestSendGiftIdempotencyIgnoresGatewayResolvedSnapshots(t *testing.T) {
	base := SendGift{
		Base:       Base{CommandID: "combo-1_b1", Room: "room-1", ActorID: 42, DeviceID: "device-first"},
		TargetType: "user", TargetUserIDs: []int64{43}, TargetUserID: 43,
		GiftID: "rose", GiftCount: 9, ComboSessionID: "combo-1", BatchSeq: 1,
		SenderRegionID: 1001, SenderCountryID: 840,
		TargetHostScopes:     []GiftTargetHostScope{{TargetUserID: 43, TargetIsHost: true, TargetHostRegionID: 1001}},
		SenderDisplayProfile: GiftDisplayProfile{UserID: 42, Username: "before"},
	}
	retry := base
	retry.SenderRegionID = 2002
	retry.SenderCountryID = 356
	retry.TargetHostScopes = []GiftTargetHostScope{{TargetUserID: 43, TargetIsHost: false}}
	retry.SenderDisplayProfile = GiftDisplayProfile{UserID: 42, Username: "after"}
	retry.DeviceID = "device-after-token-rotation"

	left, err := IdempotencyPayloadForCommand(base)
	if err != nil {
		t.Fatalf("base payload: %v", err)
	}
	right, err := IdempotencyPayloadForCommand(retry)
	if err != nil {
		t.Fatalf("retry payload: %v", err)
	}
	if !bytes.Equal(left, right) {
		t.Fatalf("gateway snapshot drift changed client idempotency semantics:\nleft=%s\nright=%s", left, right)
	}
}

func TestSendGiftCommandLogPreservesTrustedDeviceIDAndReadsLegacyPayload(t *testing.T) {
	original := SendGift{
		Base: Base{
			AppCode: "lalu", CommandID: "cmd-device-recovery", Room: "room-1", ActorID: 42,
			SessionID: "sess-not-a-device", DeviceID: "device-auth-session-1", SentAtMS: 1_000,
		},
		TargetType: "user", TargetUserIDs: []int64{43}, TargetUserID: 43,
		GiftID: "rose", GiftCount: 1,
	}
	payload, err := Serialize(original)
	if err != nil {
		t.Fatalf("serialize send gift device snapshot: %v", err)
	}
	recovered, err := Deserialize(original.Type(), payload)
	if err != nil {
		t.Fatalf("deserialize send gift device snapshot: %v", err)
	}
	got, ok := recovered.(*SendGift)
	if !ok || got.DeviceID != "device-auth-session-1" || got.SessionID != "sess-not-a-device" {
		t.Fatalf("recovered trusted device snapshot mismatch: %#v", recovered)
	}

	// 滚动升级前的 command log 不包含 device_id；反序列化必须保持可恢复，
	// 并留下空值让 fixed_v2 兼容、dynamic_v3 在 owner 处明确拒绝。
	legacy, err := Deserialize(original.Type(), []byte(`{"app_code":"lalu","command_id":"cmd-legacy","actor_user_id":42,"room_id":"room-1","session_id":"sess-legacy","target_type":"user","target_user_id":43,"gift_id":"rose","gift_count":1}`))
	if err != nil {
		t.Fatalf("deserialize legacy send gift: %v", err)
	}
	legacyGift := legacy.(*SendGift)
	if legacyGift.DeviceID != "" || legacyGift.SessionID != "sess-legacy" {
		t.Fatalf("legacy command must not derive device from session: %+v", legacyGift.Base)
	}
}

func TestSendGiftIdempotencyKeepsComboBatchIdentity(t *testing.T) {
	base := SendGift{Base: Base{CommandID: "combo-1_b1", Room: "room-1", ActorID: 42}, TargetType: "user", TargetUserIDs: []int64{43}, TargetUserID: 43, GiftID: "rose", GiftCount: 1, ComboSessionID: "combo-1", BatchSeq: 1}
	reused := base
	reused.BatchSeq = 2
	left, _ := IdempotencyPayloadForCommand(base)
	right, _ := IdempotencyPayloadForCommand(reused)
	if bytes.Equal(left, right) {
		t.Fatal("same command_id reused for another combo batch was not detected")
	}
}
