package sales

import (
	"context"
	"errors"
	"testing"
	"time"

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

func setupWarehouseTestService() (*Service, *fakeStore) {
	st := newFakeStore()
	svc := NewService(st, &fakeSeq{}, time.UTC)
	return svc, st
}

func warehouseContext() context.Context {
	u := &auth.User{
		ID:   10,
		Role: auth.RoleWarehouse,
		Permissions: auth.Permissions{
			"sales.sales-orders.view":   true,
			"sales.sales-orders.update": true,
		},
	}
	return auth.ToContext(context.Background(), u, 0)
}

func adminContext() context.Context {
	u := &auth.User{
		ID:   1,
		Role: auth.RoleAdmin,
		Permissions: auth.Permissions{
			"*": true,
		},
	}
	return auth.ToContext(context.Background(), u, 0)
}

func buyerContext() context.Context {
	u := &auth.User{
		ID:   20,
		Role: auth.RoleBuyer,
		Permissions: auth.Permissions{
			"sales.sales-orders.view": true,
		},
	}
	return auth.ToContext(context.Background(), u, 0)
}

func TestWarehouseRole_ListOrders_ExcludesUnapproved(t *testing.T) {
	svc, st := setupWarehouseTestService()

	// Seed orders with various statuses
	st.orders[1] = &SalesOrder{ID: 1, SoNumber: "SO-001", Status: "draft", TotalAmount: decimal.NewFromInt(1000)}
	st.orders[2] = &SalesOrder{ID: 2, SoNumber: "SO-002", Status: "pending_approval", TotalAmount: decimal.NewFromInt(2000)}
	st.orders[3] = &SalesOrder{ID: 3, SoNumber: "SO-003", Status: "blocked_overdue", TotalAmount: decimal.NewFromInt(3000)}
	st.orders[4] = &SalesOrder{ID: 4, SoNumber: "SO-004", Status: "confirmed", TotalAmount: decimal.NewFromInt(4000)}
	st.orders[5] = &SalesOrder{ID: 5, SoNumber: "SO-005", Status: "picking", TotalAmount: decimal.NewFromInt(5000)}
	st.orders[6] = &SalesOrder{ID: 6, SoNumber: "SO-006", Status: "shipped", TotalAmount: decimal.NewFromInt(6000)}
	st.orders[7] = &SalesOrder{ID: 7, SoNumber: "SO-007", Status: "delivered", TotalAmount: decimal.NewFromInt(7000)}

	// 1. Warehouse caller should only see confirmed, picking, shipped, delivered (4 orders)
	ctx := warehouseContext()
	orders, total, err := svc.List(ctx, SOFilter{})
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if total != 4 || len(orders) != 4 {
		t.Errorf("expected 4 approved orders for warehouse, got total=%d, len=%d", total, len(orders))
	}
	for _, o := range orders {
		if o.Status == "draft" || o.Status == "pending_approval" || o.Status == "blocked_overdue" {
			t.Errorf("warehouse must not see unapproved order %s with status %s", o.SoNumber, o.Status)
		}
	}

	// 2. Warehouse caller filtering by status=pending_approval should get 0 results
	pendingOrders, pendingTotal, err := svc.List(ctx, SOFilter{Status: "pending_approval"})
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if pendingTotal != 0 || len(pendingOrders) != 0 {
		t.Errorf("expected 0 pending orders for warehouse, got %d", pendingTotal)
	}

	// 3. Admin caller sees all 7 orders
	adminCtx := adminContext()
	allOrders, adminTotal, err := svc.List(adminCtx, SOFilter{})
	if err != nil {
		t.Fatalf("unexpected error for admin: %v", err)
	}
	if adminTotal != 7 || len(allOrders) != 7 {
		t.Errorf("expected 7 orders for admin, got %d", adminTotal)
	}
}

func TestWarehouseRole_GetOrder_UnapprovedReturnsNotFound(t *testing.T) {
	svc, st := setupWarehouseTestService()

	st.orders[1] = &SalesOrder{ID: 1, SoNumber: "SO-001", Status: "pending_approval"}
	st.orders[2] = &SalesOrder{ID: 2, SoNumber: "SO-002", Status: "draft"}
	st.orders[3] = &SalesOrder{ID: 3, SoNumber: "SO-003", Status: "confirmed"}

	ctx := warehouseContext()

	// Draft and pending_approval must return ErrNotFound to warehouse
	_, err := svc.Get(ctx, 1)
	if !errors.Is(err, ErrNotFound) {
		t.Errorf("expected ErrNotFound for pending_approval order, got %v", err)
	}

	_, err = svc.Get(ctx, 2)
	if !errors.Is(err, ErrNotFound) {
		t.Errorf("expected ErrNotFound for draft order, got %v", err)
	}

	// Confirmed order is accessible
	o, err := svc.Get(ctx, 3)
	if err != nil {
		t.Fatalf("expected confirmed order to be found, got error: %v", err)
	}
	if o.ID != 3 || o.Status != "confirmed" {
		t.Errorf("unexpected order returned: %+v", o)
	}
}

func TestWarehouseRole_CannotMutateOrApprove(t *testing.T) {
	svc, st := setupWarehouseTestService()

	st.orders[1] = &SalesOrder{ID: 1, SoNumber: "SO-001", Status: "pending_approval"}
	st.orders[2] = &SalesOrder{ID: 2, SoNumber: "SO-002", Status: "confirmed"}

	ctx := warehouseContext()

	// 1. Warehouse cannot approve / confirm
	_, err := svc.Confirm(ctx, 1)
	if !errors.Is(err, ErrNotFound) {
		t.Errorf("expected ErrNotFound on Confirm for warehouse, got %v", err)
	}

	// 2. Warehouse cannot cancel
	_, err = svc.Cancel(ctx, 2)
	if !errors.Is(err, ErrNotFound) {
		t.Errorf("expected ErrNotFound on Cancel for warehouse, got %v", err)
	}

	// 3. Warehouse cannot update order items
	_, err = svc.Update(ctx, 1, SOInput{})
	if !errors.Is(err, ErrNotFound) {
		t.Errorf("expected ErrNotFound on Update for warehouse, got %v", err)
	}

	// 4. Warehouse cannot delete order
	err = svc.Delete(ctx, 1)
	if !errors.Is(err, ErrNotFound) {
		t.Errorf("expected ErrNotFound on Delete for warehouse, got %v", err)
	}
}

func TestWarehouseRole_CanAdvanceFulfillment(t *testing.T) {
	svc, st := setupWarehouseTestService()

	st.orders[1] = &SalesOrder{ID: 1, SoNumber: "SO-001", Status: "confirmed"}
	ctx := warehouseContext()

	// confirmed -> picking
	o, err := svc.AdvanceFulfillment(ctx, 1, "picking")
	if err != nil {
		t.Fatalf("expected warehouse to advance to picking, got: %v", err)
	}
	if o.Status != "picking" {
		t.Errorf("expected status picking, got %s", o.Status)
	}

	// picking -> shipped
	o, err = svc.AdvanceFulfillment(ctx, 1, "shipped")
	if err != nil {
		t.Fatalf("expected warehouse to advance to shipped, got: %v", err)
	}
	if o.Status != "shipped" {
		t.Errorf("expected status shipped, got %s", o.Status)
	}

	// shipped -> delivered
	o, err = svc.AdvanceFulfillment(ctx, 1, "delivered")
	if err != nil {
		t.Fatalf("expected warehouse to advance to delivered, got: %v", err)
	}
	if o.Status != "delivered" {
		t.Errorf("expected status delivered, got %s", o.Status)
	}
}

// A paid e-commerce order rests at invoiced — the warehouse must still be
// able to pick and ship it; skipping a step stays invalid.
func TestWarehouseRole_AdvancesPaidOrderFromInvoiced(t *testing.T) {
	svc, st := setupWarehouseTestService()

	st.orders[1] = &SalesOrder{ID: 1, SoNumber: "SO-001", Status: "invoiced"}
	ctx := warehouseContext()

	o, err := svc.AdvanceFulfillment(ctx, 1, "picking")
	if err != nil {
		t.Fatalf("expected warehouse to advance paid (invoiced) order to picking, got: %v", err)
	}
	if o.Status != "picking" {
		t.Errorf("expected status picking, got %s", o.Status)
	}

	// invoiced -> shipped (skipping picking) stays invalid.
	st.orders[2] = &SalesOrder{ID: 2, SoNumber: "SO-002", Status: "invoiced"}
	_, err = svc.AdvanceFulfillment(ctx, 2, "shipped")
	if !errors.Is(err, ErrInvalidStatus) {
		t.Errorf("expected ErrInvalidStatus for invoiced -> shipped, got %v", err)
	}
}

// A buyer confirming receipt of their own shipped order is the storefront
// "Pesanan Diterima" action; buyer must not be able to drive the earlier
// warehouse-only steps (picking/shipped) through the same endpoint.
func TestBuyerRole_CanOnlyConfirmReceipt(t *testing.T) {
	svc, st := setupWarehouseTestService()

	st.orders[1] = &SalesOrder{ID: 1, SoNumber: "SO-001", Status: "shipped"}
	st.orders[2] = &SalesOrder{ID: 2, SoNumber: "SO-002", Status: "confirmed"}
	st.orders[3] = &SalesOrder{ID: 3, SoNumber: "SO-003", Status: "picking"}
	ctx := buyerContext()

	// shipped -> delivered: allowed
	o, err := svc.AdvanceFulfillment(ctx, 1, "delivered")
	if err != nil {
		t.Fatalf("expected buyer to confirm receipt (shipped->delivered), got: %v", err)
	}
	if o.Status != "delivered" {
		t.Errorf("expected status delivered, got %s", o.Status)
	}

	// confirmed -> picking: blocked (warehouse-only step)
	_, err = svc.AdvanceFulfillment(ctx, 2, "picking")
	if !errors.Is(err, ErrNotFound) {
		t.Errorf("expected ErrNotFound for buyer advancing to picking, got %v", err)
	}

	// picking -> shipped: blocked (warehouse-only step)
	_, err = svc.AdvanceFulfillment(ctx, 3, "shipped")
	if !errors.Is(err, ErrNotFound) {
		t.Errorf("expected ErrNotFound for buyer advancing to shipped, got %v", err)
	}

	// paid -> delivered: blocked for buyer (must flow paid → picking → shipped → delivered).
	st.orders[4] = &SalesOrder{ID: 4, SoNumber: "SO-004", Status: "paid"}
	_, err = svc.AdvanceFulfillment(ctx, 4, "delivered")
	if !errors.Is(err, ErrInvalidStatus) {
		t.Errorf("expected ErrInvalidStatus for buyer paid -> delivered jump, got %v", err)
	}

	// invoiced -> delivered: blocked for buyer for the same reason.
	st.orders[5] = &SalesOrder{ID: 5, SoNumber: "SO-005", Status: "invoiced"}
	_, err = svc.AdvanceFulfillment(ctx, 5, "delivered")
	if !errors.Is(err, ErrInvalidStatus) {
		t.Errorf("expected ErrInvalidStatus for buyer invoiced -> delivered jump, got %v", err)
	}
}

func TestWarehouseRole_AdvancesOrderWithStatusPaid(t *testing.T) {
	svc, st := setupWarehouseTestService()
	st.orders[1] = &SalesOrder{ID: 1, SoNumber: "SO-001", Status: "paid"}
	ctx := warehouseContext()

	o, err := svc.AdvanceFulfillment(ctx, 1, "picking")
	if err != nil {
		t.Fatalf("expected warehouse to advance paid order to picking, got: %v", err)
	}
	if o.Status != "picking" {
		t.Errorf("expected status picking, got %s", o.Status)
	}
}

func TestAdvanceFulfillment_UnpaidTransferOrderBlocked(t *testing.T) {
	svc, st := setupWarehouseTestService()
	ctx := warehouseContext()

	// 1. Unpaid transfer/gateway order (Total=50000, Paid=0, PaymentTerms=0, non-cash notes)
	gwNotes := "[Metode: gateway]"
	st.orders[1] = &SalesOrder{
		ID:           1,
		SoNumber:     "SO-GW-001",
		Status:       "confirmed",
		PaymentTerms: 0,
		TotalAmount:  decimal.NewFromInt(50000),
		PaidAmount:   decimal.Zero,
		Notes:        &gwNotes,
	}

	_, err := svc.AdvanceFulfillment(ctx, 1, "picking")
	if !errors.Is(err, ErrUnpaidOrderFulfillment) {
		t.Fatalf("expected ErrUnpaidOrderFulfillment for unpaid gateway order, got: %v", err)
	}

	// 2. Paid transfer order (Total=50000, Paid=50000)
	st.orders[2] = &SalesOrder{
		ID:           2,
		SoNumber:     "SO-GW-002",
		Status:       "confirmed",
		PaymentTerms: 0,
		TotalAmount:  decimal.NewFromInt(50000),
		PaidAmount:   decimal.NewFromInt(50000),
		Notes:        &gwNotes,
	}
	o2, err := svc.AdvanceFulfillment(ctx, 2, "picking")
	if err != nil {
		t.Fatalf("expected paid gateway order to advance to picking, got: %v", err)
	}
	if o2.Status != "picking" {
		t.Errorf("expected status picking, got %s", o2.Status)
	}

	// 3. Cash / COD order (Total=50000, Paid=0, notes [Metode: cash]) -> Allowed before payment
	cashNotes := "[Metode: cash]"
	st.orders[3] = &SalesOrder{
		ID:           3,
		SoNumber:     "SO-CASH-001",
		Status:       "confirmed",
		PaymentTerms: 0,
		TotalAmount:  decimal.NewFromInt(50000),
		PaidAmount:   decimal.Zero,
		Notes:        &cashNotes,
	}
	o3, err := svc.AdvanceFulfillment(ctx, 3, "picking")
	if err != nil {
		t.Fatalf("expected COD order to advance to picking prior to delivery payment, got: %v", err)
	}
	if o3.Status != "picking" {
		t.Errorf("expected status picking, got %s", o3.Status)
	}

	// 4. B2B Credit Terms (PaymentTerms=14, Total=50000, Paid=0) -> Allowed on credit terms
	st.orders[4] = &SalesOrder{
		ID:           4,
		SoNumber:     "SO-TOP-001",
		Status:       "confirmed",
		PaymentTerms: 14,
		TotalAmount:  decimal.NewFromInt(50000),
		PaidAmount:   decimal.Zero,
	}
	o4, err := svc.AdvanceFulfillment(ctx, 4, "picking")
	if err != nil {
		t.Fatalf("expected B2B credit order to advance to picking, got: %v", err)
	}
	if o4.Status != "picking" {
		t.Errorf("expected status picking, got %s", o4.Status)
	}
}

