package economy

import (
	"context"
	"path/filepath"
	"testing"

	"hakemsho/internal/store"
)

func setup(t *testing.T) (*Service, int64) {
	t.Helper()
	dbPath := filepath.Join(t.TempDir(), "test.db")
	st, err := store.Open(dbPath)
	if err != nil {
		t.Fatalf("open db: %v", err)
	}
	t.Cleanup(func() { st.Close() })

	res, err := st.DB.Exec(`INSERT INTO users (mobile) VALUES ('09120000000')`)
	if err != nil {
		t.Fatalf("insert user: %v", err)
	}
	id, _ := res.LastInsertId()
	svc := New(st.DB, DevAdVerifier{}, DevIAPVerifier{})
	if err := svc.LoadCatalog(context.Background()); err != nil {
		t.Fatalf("load catalog: %v", err)
	}
	return svc, id
}

func setCoins(t *testing.T, s *Service, userID, coins int64) {
	t.Helper()
	if _, err := s.db.Exec(`UPDATE users SET coins = ? WHERE id = ?`, coins, userID); err != nil {
		t.Fatal(err)
	}
}

func coinsOf(t *testing.T, s *Service, userID int64) int64 {
	t.Helper()
	p, err := s.GetProfile(context.Background(), userID)
	if err != nil {
		t.Fatal(err)
	}
	return p.Coins
}

func TestLevelInfo(t *testing.T) {
	cases := []struct {
		xp    int64
		level int
		into  int64
		need  int64
	}{
		{0, 1, 0, 50},
		{49, 1, 49, 50},
		{50, 2, 0, 100},
		{150, 3, 0, 150},
	}
	for _, c := range cases {
		lvl, into, need := LevelInfo(c.xp)
		if lvl != c.level || into != c.into || need != c.need {
			t.Errorf("LevelInfo(%d) = (%d,%d,%d), want (%d,%d,%d)",
				c.xp, lvl, into, need, c.level, c.into, c.need)
		}
	}
}

func TestWalletInsufficient(t *testing.T) {
	svc, id := setup(t)
	ctx := context.Background()
	setCoins(t, svc, id, 100)
	if err := svc.Adjust(ctx, id, CurrencyCoin, -200, "test", ""); err != ErrInsufficient {
		t.Fatalf("want ErrInsufficient, got %v", err)
	}
	if c := coinsOf(t, svc, id); c != 100 {
		t.Fatalf("balance changed on failed debit: %d", c)
	}
	if err := svc.Adjust(ctx, id, CurrencyCoin, -60, "test", ""); err != nil {
		t.Fatal(err)
	}
	if c := coinsOf(t, svc, id); c != 40 {
		t.Fatalf("want 40, got %d", c)
	}
}

func TestDailyCooldown(t *testing.T) {
	svc, id := setup(t)
	ctx := context.Background()
	if amt, err := svc.ClaimDaily(ctx, id); err != nil || amt != DailyReward {
		t.Fatalf("first claim: amt=%d err=%v", amt, err)
	}
	if _, err := svc.ClaimDaily(ctx, id); err != ErrTooSoon {
		t.Fatalf("want ErrTooSoon, got %v", err)
	}
	if c := coinsOf(t, svc, id); c != DailyReward {
		t.Fatalf("want %d coins, got %d", DailyReward, c)
	}
}

func TestAdRewardDedupeAndCredit(t *testing.T) {
	svc, id := setup(t)
	ctx := context.Background()
	if amt, err := svc.ClaimAdReward(ctx, id, "tok-1"); err != nil || amt != AdReward {
		t.Fatalf("claim: amt=%d err=%v", amt, err)
	}
	// همان توکن دوباره ⇒ تکراری
	if _, err := svc.ClaimAdReward(ctx, id, "tok-1"); err != ErrAdDuplicate {
		t.Fatalf("want ErrAdDuplicate, got %v", err)
	}
	// توکن خالی ⇒ تأیید نشده
	if _, err := svc.ClaimAdReward(ctx, id, ""); err != ErrAdNotVerified {
		t.Fatalf("want ErrAdNotVerified, got %v", err)
	}
	if c := coinsOf(t, svc, id); c != AdReward {
		t.Fatalf("want %d, got %d", AdReward, c)
	}
}

