package membership

import (
	"context"
	"errors"
	"testing"
	"time"

	"github.com/TaruLab/keluargamart-backend/internal/db"
	"github.com/jackc/pgx/v5"
	"github.com/jackc/pgx/v5/pgconn"
	"github.com/jackc/pgx/v5/pgtype"
	"github.com/shopspring/decimal"
)

// fakeTx satisfies Tx (db.DBTX + Commit/Rollback). The fake store ignores the
// querier, so these methods are never invoked.
type fakeTx struct {
	tag pgconn.CommandTag
	err error
}

func (f fakeTx) Exec(context.Context, string, ...interface{}) (pgconn.CommandTag, error) {
	if f.err != nil {
		return pgconn.CommandTag{}, f.err
	}
	return f.tag, nil
}
func (fakeTx) Query(context.Context, string, ...interface{}) (pgx.Rows, error) { return nil, nil }
func (fakeTx) QueryRow(context.Context, string, ...interface{}) pgx.Row        { return nil }
func (fakeTx) Commit(context.Context) error                                    { return nil }
func (fakeTx) Rollback(context.Context) error                                  { return nil }

// fakeStore is an in-memory Store. It records tier-change writes so tests can
// assert the audit trail.
type fakeStore struct {
	memberships           []db.GetMembershipByIDRow
	tierHistory           []db.MembershipTierHistory
	recordedTierChanges   []db.CreateMembershipTierHistoryParams
	tiers                 []db.MembershipTier
	tierConfigHistory     []db.CreateMembershipTierConfigHistoryParams
	tierConfigHistoryRows []db.MembershipTierConfigHistory
	customerExists        bool
	createErr             error
	updateRows            int64
	nextID                int64
}

func (s *fakeStore) Begin(context.Context) (Tx, error) { return fakeTx{}, nil }

