package integration

import (
	"context"
	"errors"
	"testing"

	"github.com/shopspring/decimal"

	"github.com/TaruLab/keluargamart-backend/internal/catalog"
)

// TestCatalogUnitPricingGate is the Phase 6 item 1 gate: brands, configured
// product units with conversion factors and unit-specific price/minimum
// purchase, and barcodes that resolve to the configured unit. It also asserts
// the DB-level guards (one base unit, one primary barcode, globally unique
// barcode) that keep the pricing engine's inputs unambiguous.
func TestCatalogUnitPricingGate(t *testing.T) {
	pool := testPool(t)
	setUp(t, pool)
	ctx := context.Background()

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

	brand, err := svc.CreateBrand(ctx, catalog.BrandInput{Name: unique("BRAND"), Description: "gate", IsActive: true})
	if err != nil {
		t.Fatalf("create brand: %v", err)
	}

	cat := seedCategory(t, pool)
	baseUnit := seedUnit(t, pool)
	boxUnit := seedUnit(t, pool)

	product, err := svc.CreateProduct(ctx, catalog.ProductInput{
		Name:          "Gate Product",
		CategoryID:    cat,
		UnitID:        baseUnit,
		BrandID:       &brand.ID,
		PurchasePrice: decimal.NewFromInt(10000),
		SellingPrice:  decimal.NewFromInt(12000),
		IsActive:      true,
	})
	if err != nil {
		t.Fatalf("create product: %v", err)
	}
	if product.BrandID == nil || *product.BrandID != brand.ID {
		t.Fatalf("product brand_id = %v, want %d", product.BrandID, brand.ID)
	}
	if product.BrandName == nil || *product.BrandName != brand.Name {
		t.Fatalf("product brand_name = %v, want %q", product.BrandName, brand.Name)
	}

	base, err := svc.CreateProductUnit(ctx, product.ID, catalog.ProductUnitInput{
		UnitID:           baseUnit,
		ConversionFactor: decimal.NewFromInt(1),
		MinPurchase:      decimal.NewFromInt(1),
		IsBase:           true,
		IsActive:         true,
	})
	if err != nil {
		t.Fatalf("create base unit: %v", err)
	}

	boxPrice := decimal.NewFromInt(138000)
	box, err := svc.CreateProductUnit(ctx, product.ID, catalog.ProductUnitInput{
		UnitID:           boxUnit,
		ConversionFactor: decimal.NewFromInt(12),
		SellingPrice:     &boxPrice,
		MinPurchase:      decimal.NewFromInt(2),
		IsActive:         true,
	})
	if err != nil {
		t.Fatalf("create box unit: %v", err)
	}

	if _, err := svc.CreateProductUnit(ctx, product.ID, catalog.ProductUnitInput{
		UnitID:           boxUnit,
		ConversionFactor: decimal.NewFromInt(24),
		MinPurchase:      decimal.NewFromInt(1),
		IsActive:         true,
	}); !errors.Is(err, catalog.ErrDuplicateUnitConfig) {
		t.Fatalf("duplicate unit config: got %v, want ErrDuplicateUnitConfig", err)
	}

	units, err := svc.ListProductUnits(ctx, product.ID, true)
	if err != nil {
		t.Fatalf("list product units: %v", err)
	}
	if len(units) != 2 {
		t.Fatalf("configured units = %d, want 2", len(units))
	}
	if !units[0].IsBase || units[0].ID != base.ID {
		t.Fatalf("first unit = %+v, want base %d", units[0], base.ID)
	}

	// promoting the box to base must leave exactly one base unit
	if _, err := svc.UpdateProductUnit(ctx, product.ID, box.ID, catalog.ProductUnitInput{
		UnitID:           boxUnit,
		ConversionFactor: decimal.NewFromInt(1),
		SellingPrice:     &boxPrice,
		MinPurchase:      decimal.NewFromInt(2),
		IsBase:           true,
		IsActive:         true,
	}); err != nil {
		t.Fatalf("promote box to base: %v", err)
	}
	var baseCount int
	if err := pool.QueryRow(ctx,
		`SELECT count(*) FROM product_units WHERE product_id = $1 AND is_base`, product.ID).Scan(&baseCount); err != nil {
		t.Fatal(err)
	}
	if baseCount != 1 {
		t.Fatalf("base units = %d, want 1", baseCount)
	}

	// restore the original base/box configuration for the barcode assertions
	if _, err := svc.UpdateProductUnit(ctx, product.ID, box.ID, catalog.ProductUnitInput{
		UnitID:           boxUnit,
		ConversionFactor: decimal.NewFromInt(12),
		SellingPrice:     &boxPrice,
		MinPurchase:      decimal.NewFromInt(2),
		IsActive:         true,
	}); err != nil {
		t.Fatalf("restore box unit: %v", err)
	}
	if _, err := svc.UpdateProductUnit(ctx, product.ID, base.ID, catalog.ProductUnitInput{
		UnitID:           baseUnit,
		ConversionFactor: decimal.NewFromInt(1),
		MinPurchase:      decimal.NewFromInt(1),
		IsBase:           true,
		IsActive:         true,
	}); err != nil {
		t.Fatalf("restore base unit: %v", err)
	}

	pieceBarcode := unique("BC")
	boxBarcode := unique("BC")
	if _, err := svc.CreateProductBarcode(ctx, product.ID, catalog.ProductBarcodeInput{
		Barcode: pieceBarcode, ProductUnitID: &base.ID, IsPrimary: true,
	}); err != nil {
		t.Fatalf("create piece barcode: %v", err)
	}
	if _, err := svc.CreateProductBarcode(ctx, product.ID, catalog.ProductBarcodeInput{
		Barcode: boxBarcode, ProductUnitID: &box.ID, IsPrimary: true,
	}); err != nil {
		t.Fatalf("create box barcode: %v", err)
	}

	var primaryCount int
	if err := pool.QueryRow(ctx,
		`SELECT count(*) FROM product_barcodes WHERE product_id = $1 AND is_primary`, product.ID).Scan(&primaryCount); err != nil {
		t.Fatal(err)
	}
	if primaryCount != 1 {
		t.Fatalf("primary barcodes = %d, want 1", primaryCount)
	}

	if _, err := svc.CreateProductBarcode(ctx, product.ID, catalog.ProductBarcodeInput{
		Barcode: boxBarcode,
	}); !errors.Is(err, catalog.ErrDuplicateBarcode) {
		t.Fatalf("duplicate barcode: got %v, want ErrDuplicateBarcode", err)
	}

	scan, err := svc.LookupBarcode(ctx, boxBarcode)
	if err != nil {
		t.Fatalf("lookup box barcode: %v", err)
	}
	if scan.ProductID != product.ID || scan.ProductCode != product.Code {
		t.Fatalf("scan resolved %d/%s, want %d/%s", scan.ProductID, scan.ProductCode, product.ID, product.Code)
	}
	if scan.ProductUnit == nil {
		t.Fatal("box scan did not resolve a configured unit")
	}
	if !scan.ProductUnit.ConversionFactor.Equal(decimal.NewFromInt(12)) {
		t.Errorf("conversion_factor = %s, want 12", scan.ProductUnit.ConversionFactor)
	}
	if scan.ProductUnit.SellingPrice == nil || !scan.ProductUnit.SellingPrice.Equal(boxPrice) {
		t.Errorf("unit selling_price = %v, want %s", scan.ProductUnit.SellingPrice, boxPrice)
	}
	if !scan.ProductUnit.MinPurchase.Equal(decimal.NewFromInt(2)) {
		t.Errorf("min_purchase = %s, want 2", scan.ProductUnit.MinPurchase)
	}

	pieceScan, err := svc.LookupBarcode(ctx, pieceBarcode)
	if err != nil {
		t.Fatalf("lookup piece barcode: %v", err)
	}
	if pieceScan.ProductUnit == nil || !pieceScan.ProductUnit.ConversionFactor.Equal(decimal.NewFromInt(1)) {
		t.Fatalf("piece scan unit = %+v, want conversion factor 1", pieceScan.ProductUnit)
	}
	if !pieceScan.ProductUnit.IsBase {
		t.Error("piece scan unit is_base = false, want true")
	}
	if pieceScan.ProductUnit.SellingPrice != nil {
		t.Errorf("piece unit selling_price = %v, want null inheritance", pieceScan.ProductUnit.SellingPrice)
	}

	if err := svc.DeleteBrand(ctx, brand.ID); !errors.Is(err, catalog.ErrInUse) {
		t.Fatalf("delete brand in use: got %v, want ErrInUse", err)
	}
}
