package distribution

import (
	"context"
	"errors"
	"testing"

	"github.com/shopspring/decimal"
)

// A delivery order in a terminal state must not accept a new dispatch.
//
// CreateDispatch checked the SHIPMENT's status (ErrShipmentClosed) but never
// the delivery order's own, and createDispatchLocked — the single point both
// the standalone and the inline-with-shipment paths go through — had no DO
// status guard at all. So:
//
//   - a CANCELLED DO was written back to in_transit, resurrecting it, and
//   - a COMPLETED DO got a fresh dispatch row plus an audit row reading
//     completed -> in_transit, and `if do.Status != StatusCompleted` only
//     skipped the status write, not the dispatch itself,
//   - either way the DO was handed to the LHPP grouper and put on a driver's
//     billing sheet.
//
// Cancelled and completed are the only terminal states. `ready` is not: a ready
// DO is fully shipped and WAITING to be dispatched, which is the path
// CreateDispatch exists to serve, so it must keep working.
func TestCreateDispatchRejectsTerminalDeliveryOrder(t *testing.T) {
	for _, status := range []string{StatusCancelled, StatusCompleted} {
		t.Run(status, func(t *testing.T) {
			f := newFakeStore()
			f.mustSO(t, 1, 2, "confirmed", 10)
			f.addStock(10, 2, decimal.NewFromInt(770), decimal.NewFromInt(770))
			svc := newService(f)
			o := f.mustDO(t, svc, 1, 10)

			// A shipment left open (no inline dispatch), so the shipment-level
			// guard is satisfied and only the DO-level guard can catch this.
			sh, err := svc.CreateShipment(context.Background(), o.ID, ShipmentInput{
				Items: []ShipmentItemInput{{ProductID: 10, ShippedQuantity: decimal.NewFromInt(770)}},
			}, 1)
			if err != nil {
				t.Fatalf("CreateShipment: %v", err)
			}

			// Force the DO terminal the way cancellation/completion would.
			f.dos[o.ID].Status = status

			dispatchesBefore := len(f.dispatches)
			if _, err := svc.CreateDispatch(context.Background(), o.ID,
				DispatchInput{ShipmentID: sh.ID, FleetID: 7, DriverID: int64Ptr(3)}, 1); !errors.Is(err, ErrCannotDispatch) {
				t.Fatalf("dispatch on a %s DO: err = %v, want ErrCannotDispatch", status, err)
			}

			if got := len(f.dispatches); got != dispatchesBefore {
				t.Errorf("dispatch rows = %d, want %d — a rejected dispatch must not leave a row behind", got, dispatchesBefore)
			}
			after, err := svc.GetDeliveryOrder(context.Background(), o.ID)
			if err != nil {
				t.Fatal(err)
			}
			if after.Status != status {
				t.Errorf("DO status = %s, want %s — a rejected dispatch must not change it", after.Status, status)
			}
		})
	}
}

// The guard must not be so broad that it disables the feature: a ready DO is
// fully shipped and waiting for a truck, and `in_transit` DOs are reassigned
// routinely (a second dispatch appends the next queue order).
func TestCreateDispatchAllowsReadyAndInTransit(t *testing.T) {
	for _, status := range []string{StatusReady, StatusInTransit, StatusPartiallyFulfilled, StatusDraft} {
		t.Run(status, func(t *testing.T) {
			f := newFakeStore()
			f.mustSO(t, 1, 2, "confirmed", 10)
			f.addStock(10, 2, decimal.NewFromInt(770), decimal.NewFromInt(770))
			svc := newService(f)
			o := f.mustDO(t, svc, 1, 10)

			sh, err := svc.CreateShipment(context.Background(), o.ID, ShipmentInput{
				Items: []ShipmentItemInput{{ProductID: 10, ShippedQuantity: decimal.NewFromInt(770)}},
			}, 1)
			if err != nil {
				t.Fatalf("CreateShipment: %v", err)
			}
			f.dos[o.ID].Status = status

			d, err := svc.CreateDispatch(context.Background(), o.ID,
				DispatchInput{ShipmentID: sh.ID, FleetID: 7}, 1)
			if err != nil {
				t.Fatalf("dispatch on a %s DO: %v — this state is dispatchable", status, err)
			}
			if d.ID == 0 {
				t.Error("dispatch has no id")
			}
			after, err := svc.GetDeliveryOrder(context.Background(), o.ID)
			if err != nil {
				t.Fatal(err)
			}
			if after.Status != StatusInTransit {
				t.Errorf("DO status = %s, want in_transit", after.Status)
			}
		})
	}
}
