package integration

import (
	"context"
	"errors"
	"testing"

	"github.com/jackc/pgx/v5/pgxpool"
	"github.com/shopspring/decimal"

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

// seedGeneratedCashflow inserts a cashflow row with a reference directly:
// Arus Kas is manual-only now (POS creates no cashflow), but reversal
// semantics for reference-bearing rows still apply.
func seedGeneratedCashflow(t *testing.T, pool *pgxpool.Pool, invoiceID int64, amount decimal.Decimal) int64 {
	t.Helper()
	var id int64
	if err := pool.QueryRow(context.Background(),
		`INSERT INTO cashflows
		   (transaction_number, type, category, transaction_date, amount,
		    reference_type, reference_id)
		 VALUES ($1, 'income', 'sales', CURRENT_DATE, $2,
		        'App\Models\Invoice', $3)
		 RETURNING id`,
		unique("CF"), amount, invoiceID).Scan(&id); err != nil {
		t.Fatal(err)
	}
	return id
}

func TestCashflowReversal(t *testing.T) {
	pool := testPool(t)
	setUp(t, pool)
	// platformCtx, not a bare context: the cashflow surface is fail-closed since
	// migration 000104, so a request with no store scope attached is refused
	// rather than treated as unrestricted. This test is about reversal semantics
	// (the sign flip, the reversal_of link, the at-most-once guard), so it says
	// what it means — a caller who may act on any outlet. The fail-closed
	// behaviour itself is asserted in internal/finance's
	// TestCashflowOutletScoping_NoStoreScopeIsRefused, not here.
	ctx := auth.WithStoreScope(context.Background(), auth.NewStoreScope(nil))
	fin := finance.NewService(finance.NewRepository(pool), seqAdapter{pool}, wib)

	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, "5")
	salesSvc := salesService(pool, inventory.NewService(inventory.NewRepository(pool), seqAdapter{pool}, wib))
	_, 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("seed POS: %v", err)
	}
	cfID := seedGeneratedCashflow(t, pool, invc.ID, invc.TotalAmount)

	rev, err := fin.ReverseCashflow(ctx, cfID, finance.CashflowReverseInput{Reason: nil})
	if err != nil {
		t.Fatalf("ReverseCashflow: %v", err)
	}
	if rev.Type != "expense" {
		t.Errorf("reversal type = %s, want expense", rev.Type)
	}
	if rev.Amount.String() != invc.TotalAmount.String() {
		t.Errorf("reversal amount = %s, want %s", rev.Amount, invc.TotalAmount)
	}
	if rev.ReversalOf == nil || *rev.ReversalOf != cfID {
		t.Errorf("reversal_of = %v, want %d", rev.ReversalOf, cfID)
	}
	if rev.ReferenceID == nil || *rev.ReferenceID != invc.ID {
		t.Errorf("reversal reference_id = %v, want %d", rev.ReferenceID, invc.ID)
	}
	if rev.Category != "sales" {
		t.Errorf("reversal category = %s, want sales", rev.Category)
	}
	if rev.Description == nil || len(*rev.Description) == 0 {
		t.Error("reversal description missing")
	}
	if rev.TransactionNumber == "" {
		t.Error("reversal transaction_number empty")
	}

	// A reversal row is itself never reversible: migration 000019 states a
	// generated entry can be reversed at most once and reversal rows are
	// never reversed. Corrections go through a new adjustment (§18).
	if _, err := fin.ReverseCashflow(ctx, rev.ID, finance.CashflowReverseInput{}); !errors.Is(err, finance.ErrAlreadyReversed) {
		t.Fatalf("reverse of reversal error = %v, want ErrAlreadyReversed", err)
	}

	// A manual (non-generated) cashflow cannot be reversed.
	var manualID int64
	if err := pool.QueryRow(ctx,
		`INSERT INTO cashflows (transaction_number, type, category, transaction_date, amount)
		 VALUES ($1, 'income', 'other', CURRENT_DATE, 10) RETURNING id`,
		unique("CF")).Scan(&manualID); err != nil {
		t.Fatal(err)
	}
	if _, err := fin.ReverseCashflow(ctx, manualID, finance.CashflowReverseInput{}); !errors.Is(err, finance.ErrNotGenerated) {
		t.Fatalf("manual reversal error = %v, want ErrNotGenerated", err)
	}
}
