package integration

import (
	"context"
	"errors"
	"testing"

	"github.com/shopspring/decimal"

	"github.com/TaruLab/keluargamart-backend/internal/db"
	"github.com/TaruLab/keluargamart-backend/internal/membership"
	"github.com/TaruLab/keluargamart-backend/internal/pricing"
)

// TestMembershipTierConfig exercises STEP 0: tier discount percentages are
// database-backed via membership_tiers and every change is audited in
// membership_tier_config_history. Skips without DATABASE_URL.
func TestMembershipTierConfig(t *testing.T) {
	pool := testPool(t)
	setUp(t, pool)
	ctx := context.Background()

	// Start from the migration's seeded rows so the test is deterministic.
	if _, err := pool.Exec(ctx, "TRUNCATE membership_tiers RESTART IDENTITY CASCADE"); err != nil {
		t.Fatal(err)
	}
	if _, err := pool.Exec(ctx,
		`INSERT INTO membership_tiers (code, name, discount_type, discount_value, is_active)
		 VALUES ('bronze','Bronze','percentage',2,true),
		        ('gold','Gold','percentage',5,true),
		        ('platinum','Platinum','percentage',8,true)`); err != nil {
		t.Fatal(err)
	}

	repo := membership.NewRepository(pool)
	svc := membership.NewService(repo, seqAdapter{pool}, wib)

	tiers, err := svc.ListTiers(ctx)
	if err != nil {
		t.Fatalf("list tiers: %v", err)
	}
	byCode := map[string]membership.TierConfig{}
	for _, tc := range tiers {
		byCode[tc.Code] = tc
	}
	if len(tiers) != 3 {
		t.Fatalf("expected 3 seeded tiers, got %d", len(tiers))
	}
	if v := byCode["bronze"].DiscountValue; !v.Equal(decimal.NewFromInt(2)) {
		t.Errorf("bronze discount = %s, want 2", v)
	}
	if v := byCode["gold"].DiscountValue; !v.Equal(decimal.NewFromInt(5)) {
		t.Errorf("gold discount = %s, want 5", v)
	}
	if v := byCode["platinum"].DiscountValue; !v.Equal(decimal.NewFromInt(8)) {
		t.Errorf("platinum discount = %s, want 8", v)
	}

	// End-to-end: a gold member sees the DB-sourced 5%, then 7% after an update.
	goldID := byCode["gold"].ID

	pricingSvc := pricing.NewService(pricing.NewRepository(pool))
	pricingSvc.WithMembershipLookup(func(ctx context.Context, q db.DBTX, customerID int64) (string, bool, error) {
		row, lerr := repo.MembershipByCustomer(ctx, q, customerID)
		if lerr != nil {
			if errors.Is(lerr, membership.ErrNotFound) {
				return "", false, nil
			}
			return "", false, lerr
		}
		if !row.IsActive {
			return "", false, nil
		}
		return row.Tier, true, nil
	})
	pricingSvc.WithTierBenefitResolver(func(ctx context.Context, q db.DBTX, tier string) (decimal.Decimal, error) {
		row, lerr := repo.GetTierByCode(ctx, q, tier)
		if lerr != nil {
			return decimal.Zero, lerr
		}
		if !row.IsActive {
			return decimal.Zero, membership.ErrTierInactive
		}
		if row.DiscountType != "percentage" {
			return decimal.Zero, errors.New("unsupported membership discount type")
		}
		return row.DiscountValue, nil
	})

	cust := seedCustomer(t, pool)
	if _, err := svc.Create(ctx, membership.CreateInput{
		CustomerID: cust, Tier: membership.TierGold, JoinedDate: "2026-08-17",
	}, nil); err != nil {
		t.Fatalf("create gold member: %v", err)
	}
	prod := seedProduct(t, pool, seedCategory(t, pool), seedUnit(t, pool), "12000")
	custID := cust
	got, err := pricingSvc.Resolve(ctx, nil, pricing.Request{
		ProductID: prod.ID, Quantity: decimal.NewFromInt(1), CustomerID: &custID,
	})
	if err != nil {
		t.Fatalf("resolve (before update): %v", err)
	}
	// 12000 * 0.05 = 600; FinalSubtotal = 11400
	if !got.MembershipDiscountPercent.Equal(decimal.NewFromInt(5)) {
		t.Errorf("before update discount_percent = %s, want 5", got.MembershipDiscountPercent)
	}
	if !got.MembershipDiscountAmount.Equal(decimal.NewFromInt(600)) {
		t.Errorf("before update discount_amount = %s, want 600", got.MembershipDiscountAmount)
	}

	// Admin changes gold to 7% and the change is audited.
	if _, err := svc.UpdateTier(ctx, goldID, membership.UpdateTierInput{DiscountValue: decimalPtr("7")}, nil); err != nil {
		t.Fatalf("update gold tier: %v", err)
	}

	tiersAfter, err := svc.ListTiers(ctx)
	if err != nil {
		t.Fatalf("list tiers after: %v", err)
	}
	var goldAfter decimal.Decimal
	for _, tc := range tiersAfter {
		if tc.Code == "gold" {
			goldAfter = tc.DiscountValue
		}
	}
	if !goldAfter.Equal(decimal.NewFromInt(7)) {
		t.Errorf("gold discount after update = %s, want 7", goldAfter)
	}

	var histCount int
	if err := pool.QueryRow(ctx,
		`SELECT count(*) FROM membership_tier_config_history WHERE membership_tier_id = $1 AND to_discount_value = 7`,
		goldID).Scan(&histCount); err != nil {
		t.Fatal(err)
	}
	if histCount < 1 {
		t.Fatalf("expected >=1 config history row for gold change to 7, got %d", histCount)
	}

	// The new 7% is reflected on the next pricing resolution.
	got7, err := pricingSvc.Resolve(ctx, nil, pricing.Request{
		ProductID: prod.ID, Quantity: decimal.NewFromInt(1), CustomerID: &custID,
	})
	if err != nil {
		t.Fatalf("resolve (after update): %v", err)
	}
	// 12000 * 0.07 = 840; FinalSubtotal = 11160
	if !got7.MembershipDiscountPercent.Equal(decimal.NewFromInt(7)) {
		t.Errorf("after update discount_percent = %s, want 7", got7.MembershipDiscountPercent)
	}
	if !got7.MembershipDiscountAmount.Equal(decimal.NewFromInt(840)) {
		t.Errorf("after update discount_amount = %s, want 840", got7.MembershipDiscountAmount)
	}
}