func (s *fakeStore) MembershipByID(_ context.Context, _ db.DBTX, id int64) (db.GetMembershipByIDRow, error) {
	for _, m := range s.memberships {
		if m.ID == id {
			return m, nil
		}
	}
	if len(s.memberships) == 0 {
		return db.GetMembershipByIDRow{}, ErrNotFound
	}
	return s.memberships[0], nil
}
func (s *fakeStore) MembershipByCustomer(_ context.Context, _ db.DBTX, _ int64) (db.GetMembershipByIDRow, error) {
	if len(s.memberships) == 0 {
		return db.GetMembershipByIDRow{}, ErrNotFound
	}
	return s.memberships[0], nil
}
func (s *fakeStore) MembershipByUser(_ context.Context, _ db.DBTX, _ int64) (db.GetMembershipByIDRow, error) {
	if len(s.memberships) == 0 {
		return db.GetMembershipByIDRow{}, ErrNotFound
	}
	return s.memberships[0], nil
}
func (s *fakeStore) MembershipByNumber(_ context.Context, _ db.DBTX, _ string) (db.GetMembershipByIDRow, error) {
	if len(s.memberships) == 0 {
		return db.GetMembershipByIDRow{}, ErrNotFound
	}
	return s.memberships[0], nil
}
func (s *fakeStore) List(_ context.Context, _ Filter) ([]db.GetMembershipByIDRow, int, error) {
	return s.memberships, len(s.memberships), nil
}
func (s *fakeStore) CustomerExists(_ context.Context, _ db.DBTX, _ int64) (bool, error) {
	return s.customerExists, nil
}
func (s *fakeStore) CustomerOrderStats(_ context.Context, _ db.DBTX, _ int64) (decimal.Decimal, int64, error) {
	return decimal.Zero, 0, nil
}
func (s *fakeStore) Create(_ context.Context, _ db.DBTX, p db.CreateMembershipParams) (int64, error) {
	if s.createErr != nil {
		return 0, s.createErr
	}
	s.nextID++
	id := s.nextID
	s.memberships = append(s.memberships, db.GetMembershipByIDRow{
		ID:           id,
		MemberNumber: p.MemberNumber,
		CustomerID:   p.CustomerID,
		Tier:         p.Tier,
		JoinedDate:   p.JoinedDate.Time.Format("2006-01-02"),
		IsActive:     p.IsActive,
		Notes:        p.Notes,
		CreatedAt:    pgtype.Timestamptz{Time: time.Now(), Valid: true},
		UpdatedAt:    pgtype.Timestamptz{Time: time.Now(), Valid: true},
	})
	return id, nil
}
func (s *fakeStore) Update(_ context.Context, _ db.DBTX, _ db.UpdateMembershipParams) error {
	if s.updateRows == 0 {
		return ErrNotFound
	}
	return nil
}
func (s *fakeStore) RecordTierChange(_ context.Context, _ db.DBTX, p db.CreateMembershipTierHistoryParams) error {
	s.recordedTierChanges = append(s.recordedTierChanges, p)
	return nil
}
func (s *fakeStore) SetMembershipPoints(_ context.Context, _ db.DBTX, p db.SetMembershipPointsParams) error {
	for i := range s.memberships {
		if s.memberships[i].ID == p.ID {
			s.memberships[i].Points = p.Points
			s.memberships[i].Tier = p.Tier
			return nil
		}
	}
	if len(s.memberships) > 0 {
		s.memberships[0].Points = p.Points
		s.memberships[0].Tier = p.Tier
	}
	return nil
}
func (s *fakeStore) TierHistory(_ context.Context, _ int64) ([]db.MembershipTierHistory, error) {
	return s.tierHistory, nil
}
func (s *fakeStore) ListTiers(_ context.Context, _ db.DBTX) ([]db.MembershipTier, error) {
	return s.tiers, nil
}
func (s *fakeStore) GetTier(_ context.Context, _ db.DBTX, id int64) (db.MembershipTier, error) {
	for _, t := range s.tiers {
		if t.ID == id {
			return t, nil
		}
	}
	return db.MembershipTier{}, ErrTierNotFound
}
func (s *fakeStore) GetTierByCode(_ context.Context, _ db.DBTX, code string) (db.MembershipTier, error) {
	for _, t := range s.tiers {
		if t.Code == code {
			return t, nil
		}
	}
	return db.MembershipTier{}, ErrTierNotFound
}
func (s *fakeStore) CreateTier(_ context.Context, _ db.DBTX, p db.CreateMembershipTierParams) (int64, error) {
	s.nextID++
	id := s.nextID
	s.tiers = append(s.tiers, db.MembershipTier{
		ID:            id,
		Code:          p.Code,
		Name:          p.Name,
		DiscountType:  p.DiscountType,
		DiscountValue: p.DiscountValue,
		MinOrders:     p.MinOrders,
		MinSpend:      p.MinSpend,
		IsActive:      p.IsActive,
		CreatedAt:     pgtype.Timestamptz{Time: time.Now(), Valid: true},
		UpdatedAt:     pgtype.Timestamptz{Time: time.Now(), Valid: true},
	})
	return id, nil
}
func (s *fakeStore) UpdateTier(_ context.Context, _ db.DBTX, p db.UpdateMembershipTierParams) error {
	for i := range s.tiers {
		if s.tiers[i].ID == p.ID {
			s.tiers[i].Name = p.Name
			s.tiers[i].DiscountType = p.DiscountType
			s.tiers[i].DiscountValue = p.DiscountValue
			s.tiers[i].MinOrders = p.MinOrders
			s.tiers[i].MinSpend = p.MinSpend
			s.tiers[i].IsActive = p.IsActive
			s.tiers[i].UpdatedAt = pgtype.Timestamptz{Time: time.Now(), Valid: true}
			return nil
		}
	}
	return ErrTierNotFound
}
func (s *fakeStore) RecordTierConfigHistory(_ context.Context, _ db.DBTX, p db.CreateMembershipTierConfigHistoryParams) error {
	s.tierConfigHistory = append(s.tierConfigHistory, p)
	return nil
}

func (s *fakeStore) TierConfigHistory(_ context.Context, tierID int64) ([]db.MembershipTierConfigHistory, error) {
	return s.tierConfigHistoryRows, nil
}

type fakeSeq struct{ n int64 }

func (s *fakeSeq) Sequence(_ context.Context, _ string, _ time.Time, _ any) (int64, error) {
	s.n++
	return s.n, nil
}

