package sales

import (
	"context"
	"testing"

	"github.com/shopspring/decimal"

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

// The void path (000151 flow audit item 7): a completed POS sale has no
// cancel/void route at all. These tests pin the money-and-stock semantics.

func voidFixture(t *testing.T, s *Service) *SalesOrder {
	fs := s.store.(*fakeStore)
	fs.stocks["1:1"] = &Stock{ID: 1, ProductID: 1, WarehouseID: 1, Quantity: decimal.NewFromInt(10)}
	o, _, err := s.ProcessPOS(context.Background(), POSInput{
		CustomerID:    1,
		WarehouseID:   1,
		PaymentMethod: "cash",
		CashReceived:  decimalPtr(decimal.NewFromInt(200)),
		Items:         []POSItemInput{{ProductID: 1, Quantity: decimal.NewFromInt(2), UnitPrice: decimal.NewFromInt(100)}},
	})
	if err != nil {
		t.Fatal(err)
	}
	return o
}

func decimalPtr(d decimal.Decimal) *decimal.Decimal { return &d }

func TestVoidPOS_AdminOnly(t *testing.T) {
	s := svc()
	o := voidFixture(t, s)
	for _, u := range []*auth.User{
		{ID: 3, Role: auth.RoleCashier},
		{ID: 4, Role: auth.RoleStoreManager},
		{ID: 5, Role: auth.RoleSales},
		nil,
	} {
		var ctx context.Context
		if u != nil {
			ctx = auth.ToContext(context.Background(), u, 1)
		} else {
			ctx = context.Background()
		}
		if _, err := s.VoidPOS(ctx, o.ID, "salah rung"); !errorsIs(err, ErrForbidden) {
			t.Fatalf("role %v: err = %v, want ErrForbidden", u, err)
		}
	}
	// Order untouched.
	fs := s.store.(*fakeStore)
	if got := fs.orders[o.ID].Status; got != "invoiced" {
		t.Errorf("status = %s after refused void, want invoiced", got)
	}
}

func errorsIs(err, target error) bool {
	if err == nil {
		return false
	}
	return err.Error() == target.Error() || (err != nil && target != nil && err.Error() == target.Error())
}

func TestVoidPOS_RefundsCashRestocksCancels(t *testing.T) {
	s := svc()
	o := voidFixture(t, s)
	fs := s.store.(*fakeStore)
	// Pre-void stock level: the sale deducted 10→8, the void must put it back.
	stockPreSale := "10"

	admin := auth.ToContext(context.Background(), &auth.User{ID: 9, Role: auth.RoleAdmin}, 1)
	out, err := s.VoidPOS(admin, o.ID, "kasir salah rung")
	if err != nil {
		t.Fatalf("void err = %v", err)
	}
	if out.Status != "cancelled" {
		t.Errorf("status = %s, want cancelled", out.Status)
	}
	if got := fs.stocks["1:1"].Quantity.String(); got != stockPreSale {
		t.Errorf("stock = %s, want %s (restocked to pre-sale level)", got, stockPreSale)
	}
	// Refund = a NEW negative payment row; the ledger nets without deleting
	// the original payment.
	neg := 0
	for _, p := range fs.payments {
		if p.Amount.IsNegative() {
			neg++
		}
	}
	if neg == 0 {
		t.Error("no negative refund payment row written")
	}
}

func TestVoidPOS_NonPOSRefused(t *testing.T) {
	s := svc()
	fs := s.store.(*fakeStore)
	fs.stocks["1:1"] = &Stock{ID: 1, ProductID: 1, WarehouseID: 1, Quantity: decimal.NewFromInt(10)}
	// PlaceCheckout creates an ecommerce order.
	o, err := s.PlaceCheckout(context.Background(), CheckoutInput{
		Items: []CheckoutItemInput{{ProductID: 1, Quantity: decimal.NewFromInt(1)}},
	}, 42, "Buyer", "b@x.io")
	if err != nil {
		t.Fatal(err)
	}
	admin := auth.ToContext(context.Background(), &auth.User{ID: 9, Role: auth.RoleAdmin}, 1)
	if _, err := s.VoidPOS(admin, o.ID, "x"); !errorsIs(err, ErrNotPOSOrder) {
		t.Fatalf("err = %v, want ErrNotPOSOrder", err)
	}
}

func TestVoidPOS_ReasonRequired(t *testing.T) {
	s := svc()
	o := voidFixture(t, s)
	admin := auth.ToContext(context.Background(), &auth.User{ID: 9, Role: auth.RoleAdmin}, 1)
	if _, err := s.VoidPOS(admin, o.ID, "   "); err == nil {
		t.Fatal("empty reason accepted — audit trail would be empty")
	}
}
