package integration

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

	"github.com/shopspring/decimal"

	"github.com/TaruLab/keluargamart-backend/internal/inventory"
	"github.com/TaruLab/keluargamart-backend/internal/sales"
)

// TestPOS_NoPoolExhaustionUnderConcurrency is a regression test for a deadlock
// introduced with the Phase 6 pricing engine: ProcessPOS holds a transaction
// while resolving prices, so a resolver that opened its own pool connection made
// every concurrent caller wait for a connection that no one could release.
//
// max_conns=2 means each worker can hold exactly one connection, so any second
// acquisition inside the transaction stalls. The subtest deadline turns that
// stall into a fast, readable failure instead of a 10-minute package timeout.
func TestPOS_NoPoolExhaustionUnderConcurrency(t *testing.T) {
	pool := testPoolWithMaxConns(t, 2)
	setUp(t, pool)
	ctx := context.Background()

	inv := inventory.NewService(inventory.NewRepository(pool), seqAdapter{pool}, wib)
	salesSvc := salesService(pool, inv)

	wh := seedWarehouse(t, pool)
	cat := seedCategory(t, pool)
	pcs := seedUnit(t, pool)
	box := seedUnit(t, pool)
	prod := seedProduct(t, pool, cat, pcs, "12000")
	seedStock(t, pool, prod.ID, wh, "600")
	cust := seedCustomer(t, pool)

	seedProductUnit(t, pool, prod.ID, pcs, "1", "", "1", true)
	boxUnitID := seedProductUnit(t, pool, prod.ID, box, "12", "138000", "2", false)

	const workers = 4
	done := make(chan error, workers)
	var wg sync.WaitGroup
	for i := 0; i < workers; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			_, _, err := salesSvc.ProcessPOS(ctx, sales.POSInput{
				CustomerID: cust, WarehouseID: wh, PaymentMethod: "cash",
				Items: []sales.POSItemInput{
					{ProductID: prod.ID, ProductUnitID: &boxUnitID, Quantity: decimal.NewFromInt(2)},
				},
			})
			done <- err
		}()
	}

	finished := make(chan struct{})
	go func() {
		wg.Wait()
		close(finished)
	}()

	select {
	case <-finished:
	case <-time.After(45 * time.Second):
		t.Fatal("concurrent POS deadlocked: a use case is taking a second pool connection while holding its transaction")
	}

	close(done)
	for err := range done {
		if err != nil {
			t.Fatalf("concurrent POS: %v", err)
		}
	}

	// 4 workers x 2 boxes x factor 12 = 96 base units: 600 -> 504
	if got := stockQty(t, pool, prod.ID, wh); !got.Equal(decimal.NewFromInt(504)) {
		t.Errorf("stock = %s, want 504 (96 base units deducted)", got)
	}
	if n := soCount(t, pool); n != workers {
		t.Errorf("sales orders = %d, want %d", n, workers)
	}
}

// TestCheckout_NoPoolExhaustionUnderConcurrency covers the same deadlock on the
// B2B path, where PlaceCheckoutInTx receives the caller's transaction.
func TestCheckout_NoPoolExhaustionUnderConcurrency(t *testing.T) {
	pool := testPoolWithMaxConns(t, 2)
	setUp(t, pool)
	ctx := context.Background()

	inv := inventory.NewService(inventory.NewRepository(pool), seqAdapter{pool}, wib)
	salesSvc := salesService(pool, inv)

	wh := seedWarehouse(t, pool)
	cat := seedCategory(t, pool)
	pcs := seedUnit(t, pool)
	prod := seedProduct(t, pool, cat, pcs, "12000")
	seedStock(t, pool, prod.ID, wh, "200")
	seedProductUnit(t, pool, prod.ID, pcs, "1", "", "1", true)

	const workers = 3
	users := make([]int64, 0, workers)
	for i := 0; i < workers; i++ {
		users = append(users, seedUser(t, pool))
	}

	errs := make(chan error, workers)
	var wg sync.WaitGroup
	for _, uid := range users {
		wg.Add(1)
		go func(userID int64) {
			defer wg.Done()
			_, err := salesSvc.PlaceCheckout(ctx, sales.CheckoutInput{
				WarehouseID: wh,
				Items:       []sales.CheckoutItemInput{{ProductID: prod.ID, Quantity: decimal.NewFromInt(5)}},
			}, userID, "Buyer", "buyer@example.test")
			errs <- err
		}(uid)
	}

	finished := make(chan struct{})
	go func() {
		wg.Wait()
		close(finished)
	}()

	select {
	case <-finished:
	case <-time.After(45 * time.Second):
		t.Fatal("concurrent checkout deadlocked: a use case is taking a second pool connection while holding its transaction")
	}

	close(errs)
	for err := range errs {
		if err != nil && !errors.Is(err, sales.ErrInsufficientStock) {
			t.Fatalf("concurrent checkout: %v", err)
		}
	}
}
