package usecase

// shipping_usecase_test.go — Testes de unidade: cálculo de frete
//
// Cobre:
//   - Frete grátis por threshold
//   - Frete mínimo
//   - Endereço fora do raio de entrega
//   - Frete calculado por distância
//   - Sem configuração de shipping (frete grátis implícito)
//   - Retirada em loja disponível
//
// Execute com:
//   go test ./internal/usecase/... -run TestShipping -v

import (
	"context"
	"testing"

	"github.com/gofrs/uuid/v5"
	"github.com/saveinmed/backend-go/internal/domain"
	"github.com/saveinmed/backend-go/internal/infrastructure/mapbox"
	"github.com/saveinmed/backend-go/internal/infrastructure/notifications"
)

func newShippingTestService() (*Service, *MockRepository) {
	repo := NewMockRepository()
	notify := notifications.NewLoggerNotificationService()
	// Usar um cliente mapbox com token vazio — fará a requisição HTTP falhar,
	// o que faz o CalculateShipping usar haversine como fallback (linha 71 de shipping_usecase.go).
	// Isso evita nil pointer panic e permite testar a lógica de negócio localmente.
	mb := mapbox.New("test-token-invalid")
	svc := NewService(repo, nil, mb, notify, 0.05, 0.12, "test-secret", 0, "")
	return svc, repo
}

// baseSettings retorna uma configuração de frete padrão para testes.
func baseShippingSettings(vendorID uuid.UUID) domain.ShippingSettings {
	threshold := int64(10000) // frete grátis acima de R$100
	return domain.ShippingSettings{
		VendorID:                   vendorID,
		Active:                     true,
		MaxRadiusKm:                50.0,
		PricePerKmCents:            200,  // R$2/km
		MinFeeCents:                1500, // R$15 mínimo
		FreeShippingThresholdCents: &threshold,
		Latitude:                   -23.55, // São Paulo centro
		Longitude:                  -46.63,
	}
}

// =============================================================================
// Happy path — Cálculo de frete
// =============================================================================

func TestShippingCalculate_FreeByThreshold(t *testing.T) {
	svc, repo := newShippingTestService()
	vendorID := uuid.Must(uuid.NewV7())
	settings := baseShippingSettings(vendorID)
	repo.shippingSettings[vendorID] = settings

	// Compra acima do threshold → frete grátis
	buyerAddr := &domain.Address{
		Latitude:  -23.56, // próximo ao vendedor
		Longitude: -46.64,
	}
	cartTotal := int64(15000) // R$150 > threshold de R$100

	fee, _, err := svc.CalculateShipping(context.Background(), buyerAddr, vendorID, cartTotal)
	if err != nil {
		t.Fatalf("CalculateShipping não deve falhar para compra acima do threshold: %v", err)
	}
	if fee != 0 {
		t.Errorf("Frete deve ser GRÁTIS para cart_total=%d acima do threshold=%d, got fee=%d",
			cartTotal, *settings.FreeShippingThresholdCents, fee)
	}
}

func TestShippingCalculate_MinFee(t *testing.T) {
	svc, repo := newShippingTestService()
	vendorID := uuid.Must(uuid.NewV7())

	settings := baseShippingSettings(vendorID)
	settings.FreeShippingThresholdCents = nil // sem threshold de frete grátis
	settings.MaxRadiusKm = 1000              // raio gigante para não rejeitar
	settings.PricePerKmCents = 1             // tarifa mínima por km
	repo.shippingSettings[vendorID] = settings

	// Comprando muito perto — deveria pagar só o mínimo
	buyerAddr := &domain.Address{
		Latitude:  settings.Latitude + 0.001, // ~100m de distância
		Longitude: settings.Longitude,
	}

	fee, _, err := svc.CalculateShipping(context.Background(), buyerAddr, vendorID, 5000)
	if err != nil {
		t.Fatalf("CalculateShipping falhou: %v", err)
	}
	if fee < settings.MinFeeCents {
		t.Errorf("Taxa calculada (%d) deve ser pelo menos o mínimo (%d)", fee, settings.MinFeeCents)
	}
}

func TestShippingCalculate_OutOfRange(t *testing.T) {
	svc, repo := newShippingTestService()
	vendorID := uuid.Must(uuid.NewV7())

	settings := baseShippingSettings(vendorID)
	settings.MaxRadiusKm = 5 // raio pequeno
	settings.Latitude = -23.55
	settings.Longitude = -46.63
	repo.shippingSettings[vendorID] = settings

	// Comprador longe demais (Rio de Janeiro ~360km de SP)
	buyerAddr := &domain.Address{
		Latitude:  -22.90,
		Longitude: -43.17,
	}

	_, _, err := svc.CalculateShipping(context.Background(), buyerAddr, vendorID, 5000)
	if err == nil {
		t.Fatal("CalculateShipping deve retornar erro quando endereço está fora do raio de entrega")
	}
}

