//go:build integration

package inttest

import (
	"context"
	"strings"
	"testing"

	"github.com/google/uuid"

	"scrabble/backend/internal/payments"
)

// methodPrice is one per-method money price for a seeded chip pack.
type methodPrice struct {
	method   string
	currency string
	amount   int64
}

// seedPackProduct creates an active chip pack: a product carrying the chips atom and a money price
// per method. It returns the product id.
func seedPackProduct(t *testing.T, chips int, prices ...methodPrice) uuid.UUID {
	t.Helper()
	ctx := context.Background()
	prod := uuid.New()
	if _, err := testDB.ExecContext(ctx,
		`INSERT INTO payments.product (product_id, title, active) VALUES ($1,'test pack',true)`, prod); err != nil {
		t.Fatalf("seed pack product: %v", err)
	}
	if _, err := testDB.ExecContext(ctx,
		`INSERT INTO payments.product_item (product_id, atom_type, quantity) VALUES ($1,'chips',$2)`, prod, chips); err != nil {
		t.Fatalf("seed chips item: %v", err)
	}
	for _, p := range prices {
		if _, err := testDB.ExecContext(ctx,
			`INSERT INTO payments.product_price (product_id, method, currency, amount) VALUES ($1,$2,$3,$4)`,
			prod, p.method, p.currency, p.amount); err != nil {
			t.Fatalf("seed pack price %s: %v", p.method, err)
		}
	}
	return prod
}

// findCatalogProduct returns the projected storefront product for id in the context (the catalog
// is global, so tests assert by id rather than count — testDB is shared).
func findCatalogProduct(t *testing.T, svc *payments.Service, cxt payments.Context, id uuid.UUID) (payments.CatalogProduct, bool) {
	t.Helper()
	view, err := svc.Catalog(context.Background(), cxt)
	if err != nil {
		t.Fatalf("catalog: %v", err)
	}
	for _, p := range view.Products {
		if p.ProductID == id.String() {
			return p, true
		}
	}
	return payments.CatalogProduct{}, false
}

// TestPaymentsCatalogByContext verifies the storefront projects a value at its chip price in every
// context and a chip pack at the context method's money price, over Postgres.
func TestPaymentsCatalogByContext(t *testing.T) {
	svc := newPaymentsService()
	value := seedValueProduct(t, 500, 250, 0)
	pack := seedPackProduct(t, 100,
		methodPrice{"vk", "VOTE", 20},
		methodPrice{"telegram", "XTR", 25},
		methodPrice{"direct", "RUB", 14900},
	)

	for _, kind := range []string{"vk", "telegram", "direct"} {
		v, ok := findCatalogProduct(t, svc, payments.NewContext(kind, "web"), value)
		if !ok {
			t.Fatalf("value missing in %s context", kind)
		}
		if v.Kind != "value" || v.Chips != 500 || v.MoneyCurrency != "" {
			t.Errorf("value in %s = %+v, want kind=value chips=500 no money", kind, v)
		}
	}

	cases := []struct {
		kind     string
		amount   int64
		currency string
	}{
		{"vk", 20, "VOTE"},
		{"telegram", 25, "XTR"},
		{"direct", 14900, "RUB"},
	}
	for _, tc := range cases {
		p, ok := findCatalogProduct(t, svc, payments.NewContext(tc.kind, "web"), pack)
		if !ok {
			t.Fatalf("pack missing in %s context", tc.kind)
		}
		if p.Kind != "pack" || p.Chips != 0 || p.MoneyAmount != tc.amount || p.MoneyCurrency != tc.currency {
			t.Errorf("pack in %s = %+v, want kind=pack amount=%d currency=%s", tc.kind, p, tc.amount, tc.currency)
		}
	}
}

// TestPaymentsCatalogExcludesDeactivated verifies a soft-deleted product drops out of the storefront.
func TestPaymentsCatalogExcludesDeactivated(t *testing.T) {
	svc := newPaymentsService()
	prod := seedValueProduct(t, 100, 10, 0)
	if _, err := testDB.ExecContext(context.Background(),
		`UPDATE payments.product SET active=false WHERE product_id=$1`, prod); err != nil {
		t.Fatalf("deactivate: %v", err)
	}
	if _, ok := findCatalogProduct(t, svc, payments.NewContext("direct", "web"), prod); ok {
		t.Error("deactivated product must not appear in the storefront")
	}
}

// TestOfferPricingReflectsCatalogEdits verifies the public-offer price list (§4.4) is projected from
// the live catalog and its cache is invalidated on a catalog mutation: a newly created pack appears
// with its per-rail prices, and archiving it through the service drops it from the next read.
func TestOfferPricingReflectsCatalogEdits(t *testing.T) {
	svc := newPaymentsService()
	ctx := context.Background()
	title := "OfferTest " + uuid.NewString()
	id, err := svc.CreateProduct(ctx, payments.ProductInput{
		Title: title,
		Atoms: []payments.AtomLine{{Atom: "chips", Quantity: 50}},
		Prices: []payments.PriceLine{
			{Method: "direct", Currency: payments.CurrencyRUB, Amount: 20000},
			{Method: "vk", Currency: payments.CurrencyVote, Amount: 30},
			{Method: "telegram", Currency: payments.CurrencyStar, Amount: 100},
		},
	}, true)
	if err != nil {
		t.Fatalf("create pack: %v", err)
	}

	md, err := svc.OfferPricing(ctx)
	if err != nil {
		t.Fatalf("offer pricing: %v", err)
	}
	if row := "| " + title + " | 200.00 | 30 | 100 |"; !strings.Contains(md, row) {
		t.Fatalf("offer pricing missing the new pack row %q\n%s", row, md)
	}

	// Archiving through the service marks the cache stale; the next read must reproject without it.
	if err := svc.SetProductActive(ctx, id, false); err != nil {
		t.Fatalf("archive: %v", err)
	}
	md, err = svc.OfferPricing(ctx)
	if err != nil {
		t.Fatalf("offer pricing after archive: %v", err)
	}
	if strings.Contains(md, title) {
		t.Errorf("archived pack must drop from the offer pricing:\n%s", md)
	}
}
