package usecase

import (
	"context"
	"errors"
	"math"

	"github.com/gofrs/uuid/v5"

	"github.com/saveinmed/backend-go/internal/domain"
)

// CreateShipment persists a freight label for an order if not already present.
func (s *Service) CreateShipment(ctx context.Context, shipment *domain.Shipment) error {
	if _, err := s.repo.GetOrder(ctx, shipment.OrderID); err != nil {
		return err
	}
	shipment.ID = uuid.Must(uuid.NewV7())
	shipment.Status = "Label gerada"
	return s.repo.CreateShipment(ctx, shipment)
}

// GetShipmentByOrderID returns freight details for an order.
func (s *Service) GetShipmentByOrderID(ctx context.Context, orderID uuid.UUID) (*domain.Shipment, error) {
	return s.repo.GetShipmentByOrderID(ctx, orderID)
}

// ListShipments returns a paginated list of shipments matching the filter.
func (s *Service) ListShipments(ctx context.Context, filter domain.ShipmentFilter, page, pageSize int) (*domain.ShipmentPage, error) {
	if pageSize <= 0 {
		pageSize = 20
	}
	if page <= 0 {
		page = 1
	}
	filter.Limit = pageSize
	filter.Offset = (page - 1) * pageSize
	shipments, total, err := s.repo.ListShipments(ctx, filter)
	if err != nil {
		return nil, err
	}
	return &domain.ShipmentPage{Shipments: shipments, Total: total, Page: page, PageSize: pageSize}, nil
}

// GetShippingSettings returns the seller's shipping configuration.
func (s *Service) GetShippingSettings(ctx context.Context, vendorID uuid.UUID) (*domain.ShippingSettings, error) {
	return s.repo.GetShippingSettings(ctx, vendorID)
}

// UpsertShippingSettings creates or updates the seller's shipping configuration.
func (s *Service) UpsertShippingSettings(ctx context.Context, settings *domain.ShippingSettings) error {
	return s.repo.UpsertShippingSettings(ctx, settings)
}

// CalculateShipping computes the shipping fee and distance from the seller to
// the buyer's address. It uses Mapbox for road distance and falls back to
// haversine. Returns (feeCents, distanceKm, error).
func (s *Service) CalculateShipping(ctx context.Context, buyerAddress *domain.Address, sellerID uuid.UUID, cartTotalCents int64) (int64, float64, error) {
	settings, err := s.repo.GetShippingSettings(ctx, sellerID)
	if err != nil {
		return 0, 0, nil // No settings → free shipping
	}
	if settings == nil || !settings.Active {
		return 0, 0, nil
	}

	if buyerAddress.Latitude == 0 || buyerAddress.Longitude == 0 {
		return settings.MinFeeCents, 0, nil
	}

	distKm, err := s.mapbox.GetDrivingDistance(settings.Latitude, settings.Longitude, buyerAddress.Latitude, buyerAddress.Longitude)
	if err != nil {
		distKm = haversine(buyerAddress.Latitude, buyerAddress.Longitude, settings.Latitude, settings.Longitude)
	}

	if distKm > settings.MaxRadiusKm {
		return 0, distKm, errors.New("address out of delivery range")
	}

	if settings.FreeShippingThresholdCents != nil && cartTotalCents >= *settings.FreeShippingThresholdCents {
		return 0, distKm, nil
	}

	var fee int64
	switch {
	case settings.PricePerKmCents > 0:
		fee = int64(distKm * float64(settings.PricePerKmCents))
	case settings.MinFeeCents > 0:
		fee = settings.MinFeeCents
	}

	return fee, distKm, nil
}

// haversine calculates the great-circle distance between two points in km.
func haversine(lat1, lon1, lat2, lon2 float64) float64 {
	const R = 6371
	dLat := (lat2 - lat1) * (math.Pi / 180.0)
	dLon := (lon2 - lon1) * (math.Pi / 180.0)

	lat1Rad := lat1 * (math.Pi / 180.0)
	lat2Rad := lat2 * (math.Pi / 180.0)

	a := math.Sin(dLat/2)*math.Sin(dLat/2) +
		math.Sin(dLon/2)*math.Sin(dLon/2)*math.Cos(lat1Rad)*math.Cos(lat2Rad)
	c := 2 * math.Atan2(math.Sqrt(a), math.Sqrt(1-a))

	return R * c
}