func TestShippingCalculate_NoSettings(t *testing.T) {
	svc, repo := newShippingTestService()
	vendorID := uuid.Must(uuid.NewV7())
	// Não adicionar settings → frete grátis implícito

	_ = repo // sem settings configurados

	buyerAddr := &domain.Address{
		Latitude:  -23.56,
		Longitude: -46.64,
	}

	fee, dist, err := svc.CalculateShipping(context.Background(), buyerAddr, vendorID, 5000)
	if err != nil {
		t.Fatalf("Sem configuração de frete deve retornar fee=0 sem erro: %v", err)
	}
	if fee != 0 {
		t.Errorf("Sem configuração, fee deve ser 0, got %d", fee)
	}
	if dist != 0 {
		t.Errorf("Sem configuração, dist deve ser 0, got %f", dist)
	}
}

func TestShippingCalculate_InactiveSettings(t *testing.T) {
	svc, repo := newShippingTestService()
	vendorID := uuid.Must(uuid.NewV7())

	settings := baseShippingSettings(vendorID)
	settings.Active = false // frete desativado pelo vendedor
	repo.shippingSettings[vendorID] = settings

	buyerAddr := &domain.Address{
		Latitude:  -23.56,
		Longitude: -46.64,
	}

	fee, _, err := svc.CalculateShipping(context.Background(), buyerAddr, vendorID, 5000)
	if err != nil {
		t.Fatalf("Frete inativo deve retornar fee=0 sem erro: %v", err)
	}
	if fee != 0 {
		t.Errorf("Frete inativo deve retornar fee=0, got %d", fee)
	}
}

func TestShippingCalculate_MissingCoordinates(t *testing.T) {
	svc, repo := newShippingTestService()
	vendorID := uuid.Must(uuid.NewV7())

	settings := baseShippingSettings(vendorID)
	repo.shippingSettings[vendorID] = settings

	// Endereço sem coordenadas → deve usar fee mínimo
	buyerAddr := &domain.Address{
		Latitude:  0,
		Longitude: 0,
	}

	fee, _, err := svc.CalculateShipping(context.Background(), buyerAddr, vendorID, 1000)
	if err != nil {
		t.Fatalf("Endereço sem coordenadas não deve retornar erro: %v", err)
	}
	// Sem coordenadas retorna o mínimo configurado
	if fee != settings.MinFeeCents {
		t.Errorf("Sem coordenadas, fee deve ser MinFeeCents=%d, got %d", settings.MinFeeCents, fee)
	}
}

// =============================================================================
// Opções de shipping (delivery + pickup)
// =============================================================================

func TestShippingCalculateOptions_DeliveryAndPickup(t *testing.T) {
	svc, repo := newShippingTestService()
	vendorID := uuid.Must(uuid.NewV7())

	settings := baseShippingSettings(vendorID)
	settings.PickupActive = true
	settings.PickupAddress = "Av. Paulista, 1000 - SP"
	settings.PickupHours = "08:00-18:00"
	repo.shippingSettings[vendorID] = settings

	options, err := svc.CalculateShippingOptions(context.Background(), vendorID, -23.56, -46.64, 5000)
	if err != nil {
		t.Fatalf("CalculateShippingOptions falhou: %v", err)
	}

	hasDelivery := false
	hasPickup := false
	for _, opt := range options {
		switch opt.Type {
		case "delivery":
			hasDelivery = true
		case "pickup":
			hasPickup = true
			if opt.ValueCents != 0 {
				t.Errorf("Pickup deve ter custo zero, got %d", opt.ValueCents)
			}
		}
	}

	if !hasDelivery {
		t.Error("Deve haver opção de entrega (delivery)")
	}
	if !hasPickup {
		t.Error("Pickup ativo deve gerar opção de retirada")
	}
}

func TestShippingCalculateOptions_PickupOnly(t *testing.T) {
	svc, repo := newShippingTestService()
	vendorID := uuid.Must(uuid.NewV7())

	settings := baseShippingSettings(vendorID)
	settings.Active = false // entrega desativada
	settings.PickupActive = true
	settings.PickupAddress = "Rua Teste, 123"
	settings.PickupHours = "09:00-17:00"
	repo.shippingSettings[vendorID] = settings

	options, err := svc.CalculateShippingOptions(context.Background(), vendorID, -23.56, -46.64, 5000)
	if err != nil {
		t.Fatalf("CalculateShippingOptions falhou: %v", err)
	}

	for _, opt := range options {
		if opt.Type == "delivery" {
			t.Error("Entrega desativada não deve gerar opção delivery")
		}
	}
}
