package cart

import (
	"fmt"
	"slices"

	messages "git.tornberg.me/go-cart-actor/pkg/messages"
)

// mutation_set_delivery.go
//
// Registers the SetDelivery mutation.
//
// Semantics (mirrors legacy switch logic):
//   - If the payload specifies an explicit list of item IDs (payload.Items):
//   - Each referenced cart line must exist.
//   - None of the referenced items may already belong to a delivery.
//   - Only those items are associated with the new delivery.
//   - If payload.Items is empty:
//   - All items currently without any delivery are associated with the new delivery.
//   - A new delivery line is created with:
//   - Auto-incremented delivery ID (cart-local)
//   - Provider from payload
//   - Fixed price (currently hard-coded: 4900 minor units) – adjust as needed
//   - Optional PickupPoint copied from payload
//   - Cart totals are recalculated (WithTotals)
//
// Error cases:
//   - Referenced item does not exist
//   - Referenced item already has a delivery
//   - No items qualify (resulting association set empty) -> returns error (prevents creating empty delivery)
//
// Concurrency:
//   - Uses g.mu to protect lastDeliveryId increment and append to Deliveries slice.
//     Item scans are read-only and performed outside the lock for simplicity;
//     if stricter guarantees are needed, widen the lock section.
//
// Future extension points:
//   - Variable delivery pricing (based on weight, distance, provider, etc.)
//   - Validation of provider codes
//   - Multi-currency delivery pricing

func SetDelivery(g *CartGrain, m *messages.SetDelivery) error {
	if m == nil {
		return fmt.Errorf("SetDelivery: nil payload")
	}
	if m.Provider == "" {
		return fmt.Errorf("SetDelivery: provider is empty")
	}

	withDelivery := g.ItemsWithDelivery()
	targetItems := make([]uint32, 0)

	if len(m.Items) == 0 {
		// Use every item currently without a delivery
		targetItems = append(targetItems, g.ItemsWithoutDelivery()...)
	} else {
		// Validate explicit list
		for _, id64 := range m.Items {
			id := uint32(id64)
			found := false
			for _, it := range g.Items {
				if it.Id == id {
					found = true
					break
				}
			}
			if !found {
				return fmt.Errorf("SetDelivery: item id %d not found", id)
			}
			if slices.Contains(withDelivery, id) {
				return fmt.Errorf("SetDelivery: item id %d already has a delivery", id)
			}
			targetItems = append(targetItems, id)
		}
	}

	if len(targetItems) == 0 {
		return fmt.Errorf("SetDelivery: no eligible items to attach")
	}

	// Append new delivery
	g.mu.Lock()
	g.lastDeliveryId++
	newId := g.lastDeliveryId
	g.Deliveries = append(g.Deliveries, &CartDelivery{
		Id:          newId,
		Provider:    m.Provider,
		PickupPoint: m.PickupPoint,
		Price:       *NewPriceFromIncVat(4900, 25.0),
		Items:       targetItems,
	})
	g.mu.Unlock()

	return nil
}