func membershipRow(id int64, tier string) db.GetMembershipByIDRow {
	now := time.Now()
	return db.GetMembershipByIDRow{
		ID:           id,
		MemberNumber: "MBR-202608-000001",
		CustomerID:   10,
		Tier:         tier,
		JoinedDate:   "2026-08-17",
		IsActive:     true,
		CreatedAt:    pgtype.Timestamptz{Time: now, Valid: true},
		UpdatedAt:    pgtype.Timestamptz{Time: now, Valid: true},
		CustomerCode: "CUS-1",
		CustomerName: "C",
	}
}

func TestList(t *testing.T) {
	store := &fakeStore{memberships: []db.GetMembershipByIDRow{membershipRow(1, "bronze")}}
	svc := NewService(store, &fakeSeq{}, time.UTC)

	v, total, err := svc.List(context.Background(), Filter{Page: 0, PerPage: 0})
	if err != nil {
		t.Fatalf("list: %v", err)
	}
	if total != 1 || len(v) != 1 {
		t.Fatalf("expected 1 membership, got total=%d len=%d", total, len(v))
	}

	if _, _, err := svc.List(context.Background(), Filter{Tier: "silver"}); !errors.Is(err, ErrTierInvalid) {
		t.Fatalf("expected ErrTierInvalid for invalid tier filter, got %v", err)
	}
}

func TestCreate(t *testing.T) {
	cases := []struct {
		name    string
		in      CreateInput
		setup   func(*fakeStore)
		wantErr error
	}{
		{
			name:    "invalid tier rejected before any write",
			in:      CreateInput{CustomerID: 1, Tier: Tier("silver"), JoinedDate: "2026-08-17"},
			wantErr: ErrTierInvalid,
		},
		{
			name:    "unknown customer rejected",
			in:      CreateInput{CustomerID: 1, Tier: TierBronze, JoinedDate: "2026-08-17"},
			setup:   func(s *fakeStore) { s.customerExists = false },
			wantErr: ErrCustomerInvalid,
		},
		{
			name:    "duplicate customer rejected",
			in:      CreateInput{CustomerID: 1, Tier: TierBronze, JoinedDate: "2026-08-17"},
			setup:   func(s *fakeStore) { s.createErr = ErrAlreadyMember },
			wantErr: ErrAlreadyMember,
		},
	}
	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			s := &fakeStore{customerExists: true}
			if tc.setup != nil {
				tc.setup(s)
			}
			svc := NewService(s, &fakeSeq{}, time.UTC)
			if _, err := svc.Create(context.Background(), tc.in, nil); !errors.Is(err, tc.wantErr) {
				t.Fatalf("want %v, got %v", tc.wantErr, err)
			}
		})
	}

	s := &fakeStore{customerExists: true}
	svc := NewService(s, &fakeSeq{}, time.UTC)
	v, err := svc.Create(context.Background(), CreateInput{CustomerID: 1, Tier: TierGold, JoinedDate: "2026-08-17"}, nil)
	if err != nil {
		t.Fatalf("create: %v", err)
	}
	if v.Tier != TierGold {
		t.Fatalf("expected gold, got %s", v.Tier)
	}
	if len(s.recordedTierChanges) != 1 {
		t.Fatalf("expected 1 tier-change audit, got %d", len(s.recordedTierChanges))
	}
	if s.recordedTierChanges[0].ToTier != "gold" || s.recordedTierChanges[0].Reason != "membership created" {
		t.Fatalf("audit mismatch: %+v", s.recordedTierChanges[0])
	}
}

func TestUpdate(t *testing.T) {
	store := &fakeStore{memberships: []db.GetMembershipByIDRow{membershipRow(1, "bronze")}, updateRows: 1}
	svc := NewService(store, &fakeSeq{}, time.UTC)

	if _, err := svc.Update(context.Background(), 1, UpdateInput{Tier: TierGold, Reason: ""}, nil); !errors.Is(err, ErrReasonRequired) {
		t.Fatalf("expected ErrReasonRequired, got %v", err)
	}

	if _, err := svc.Update(context.Background(), 1, UpdateInput{Tier: TierGold, Reason: "promo"}, nil); err != nil {
		t.Fatalf("update: %v", err)
	}
	if len(store.recordedTierChanges) != 1 {
		t.Fatalf("expected 1 audit entry, got %d", len(store.recordedTierChanges))
	}
	tc := store.recordedTierChanges[0]
	if tc.FromTier.String != "bronze" || !tc.FromTier.Valid || tc.ToTier != "gold" {
		t.Fatalf("audit mismatch: %+v", tc)
	}

	store2 := &fakeStore{updateRows: 0}
	svc2 := NewService(store2, &fakeSeq{}, time.UTC)
	if _, err := svc2.Update(context.Background(), 1, UpdateInput{Tier: TierGold, Reason: "x"}, nil); !errors.Is(err, ErrNotFound) {
		t.Fatalf("expected ErrNotFound, got %v", err)
	}
}

