package usecase

import (
	"context"
	"errors"
	"fmt"

	"github.com/gofrs/uuid/v5"

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

// CreateOrder validates and persists a new order, calculates shipping, reserves
// stock and fires async notifications. Any pre-existing pending order for the
// same buyer is deleted first to prevent zombie drafts.
func (s *Service) CreateOrder(ctx context.Context, order *domain.Order) error {
	// Delete any stale pending orders from this buyer before creating a new one.
	orders, _, err := s.repo.ListOrders(ctx, domain.OrderFilter{BuyerID: &order.BuyerID, Limit: 100})
	if err == nil {
		for _, o := range orders {
			if o.Status == domain.OrderStatusPending {
				_ = s.DeleteOrder(ctx, o.ID)
			}
		}
	}

	order.ID = uuid.Must(uuid.NewV7())
	order.Status = domain.OrderStatusPending

	// Calculate and apply shipping cost.
	buyerAddr := &domain.Address{
		Latitude:  order.Shipping.Latitude,
		Longitude: order.Shipping.Longitude,
	}
	fee, dist, err := s.CalculateShipping(ctx, buyerAddr, order.SellerID, order.TotalCents)
	if err == nil {
		order.ShippingFeeCents = fee
		order.DistanceKm = dist
		order.TotalCents += fee
	} else {
		if err.Error() == "address out of delivery range" {
			return err
		}
	}

	if err := s.repo.CreateOrder(ctx, order); err != nil {
		return err
	}

	// Fire async notifications without blocking the HTTP response.
	go func() {
		buyer, err := s.repo.GetUser(context.Background(), order.BuyerID)
		if err != nil {
			return
		}

		users, _, _ := s.repo.ListUsers(context.Background(), domain.UserFilter{CompanyID: &order.SellerID, Limit: 1})
		var seller *domain.User
		if len(users) > 0 {
			seller = &users[0]
		} else {
			seller = &domain.User{Name: "Seller", Email: "seller@platform.com"}
		}

		s.notify.NotifyOrderCreated(context.Background(), order, buyer, seller)
	}()

	return nil
}

// ListOrders returns a paginated list of orders.
func (s *Service) ListOrders(ctx context.Context, filter domain.OrderFilter, page, pageSize int) (*domain.OrderPage, error) {
	if pageSize <= 0 {
		pageSize = 20
	}
	if page <= 0 {
		page = 1
	}
	filter.Limit = pageSize
	filter.Offset = (page - 1) * pageSize
	orders, total, err := s.repo.ListOrders(ctx, filter)
	if err != nil {
		return nil, err
	}
	return &domain.OrderPage{Orders: orders, Total: total, Page: page, PageSize: pageSize}, nil
}

// GetOrder retrieves an order by ID.
func (s *Service) GetOrder(ctx context.Context, id uuid.UUID) (*domain.Order, error) {
	return s.repo.GetOrder(ctx, id)
}

// UpdateOrderStatus enforces the order state machine, restores stock
// on cancellation, and fires async notifications.
func (s *Service) UpdateOrderStatus(ctx context.Context, id uuid.UUID, status domain.OrderStatus) error {
	order, err := s.repo.GetOrder(ctx, id)
	if err != nil {
		return err
	}

	// State machine transitions.
	switch order.Status {
	case domain.OrderStatusPending:
		if status != domain.OrderStatusPaid && status != domain.OrderStatusCancelled {
			return errors.New("invalid transition from Pending")
		}
	case domain.OrderStatusPaid:
		if status != domain.OrderStatusInvoiced && status != domain.OrderStatusShipped && status != domain.OrderStatusCancelled {
			return errors.New("invalid transition from Paid")
		}
	case domain.OrderStatusInvoiced:
		if status != domain.OrderStatusShipped && status != domain.OrderStatusCancelled {
			return errors.New("invalid transition from Invoiced")
		}
	case domain.OrderStatusShipped:
		if status != domain.OrderStatusDelivered {
			return errors.New("invalid transition from Shipped")
		}
	case domain.OrderStatusDelivered:
		if status != domain.OrderStatusCompleted {
			return errors.New("invalid transition from Delivered")
		}
	case domain.OrderStatusCompleted, domain.OrderStatusCancelled:
		return errors.New("order is in terminal state")
	}

	if err := s.repo.UpdateOrderStatus(ctx, id, status); err != nil {
		return err
	}

	// Restore stock when cancelling a non-shipped order.
	if status == domain.OrderStatusCancelled && (order.Status == domain.OrderStatusPending || order.Status == domain.OrderStatusPaid || order.Status == domain.OrderStatusInvoiced) {
		for _, item := range order.Items {
			if _, err := s.repo.AdjustInventory(ctx, item.ProductID, int64(item.Quantity), "Order Cancelled"); err != nil {
				// TODO: enqueue a reconciliation job instead of silently ignoring.
				fmt.Printf("WARN: failed to restore stock for item %s: %v\n", item.ProductID, err)
			}
		}
	}

	// Async buyer notification.
	go func() {
		updatedOrder, _ := s.repo.GetOrder(context.Background(), id)
		if updatedOrder == nil {
			return
		}
		buyer, err := s.repo.GetUser(context.Background(), updatedOrder.BuyerID)
		if err != nil {
			return
		}
		s.notify.NotifyOrderStatusChanged(context.Background(), updatedOrder, buyer)
	}()

	return nil
}

// DeleteOrder removes an order and restores reserved stock for non-shipped orders.
func (s *Service) DeleteOrder(ctx context.Context, id uuid.UUID) error {
	order, err := s.repo.GetOrder(ctx, id)
	if err != nil {
		return err
	}

	if order.Status == domain.OrderStatusPending || order.Status == domain.OrderStatusPaid || order.Status == domain.OrderStatusInvoiced {
		for _, item := range order.Items {
			if _, err := s.repo.AdjustInventory(ctx, item.ProductID, int64(item.Quantity), "Order Deleted"); err != nil {
				fmt.Printf("WARN: failed to restore stock for item %s: %v\n", item.ProductID, err)
			}
		}
	}

	return s.repo.DeleteOrder(ctx, id)
}

// UpdateOrderItems replaces the line-items and total on an existing order.
func (s *Service) UpdateOrderItems(ctx context.Context, orderID uuid.UUID, items []domain.OrderItem, totalCents int64) error {
	if _, err := s.repo.GetOrder(ctx, orderID); err != nil {
		return err
	}
	return s.repo.UpdateOrderItems(ctx, orderID, items, totalCents)
}
