package integration

import (
	"context"
	"fmt"
	"sync"
	"testing"
	"time"

	"github.com/TaruLab/keluargamart-backend/internal/auth"
	"github.com/TaruLab/keluargamart-backend/internal/inventory"
	"github.com/TaruLab/keluargamart-backend/internal/purchasing"
	"github.com/TaruLab/keluargamart-backend/internal/reports"
	"github.com/shopspring/decimal"
)

func TestMovementIntegrityReceiptManualOpnameAndReports(t *testing.T) {
	pool := testPool(t)
	setUp(t, pool)
	ctx := context.Background()
	wh := seedWarehouse(t, pool)
	other := seedWarehouse(t, pool)
	p := seedProduct(t, pool, seedCategory(t, pool), seedUnit(t, pool), "100")
	var supplier int64
	// Suppliers survive setUp, so the process-local unique counter cannot own this code.
	code := fmt.Sprintf("MOV-SUP-%d", time.Now().UnixNano())
	if err := pool.QueryRow(ctx, `INSERT INTO suppliers(code,name) VALUES($1,'Movement supplier') RETURNING id`, code).Scan(&supplier); err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() {
		// Remove only this fixture, with receipts before their purchase order and supplier.
		for _, query := range []string{
			`DELETE FROM goods_receipts WHERE purchase_order_id IN (SELECT id FROM purchase_orders WHERE supplier_id=$1)`,
			`DELETE FROM purchase_orders WHERE supplier_id=$1`,
			`DELETE FROM suppliers WHERE id=$1`,
		} {
			if _, err := pool.Exec(context.Background(), query, supplier); err != nil {
				t.Error(err)
				return
			}
		}
	})
	var po int64
	if err := pool.QueryRow(ctx, `INSERT INTO purchase_orders(po_number,supplier_id,warehouse_id,order_date,status) VALUES($1,$2,$3,current_date,'approved') RETURNING id`, unique("PO"), supplier, wh).Scan(&po); err != nil {
		t.Fatal(err)
	}
	if _, err := pool.Exec(ctx, `INSERT INTO purchase_order_items(purchase_order_id,product_id,quantity,unit_price,subtotal) VALUES($1,$2,10,100,1000)`, po, p.ID); err != nil {
		t.Fatal(err)
	}
	purch := purchasing.NewService(purchasing.NewRepository(pool), seqAdapter{pool}, wib)
	input := purchasing.GRInput{PurchaseOrderID: po, WarehouseID: wh, ReceivedDate: time.Now().In(wib).Format("2006-01-02"), Items: []purchasing.GRItemInput{{ProductID: p.ID, QuantityReceived: decimal.NewFromInt(10)}}}
	userID := seedUser(t, pool)
	ctx = auth.ToContext(ctx, &auth.User{ID: userID, Role: auth.RoleAdmin}, 0)
	receiptCtx := inventory.WithMovementRequest(ctx, "receipt-retry", "goods_receipt", input)
	// A real constraint failure after the receipt header must roll everything back.
	if _, err := pool.Exec(ctx, `ALTER TABLE stock_movements ADD CONSTRAINT integration_reject_receipt CHECK(reference_type IS DISTINCT FROM 'goods_receipt') NOT VALID`); err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() {
		if _, err := pool.Exec(context.Background(), `ALTER TABLE stock_movements DROP CONSTRAINT IF EXISTS integration_reject_receipt`); err != nil {
			t.Error(err)
		}
	})
	_, err := purch.CreateGR(receiptCtx, input)
	if _, dropErr := pool.Exec(ctx, `ALTER TABLE stock_movements DROP CONSTRAINT integration_reject_receipt`); dropErr != nil {
		t.Fatal(dropErr)
	}
	if err == nil {
		t.Fatal("receipt accepted a failed ledger insert")
	}
	var count int
	if err := pool.QueryRow(ctx, `SELECT count(*) FROM goods_receipts WHERE purchase_order_id=$1`, po).Scan(&count); err != nil || count != 0 {
		t.Fatalf("partial receipt count=%d err=%v", count, err)
	}
	if err := pool.QueryRow(ctx, `SELECT count(*) FROM stocks WHERE product_id=$1`, p.ID).Scan(&count); err != nil || count != 0 {
		t.Fatalf("partial stock count=%d err=%v", count, err)
	}
	gr, err := purch.CreateGR(receiptCtx, input)
	if err != nil {
		t.Fatal(err)
	}
	replay, err := purch.CreateGR(receiptCtx, input)
	if err != nil || replay.ID != gr.ID {
		t.Fatalf("receipt replay=%+v err=%v", replay, err)
	}
	if _, err := purch.CreateGR(ctx, input); err == nil {
		t.Fatal("fully received PO accepted duplicate quantity")
	}
	var received decimal.Decimal
	if err := pool.QueryRow(ctx, `SELECT sum(quantity) FROM stock_movements WHERE reference_type='goods_receipt' AND reference_id=$1`, gr.ID).Scan(&received); err != nil || !received.Equal(decimal.NewFromInt(10)) {
		t.Fatalf("receipt ledger=%s err=%v", received, err)
	}
	inv := inventory.NewService(inventory.NewRepository(pool), seqAdapter{pool}, wib)
	manual := inventory.StockInInput{ProductID: p.ID, WarehouseID: wh, Quantity: decimal.NewFromInt(3)}
	manualCtx := inventory.WithMovementRequest(ctx, "manual-retry", "stock_in", manual)
	var wg sync.WaitGroup
	failures := make(chan error, 2)
	for range 2 {
		wg.Add(1)
		go func() { defer wg.Done(); _, err := inv.StockIn(manualCtx, manual); failures <- err }()
	}
	wg.Wait()
	close(failures)
	for err := range failures {
		if err != nil {
			t.Fatal(err)
		}
	}
	if _, err := inv.StockOut(ctx, inventory.StockOutInput{ProductID: p.ID, WarehouseID: wh, Quantity: decimal.NewFromInt(2)}); err != nil {
		t.Fatal(err)
	}
	physical := decimal.NewFromInt(8)
	op, err := inv.OpnameCreate(ctx, inventory.OpnameInput{WarehouseID: wh, OpnameDate: time.Now().In(wib).Format("2006-01-02")})
	if err != nil {
		t.Fatal(err)
	}
	if err := inv.OpnameSetPhysical(ctx, op.ID, []inventory.OpnameItemInput{{ProductID: p.ID, PhysicalQty: &physical}}); err != nil {
		t.Fatal(err)
	}
	if _, err := inv.OpnameFinalize(ctx, op.ID); err != nil {
		t.Fatal(err)
	}
	if _, err := inv.OpnameFinalize(ctx, op.ID); err == nil {
		t.Fatal("duplicate opname finalize accepted")
	}
	f := inventory.MovementFilter{ProductID: p.ID, WarehouseID: wh, Page: 1, PerPage: 1}
	rows, total, err := inv.ListMovements(ctx, f)
	if err != nil || total != 4 || len(rows) != 1 || rows[0].Direction != "out" {
		t.Fatalf("ledger rows=%+v total=%d err=%v", rows, total, err)
	}
	summary, err := inv.MovementSummary(ctx, f)
	if err != nil || !summary.TotalIn.Equal(decimal.NewFromInt(13)) || !summary.TotalOut.Equal(decimal.NewFromInt(5)) {
		t.Fatalf("full summary=%+v err=%v", summary, err)
	}
	f.WarehouseID = other
	empty, total, err := inv.ListMovements(ctx, f)
	if err != nil || total != 0 || len(empty) != 0 {
		t.Fatalf("warehouse filter=%+v/%d err=%v", empty, total, err)
	}
	f.WarehouseID = wh
	f.DateFrom = "2099-01-01"
	_, total, err = inv.ListMovements(ctx, f)
	if err != nil || total != 0 {
		t.Fatalf("date filter total=%d err=%v", total, err)
	}
	card, err := reports.NewRepository(pool).StockCard(ctx, reports.StockCardFilter{ProductID: p.ID, WarehouseID: wh})
	if err != nil {
		t.Fatal(err)
	}
	in, out := decimal.Zero, decimal.Zero
	for _, row := range card {
		in = in.Add(row.In)
		out = out.Add(row.Out)
	}
	if !in.Equal(decimal.NewFromInt(13)) || !out.Equal(decimal.NewFromInt(5)) {
		t.Fatalf("stock card totals in=%s out=%s", in, out)
	}
}
