package wheel

import (
	"context"
	"testing"
	"time"

	"google.golang.org/grpc"
	walletv1 "hyapp.local/api/proto/wallet/v1"
	"hyapp/pkg/appcode"
	domain "hyapp/services/activity-service/internal/domain/wheel"
)

func TestDrawGrantsResourceReward(t *testing.T) {
	repository := &fakeWheelRepository{executeResult: domain.DrawResult{
		DrawID:         "wheel_draw_1",
		DrawIDs:        []string{"wheel_draw_1"},
		CommandID:      "cmd-1",
		WheelID:        "classic",
		SelectedTierID: "classic-1",
		RewardType:     domain.RewardTypeGift,
		RewardID:       "gift-1",
		RewardCount:    2,
		RewardStatus:   domain.StatusPending,
		MetadataJSON:   `{"resource_id":326}`,
	}}
	wallet := &fakeWheelWallet{}
	service := New(repository, WithWallet(wallet))
	service.SetClock(func() time.Time { return time.UnixMilli(1700000000000).UTC() })

	result, err := service.Draw(appcode.WithContext(context.Background(), "yumi"), domain.DrawCommand{
		CommandID: "cmd-1",
		WheelID:   "classic",
		UserID:    1001,
		DeviceID:  "device-1",
		CoinSpent: 10000,
	})
	if err != nil {
		t.Fatalf("Draw returned error: %v", err)
	}
	if result.RewardStatus != domain.StatusGranted {
		t.Fatalf("reward status mismatch: %s", result.RewardStatus)
	}
	if len(wallet.resourceGrants) != 1 {
		t.Fatalf("resource grants mismatch: %+v", wallet.resourceGrants)
	}
	req := wallet.resourceGrants[0]
	if req.GetCommandId() != "wheel_resource:wheel_draw_1" || req.GetTargetUserId() != 1001 || req.GetResourceId() != 326 || req.GetQuantity() != 2 {
		t.Fatalf("grant request mismatch: %+v", req)
	}
	if req.GetReason() != "wheel_reward" || req.GetGrantSource() != "wheel_reward" || req.GetOperatorUserId() != 1001 {
		t.Fatalf("grant audit fields mismatch: %+v", req)
	}
	if len(repository.markGrantedDrawIDs) != 1 || repository.markGrantedDrawIDs[0] != "wheel_draw_1" {
		t.Fatalf("marked draw IDs mismatch: %+v", repository.markGrantedDrawIDs)
	}
}

func TestDrawFulfillsBatchCoinAndResourceRewards(t *testing.T) {
	repository := &fakeWheelRepository{executeResult: domain.DrawResult{
		DrawID:       "wheel_draw_batch",
		DrawIDs:      []string{"wheel_draw_batch"},
		CommandID:    "cmd-batch",
		WheelID:      "advanced",
		RewardType:   "batch",
		RewardStatus: domain.StatusPending,
		Rewards: []domain.DrawReward{
			{SelectedTierID: "advanced-1", RewardType: domain.RewardTypeCoin, RewardCoins: 100},
			{SelectedTierID: "advanced-2", RewardType: domain.RewardTypeProp, RewardID: "901", RewardCount: 1, MetadataJSON: `{"resource_id":901}`},
		},
	}}
	wallet := &fakeWheelWallet{coinBalanceAfter: 9900}
	service := New(repository, WithWallet(wallet))
	service.SetClock(func() time.Time { return time.UnixMilli(1700000000000).UTC() })

	result, err := service.Draw(appcode.WithContext(context.Background(), "yumi"), domain.DrawCommand{
		CommandID: "cmd-batch",
		WheelID:   "advanced",
		UserID:    2002,
		DeviceID:  "device-2",
		CoinSpent: 50000,
		DrawCount: 2,
	})
	if err != nil {
		t.Fatalf("Draw returned error: %v", err)
	}
	if result.RewardStatus != domain.StatusGranted {
		t.Fatalf("reward status mismatch: %s", result.RewardStatus)
	}
	if len(wallet.coinCredits) != 1 || wallet.coinCredits[0].GetCommandId() != "wheel_reward:wheel_draw_batch:1" {
		t.Fatalf("coin credit mismatch: %+v", wallet.coinCredits)
	}
	if len(wallet.resourceGrants) != 1 || wallet.resourceGrants[0].GetCommandId() != "wheel_resource:wheel_draw_batch:2" {
		t.Fatalf("resource grant mismatch: %+v", wallet.resourceGrants)
	}
	if result.CoinBalanceAfter != 9900 {
		t.Fatalf("coin balance mismatch: %d", result.CoinBalanceAfter)
	}
}

type fakeWheelRepository struct {
	executeResult      domain.DrawResult
	markGrantedDrawIDs []string
}

func (r *fakeWheelRepository) PublishWheelRuleConfig(context.Context, domain.RuleConfig, int64) (domain.RuleConfig, error) {
	return domain.RuleConfig{}, nil
}

func (r *fakeWheelRepository) GetWheelRuleConfig(context.Context, string) (domain.RuleConfig, bool, error) {
	return domain.RuleConfig{}, false, nil
}

func (r *fakeWheelRepository) ExecuteWheelDraw(context.Context, domain.DrawCommand, int64) (domain.DrawResult, error) {
	return r.executeResult, nil
}

func (r *fakeWheelRepository) ListWheelDraws(context.Context, domain.DrawQuery) ([]domain.DrawResult, int64, error) {
	return nil, 0, nil
}

func (r *fakeWheelRepository) GetWheelDrawSummary(context.Context, domain.DrawQuery) (domain.DrawSummary, error) {
	return domain.DrawSummary{}, nil
}

func (r *fakeWheelRepository) MarkWheelDrawsGranted(_ context.Context, _ string, drawIDs []string, _ string, _ int64) error {
	r.markGrantedDrawIDs = append([]string(nil), drawIDs...)
	return nil
}

type fakeWheelWallet struct {
	coinCredits      []*walletv1.CreditWheelRewardRequest
	resourceGrants   []*walletv1.GrantResourceRequest
	coinBalanceAfter int64
}

func (w *fakeWheelWallet) CreditWheelReward(_ context.Context, req *walletv1.CreditWheelRewardRequest, _ ...grpc.CallOption) (*walletv1.CreditWheelRewardResponse, error) {
	w.coinCredits = append(w.coinCredits, req)
	return &walletv1.CreditWheelRewardResponse{
		TransactionId: "tx-" + req.GetCommandId(),
		Balance:       &walletv1.AssetBalance{AssetType: "COIN", AvailableAmount: w.coinBalanceAfter},
	}, nil
}

func (w *fakeWheelWallet) GrantResource(_ context.Context, req *walletv1.GrantResourceRequest, _ ...grpc.CallOption) (*walletv1.ResourceGrantResponse, error) {
	w.resourceGrants = append(w.resourceGrants, req)
	return &walletv1.ResourceGrantResponse{Grant: &walletv1.ResourceGrant{GrantId: "grant-" + req.GetCommandId()}}, nil
}
