package luckygift

import (
	"context"
	"fmt"
	"strings"
	"testing"

	"hyapp/pkg/xerr"
	domain "hyapp/services/lucky-gift-service/internal/domain/luckygift"
)

type batchLimitRepository struct {
	Repository
	batchCalls int
}

func (r *batchLimitRepository) ExecuteLuckyGiftDrawBatch(_ context.Context, cmds []domain.DrawCommand, _ int64) ([]domain.DrawResult, error) {
	r.batchCalls++
	return make([]domain.DrawResult, len(cmds)), nil
}

func TestNormalizeDrawCommandRejectsOversizedInternalGiftWork(t *testing.T) {
	base := domain.DrawCommand{
		CommandID:    "cmd-lucky",
		UserID:       1,
		DeviceID:     "device-1",
		RoomID:       "room-1",
		AnchorID:     "2",
		GiftID:       "rose",
		GiftCount:    1,
		CoinSpent:    10,
		TargetUserID: 2,
	}
	svc := &Service{}
	oversizedCount := base
	oversizedCount.GiftCount = 1_000
	if _, err := svc.normalizeDrawCommand(oversizedCount); err == nil {
		t.Fatal("gift_count above 999 was accepted")
	}
	oversizedID := base
	oversizedID.CommandID = strings.Repeat("x", 129)
	if _, err := svc.normalizeDrawCommand(oversizedID); err == nil {
		t.Fatal("command_id above 128 bytes was accepted")
	}
	normalized, err := svc.normalizeDrawCommand(base)
	if err != nil {
		t.Fatalf("normalize valid command: %v", err)
	}
	if normalized.PaidAtMS != 0 {
		t.Fatalf("service layer invented wallet paid_at_ms: %d", normalized.PaidAtMS)
	}
}

func TestNormalizeDrawCommandKeepsLegacyMissingDeviceForRuleAwareValidation(t *testing.T) {
	svc := &Service{}
	normalized, err := svc.normalizeDrawCommand(domain.DrawCommand{
		CommandID: "cmd-fixed-legacy-device", UserID: 1, TargetUserID: 2,
		RoomID: "room-1", AnchorID: "2", GiftID: "rose", GiftCount: 1, CoinSpent: 10,
	})
	if err != nil || normalized.DeviceID != "" {
		// normalize 尚未读到不可变规则；此处拒绝会破坏 fixed_v2 存量 JWT/command log 重放。
		// 真正的 dynamic_v3 空设备拒绝在 MySQL 规则事务入口完成。
		t.Fatalf("legacy missing device normalization got=%+v err=%v", normalized, err)
	}
}

func TestNormalizeExternalDrawCommandRejectsMoreThan999SequentialDraws(t *testing.T) {
	svc := &Service{}
	_, err := svc.normalizeExternalDrawCommand(domain.ExternalDrawCommand{
		AppCode: "aslan", ExternalUserID: "u-1", RequestID: "req-1",
		GiftCount: 1_000, UnitAmount: 1, TotalAmount: 1_000, Currency: "COIN", PaidAtMS: 1,
	})
	if !xerr.IsCode(err, xerr.InvalidArgument) {
		t.Fatalf("external gift_count=1000 should be rejected, got %v", err)
	}
}

func TestNormalizeExternalDrawCommandDoesNotInventPaidAt(t *testing.T) {
	svc := &Service{}
	normalized, err := svc.normalizeExternalDrawCommand(domain.ExternalDrawCommand{
		AppCode: "aslan", ExternalUserID: "u-1", RequestID: "req-paid-at",
		GiftCount: 1, UnitAmount: 10, TotalAmount: 10, Currency: "COIN",
	})
	if err != nil {
		t.Fatalf("normalize external draw: %v", err)
	}
	if normalized.PaidAtMS != 0 {
		t.Fatalf("service layer invented external paid_at_ms: %d", normalized.PaidAtMS)
	}
}

func TestDrawBatchRejectsAggregateWorkAcrossTargets(t *testing.T) {
	repository := &batchLimitRepository{}
	svc := New(repository)
	commands := make([]domain.DrawCommand, 0, 6)
	for index := 0; index < 6; index++ {
		commands = append(commands, domain.DrawCommand{
			CommandID: fmt.Sprintf("cmd-lucky-target-%d", index), UserID: 1, TargetUserID: int64(index + 10),
			DeviceID: "device-1", RoomID: "room-1", AnchorID: "2", GiftID: "rose",
			GiftCount: 999, CoinSpent: 999,
		})
	}
	if _, err := svc.DrawBatch(context.Background(), commands); !xerr.IsCode(err, xerr.InvalidArgument) {
		t.Fatalf("5994 aggregate draw units should be rejected, got %v", err)
	}
	if repository.batchCalls != 0 {
		t.Fatalf("oversized batch reached repository %d times", repository.batchCalls)
	}
	if _, err := svc.DrawBatch(context.Background(), commands[:5]); err != nil {
		t.Fatalf("4995 aggregate draw units should be accepted: %v", err)
	}
	if repository.batchCalls != 1 {
		t.Fatalf("accepted batch repository calls=%d, want 1", repository.batchCalls)
	}
}