func TestBuyAndSelectCard(t *testing.T) {
	svc, id := setup(t)
	ctx := context.Background()
	setCoins(t, svc, id, 10000)

	// انتخاب کارتی که مالکش نیست ⇒ خطا
	if err := svc.SelectCard(ctx, id, "wizard"); err != ErrNotOwned {
		t.Fatalf("want ErrNotOwned, got %v", err)
	}
	// خرید کارت جادوگر (۱۰۰۰۰)
	if err := svc.BuyCard(ctx, id, "wizard"); err != nil {
		t.Fatalf("buy: %v", err)
	}
	if c := coinsOf(t, svc, id); c != 0 {
		t.Fatalf("want 0 after buy, got %d", c)
	}
	// خرید دوباره ⇒ قبلاً مالک است
	if err := svc.BuyCard(ctx, id, "wizard"); err != ErrAlreadyOwned {
		t.Fatalf("want ErrAlreadyOwned, got %v", err)
	}
	// حالا قابل انتخاب است
	if err := svc.SelectCard(ctx, id, "wizard"); err != nil {
		t.Fatalf("select: %v", err)
	}
	p, _ := svc.GetProfile(ctx, id)
	if p.SelectedCard != "wizard" {
		t.Fatalf("selected = %s", p.SelectedCard)
	}
	// خرید بدون سکه کافی ⇒ insufficient
	if err := svc.BuyCard(ctx, id, "epic"); err != ErrInsufficient {
		t.Fatalf("want ErrInsufficient, got %v", err)
	}
}

func TestPurchaseCreditsAndIdempotent(t *testing.T) {
	svc, id := setup(t)
	ctx := context.Background()
	req := PurchaseRequest{Store: "bazaar", Kind: "coin", ProductID: "small", Token: "pt-1"}

	if err := svc.VerifyPurchase(ctx, id, req); err != nil {
		t.Fatalf("purchase: %v", err)
	}
	pkg := svc.findCoinPackage("small")
	if c := coinsOf(t, svc, id); c != pkg.Coins {
		t.Fatalf("want %d coins, got %d", pkg.Coins, c)
	}
	// همان توکن دوباره ⇒ قبلاً پردازش شده، بدون شارژ مجدد
	if err := svc.VerifyPurchase(ctx, id, req); err != ErrAlreadyOwned {
		t.Fatalf("want ErrAlreadyOwned, got %v", err)
	}
	if c := coinsOf(t, svc, id); c != pkg.Coins {
		t.Fatalf("double credit! got %d", c)
	}
}

func TestGameSettlement(t *testing.T) {
	svc, id := setup(t)
	ctx := context.Background()
	setCoins(t, svc, id, 1000)

	// کسر ورودی
	if err := svc.ChargeEntry(ctx, id, 100, "r1"); err != nil {
		t.Fatalf("charge: %v", err)
	}
	if c := coinsOf(t, svc, id); c != 900 {
		t.Fatalf("after entry want 900, got %d", c)
	}
	// جایزه برنده
	if err := svc.AwardWinner(ctx, id, 250, 15, 50, "r1"); err != nil {
		t.Fatalf("award: %v", err)
	}
	p, _ := svc.GetProfile(ctx, id)
	if p.Coins != 1150 {
		t.Fatalf("after prize want 1150, got %d", p.Coins)
	}
	if p.XP != 15 {
		t.Fatalf("want 15 xp, got %d", p.XP)
	}
	if p.Trophies != 50 {
		t.Fatalf("want 50 trophies, got %d", p.Trophies)
	}
}