func TestByNumber(t *testing.T) {
	store := &fakeStore{memberships: []db.GetMembershipByIDRow{membershipRow(1, "bronze")}}
	svc := NewService(store, &fakeSeq{}, time.UTC)

	if _, err := svc.ByNumber(context.Background(), "   "); !errors.Is(err, ErrNotFound) {
		t.Fatalf("expected ErrNotFound for blank number, got %v", err)
	}
	v, err := svc.ByNumber(context.Background(), "MBR-202608-000001")
	if err != nil {
		t.Fatalf("byNumber: %v", err)
	}
	if v.ID != 1 {
		t.Fatalf("expected id 1, got %d", v.ID)
	}
}

func TestTierHistoryShapes(t *testing.T) {
	store := &fakeStore{
		memberships: []db.GetMembershipByIDRow{membershipRow(1, "bronze")},
		tierHistory: []db.MembershipTierHistory{
			{ID: 1, MembershipID: 1, FromTier: pgtype.Text{Valid: false}, ToTier: "bronze", Reason: "created",
				CreatedAt: pgtype.Timestamptz{Time: time.Now(), Valid: true}},
			{ID: 2, MembershipID: 1, FromTier: pgtype.Text{String: "bronze", Valid: true}, ToTier: "gold", Reason: "promo",
				CreatedAt: pgtype.Timestamptz{Time: time.Now(), Valid: true}},
		},
	}
	svc := NewService(store, &fakeSeq{}, time.UTC)
	list, err := svc.TierHistory(context.Background(), 1)
	if err != nil {
		t.Fatalf("tierHistory: %v", err)
	}
	if len(list) != 2 {
		t.Fatalf("expected 2 entries, got %d", len(list))
	}
	if list[0].FromTier != nil {
		t.Fatalf("first entry should have nil FromTier (initial assignment)")
	}
	if list[1].FromTier == nil || *list[1].FromTier != TierBronze || list[1].ToTier != TierGold {
		t.Fatalf("second entry mismatch: %+v", list[1])
	}
}

