package integration

import (
	"context"
	"regexp"
	"sync"
	"testing"
	"time"

	"github.com/shopspring/decimal"

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

// confirmedNumber matches the monthly format PREFIX-YYYYMM-000001 (§17).
var confirmedNumber = regexp.MustCompile(`^(SO|INV|PAY)-\d{6}-\d{6}$`)

// TestMonthlyDocumentNumbers verifies the Phase 3 gate: SO, INV and PAY
// numbers follow PREFIX-YYYYMM-%06d and the counter restarts each month (here
// it must be 000001 because the document_number_sequences table is reset).
func TestMonthlyDocumentNumbers(t *testing.T) {
	pool := testPool(t)
	setUp(t, pool)
	ctx := context.Background()

	cust := seedCustomer(t, pool)
	wh := seedWarehouse(t, pool)
	cat := seedCategory(t, pool)
	unit := seedUnit(t, pool)
	p := seedProduct(t, pool, cat, unit, "100")
	seedStock(t, pool, p.ID, wh, "100")

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

	so, invc, err := salesSvc.ProcessPOS(ctx, sales.POSInput{
		CustomerID: cust, WarehouseID: wh, PaymentMethod: "cash",
		Items: []sales.POSItemInput{
			{ProductID: p.ID, Quantity: decimal.NewFromInt(1), UnitPrice: decimal.NewFromInt(100)},
		},
	})
	if err != nil {
		t.Fatalf("ProcessPOS: %v", err)
	}
	if !confirmedNumber.MatchString(so.SoNumber) {
		t.Errorf("so_number = %s, want SO-YYYYMM-000001", so.SoNumber)
	}
	if !confirmedNumber.MatchString(invc.InvoiceNumber) {
		t.Errorf("invoice_number = %s, want INV-YYYYMM-000001", invc.InvoiceNumber)
	}
	var payNumber string
	if err := pool.QueryRow(ctx,
		`SELECT payment_number FROM payments WHERE payable_type='App\Models\Invoice' AND payable_id=$1`,
		invc.ID).Scan(&payNumber); err != nil {
		t.Fatal(err)
	}
	if !confirmedNumber.MatchString(payNumber) {
		t.Errorf("payment_number = %s, want PAY-YYYYMM-000001", payNumber)
	}
}

// TestConcurrentSalesOrderNumbers verifies counter allocation is concurrency
// safe: parallel Create calls must each receive a distinct monthly number.
func TestConcurrentSalesOrderNumbers(t *testing.T) {
	pool := testPool(t)
	setUp(t, pool)
	ctx := context.Background()

	cust := seedCustomer(t, pool)
	wh := seedWarehouse(t, pool)
	cat := seedCategory(t, pool)
	unit := seedUnit(t, pool)
	p := seedProduct(t, pool, cat, unit, "100")
	seedStock(t, pool, p.ID, wh, "100")

	salesSvc := salesService(pool, nil)

	const n = 10
	var wg sync.WaitGroup
	errs := make(chan error, n)
	for i := 0; i < n; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			_, err := salesSvc.Create(ctx, sales.SOInput{
				CustomerID: cust,
				OrderDate:  time.Now().In(wib).Format("2006-01-02"),
				Items: []sales.SOItemInput{
					{ProductID: p.ID, Quantity: decimal.NewFromInt(1), UnitPrice: decimal.NewFromInt(100)},
				},
			})
			errs <- err
		}()
	}
	wg.Wait()
	close(errs)
	for err := range errs {
		if err != nil {
			t.Fatalf("concurrent Create: %v", err)
		}
	}

	rows, err := pool.Query(ctx, `SELECT so_number FROM sales_orders`)
	if err != nil {
		t.Fatal(err)
	}
	numbers := map[string]bool{}
	for rows.Next() {
		var num string
		if err := rows.Scan(&num); err != nil {
			t.Fatal(err)
		}
		if !confirmedNumber.MatchString(num) {
			t.Errorf("so_number = %s, want SO-YYYYMM-000001", num)
		}
		numbers[num] = true
	}
	rows.Close()
	if len(numbers) != n {
		t.Errorf("got %d distinct so_numbers, want %d", len(numbers), n)
	}
}