// The config trail has to stay distinguishable from the assignment trail: the
// two tables answer different questions and were previously confusable because
// only the assignment one was readable.
func TestTierConfigHistoryShapes(t *testing.T) {
	at := time.Date(2026, 9, 18, 14, 12, 0, 0, time.UTC)
	store := &fakeStore{
		tiers: []db.MembershipTier{{ID: 7, Code: "gold", Name: "Gold"}},
		tierConfigHistoryRows: []db.MembershipTierConfigHistory{
			// Written before migration 000077 added the requirement columns.
			// NULLs here are real history, not missing data, and must not be
			// flattened to zero: "the minimum spend used to be 0" is a claim
			// the table never made.
			{ID: 1, MembershipTierID: 7, ToDiscountType: "percentage",
				ToDiscountValue: decimal.NewFromFloat(0), Reason: pgtype.Text{String: "tier created", Valid: true},
				ChangedAt: pgtype.Timestamptz{Time: at, Valid: true}},
			// A real update, with both sides populated.
			{ID: 2, MembershipTierID: 7,
				FromDiscountType:  pgtype.Text{String: "percentage", Valid: true},
				FromDiscountValue: decimal.NullDecimal{Decimal: decimal.NewFromInt(5), Valid: true},
				ToDiscountType:    "percentage",
				ToDiscountValue:   decimal.NewFromInt(10),
				FromMinOrders:     pgtype.Int4{Int32: 5, Valid: true},
				ToMinOrders:       pgtype.Int4{Int32: 15, Valid: true},
				FromMinSpend:      decimal.NullDecimal{Decimal: decimal.NewFromInt(10000000), Valid: true},
				ToMinSpend:        decimal.NullDecimal{Decimal: decimal.NewFromInt(50000000), Valid: true},
				Reason:            pgtype.Text{String: "tier config updated", Valid: true},
				ChangedBy:         pgtype.Int8{Int64: 1, Valid: true},
				ChangedAt:         pgtype.Timestamptz{Time: at, Valid: true}},
		},
	}
	svc := NewService(store, &fakeSeq{}, time.UTC)

	list, err := svc.TierConfigHistory(context.Background(), 7)
	if err != nil {
		t.Fatalf("tierConfigHistory: %v", err)
	}
	if len(list) != 2 {
		t.Fatalf("expected 2 entries, got %d", len(list))
	}

	// A creation has no "before". Everything optional stays nil.
	created := list[0]
	if created.FromDiscountType != nil || created.FromDiscountValue != nil {
		t.Fatalf("creation row should have no from-values: %+v", created)
	}
	if created.ToMinOrders != nil || created.ToMinSpend != nil {
		t.Fatalf("pre-000077 row must not invent requirement values: %+v", created)
	}
	if created.Reason != "tier created" {
		t.Fatalf("reason = %q", created.Reason)
	}

	// A change carries both sides, including the columns 000077 added.
	changed := list[1]
	if changed.FromDiscountType == nil || *changed.FromDiscountType != "percentage" {
		t.Fatalf("from discount type missing: %+v", changed)
	}
	if !changed.FromDiscountValue.Equal(decimal.NewFromInt(5)) || !changed.ToDiscountValue.Equal(decimal.NewFromInt(10)) {
		t.Fatalf("discount values mismatch: %+v", changed)
	}
	if changed.FromMinOrders == nil || *changed.FromMinOrders != 5 || changed.ToMinOrders == nil || *changed.ToMinOrders != 15 {
		t.Fatalf("min orders mismatch: %+v", changed)
	}
	if changed.FromMinSpend == nil || !changed.FromMinSpend.Equal(decimal.NewFromInt(10000000)) {
		t.Fatalf("from min spend mismatch: %+v", changed)
	}
	if changed.ChangedBy == nil || *changed.ChangedBy != 1 {
		t.Fatalf("changedBy should be 1: %+v", changed)
	}
	if !changed.ChangedAt.Equal(at) {
		t.Fatalf("changedAt = %v", changed.ChangedAt)
	}
}

// An unknown tier must be an error, not an empty list. "This tier has never
// been configured" and "this tier does not exist" are different answers and only
// the second is a 404.
func TestTierConfigHistory_UnknownTierIsNotFound(t *testing.T) {
	store := &fakeStore{
		tiers:                 []db.MembershipTier{{ID: 7, Code: "gold"}},
		tierConfigHistoryRows: nil,
	}
	svc := NewService(store, &fakeSeq{}, time.UTC)

	if _, err := svc.TierConfigHistory(context.Background(), 999); !errors.Is(err, ErrTierNotFound) {
		t.Fatalf("expected ErrTierNotFound, got %v", err)
	}
}

// A real tier that was never configured changes returns an empty list, not an
// error — the tier exists, it just has no history yet.
func TestTierConfigHistory_KnownTierNoHistoryIsEmpty(t *testing.T) {
	store := &fakeStore{tiers: []db.MembershipTier{{ID: 7, Code: "gold"}}}
	svc := NewService(store, &fakeSeq{}, time.UTC)

	list, err := svc.TierConfigHistory(context.Background(), 7)
	if err != nil {
		t.Fatalf("expected no error, got %v", err)
	}
	if len(list) != 0 {
		t.Fatalf("expected empty, got %d", len(list))
	}
	if list == nil {
		t.Fatal("must return an empty slice, not nil, so the API emits [] rather than null")
	}
}

func TestAccruePoints_AwardsAndAutoPromotes(t *testing.T) {
	store := &fakeStore{memberships: []db.GetMembershipByIDRow{membershipRow(1, "bronze")}}
	svc := NewService(store, &fakeSeq{}, time.UTC)

	// 1 point per Rp 10,000 -> Rp 9,999,000 spend = 999 points (still bronze).
	if _, err := svc.AccruePoints(context.Background(), 1, decimal.NewFromInt(9999000)); err != nil {
		t.Fatalf("accrue: %v", err)
	}
	if got := store.memberships[0].Points; got != 999 {
		t.Fatalf("points = %d, want 999", got)
	}
	if store.memberships[0].Tier != "bronze" {
		t.Fatalf("tier = %q, want bronze", store.memberships[0].Tier)
	}
	if len(store.recordedTierChanges) != 0 {
		t.Fatalf("unexpected tier change: %+v", store.recordedTierChanges)
	}

	// Cross 1000 -> gold.
	if _, err := svc.AccruePoints(context.Background(), 1, decimal.NewFromInt(10000)); err != nil {
		t.Fatalf("accrue: %v", err)
	}
	if got := store.memberships[0].Points; got != 1000 {
		t.Fatalf("points = %d, want 1000", got)
	}
	if store.memberships[0].Tier != "gold" {
		t.Fatalf("tier = %q, want gold", store.memberships[0].Tier)
	}
	if len(store.recordedTierChanges) != 1 {
		t.Fatalf("expected 1 tier change, got %d", len(store.recordedTierChanges))
	}

	// Cross 5000 -> platinum.
	if _, err := svc.AccruePoints(context.Background(), 1, decimal.NewFromInt(40000000)); err != nil {
		t.Fatalf("accrue: %v", err)
	}
	if store.memberships[0].Tier != "platinum" {
		t.Fatalf("tier = %q, want platinum", store.memberships[0].Tier)
	}
	if len(store.recordedTierChanges) != 2 {
		t.Fatalf("expected 2 tier changes, got %d", len(store.recordedTierChanges))
	}
}

func TestAccruePoints_RejectsNegative(t *testing.T) {
	store := &fakeStore{memberships: []db.GetMembershipByIDRow{membershipRow(1, "bronze")}}
	svc := NewService(store, &fakeSeq{}, time.UTC)
	if _, err := svc.AccruePoints(context.Background(), 1, decimal.NewFromInt(-1)); err == nil {
		t.Fatal("expected error for negative amount")
	}
}

func TestCreateAndUpdateTier_Requirements(t *testing.T) {
	store := &fakeStore{}
	svc := NewService(store, &fakeSeq{}, time.UTC)

	// Create a tier with min_orders=5 and min_spend=10,000,000
	created, err := svc.CreateTier(context.Background(), CreateTierInput{
		Code:          "silver",
		Name:          "Silver",
		DiscountType:  "percentage",
		DiscountValue: decimal.NewFromFloat(3.5),
		MinOrders:     5,
		MinSpend:      decimal.NewFromInt(10000000),
		IsActive:      true,
	}, nil)
	if err != nil {
		t.Fatalf("CreateTier: %v", err)
	}
	if created.MinOrders != 5 {
		t.Errorf("got min_orders=%d, want 5", created.MinOrders)
	}
	if !created.MinSpend.Equal(decimal.NewFromInt(10000000)) {
		t.Errorf("got min_spend=%v, want 10000000", created.MinSpend)
	}

	// Reject negative min_orders
	_, err = svc.CreateTier(context.Background(), CreateTierInput{
		Code:          "invalid",
		Name:          "Invalid",
		DiscountType:  "percentage",
		DiscountValue: decimal.NewFromInt(5),
		MinOrders:     -1,
		MinSpend:      decimal.Zero,
	}, nil)
	if err == nil {
		t.Errorf("expected error for negative min_orders, got nil")
	}

	// Reject negative min_spend
	_, err = svc.CreateTier(context.Background(), CreateTierInput{
		Code:          "invalid_spend",
		Name:          "Invalid Spend",
		DiscountType:  "percentage",
		DiscountValue: decimal.NewFromInt(5),
		MinOrders:     0,
		MinSpend:      decimal.NewFromInt(-100),
	}, nil)
	if err == nil {
		t.Errorf("expected error for negative min_spend, got nil")
	}

	// Update the tier
	newOrders := int32(8)
	newSpend := decimal.NewFromInt(20000000)
	updated, err := svc.UpdateTier(context.Background(), created.ID, UpdateTierInput{
		MinOrders: &newOrders,
		MinSpend:  &newSpend,
	}, nil)
	if err != nil {
		t.Fatalf("UpdateTier: %v", err)
	}
	if updated.MinOrders != 8 {
		t.Errorf("got updated min_orders=%d, want 8", updated.MinOrders)
	}
	if !updated.MinSpend.Equal(newSpend) {
		t.Errorf("got updated min_spend=%v, want 20000000", updated.MinSpend)
	}
}
