package service

import (
	"context"
	"fmt"
	"slices"

	"route256/cart/internal/domain/entity"
	"route256/cart/internal/domain/model"
	"route256/cart/internal/infra/tracing"
)

//go:generate minimock -i Repository -o ./mock -s _mock.go
type Repository interface {
	AddItem(ctx context.Context, userID entity.UID, item *model.Item) error
	GetItemsByUserID(ctx context.Context, userID entity.UID) (entity.Cart, error)
	DeleteItem(ctx context.Context, userID entity.UID, sku entity.Sku) error
	DeleteItemsByUserID(ctx context.Context, userID entity.UID) error
}

type ProductService interface {
	GetProductBySku(ctx context.Context, sku entity.Sku) (*model.Product, error)
	GetProducts(ctx context.Context, skus []entity.Sku) ([]*model.Product, error)
}

type LomsService interface {
	OrderCreate(ctx context.Context, cart *model.Cart) (int64, error)
	StocksInfo(ctx context.Context, sku entity.Sku) (uint32, error)
}

type CartService struct {
	repository Repository

	productService ProductService
	lomsService    LomsService
}

func NewCartService(repository Repository, productService ProductService, lomsService LomsService) *CartService {
	return &CartService{
		repository:     repository,
		productService: productService,
		lomsService:    lomsService,
	}
}

func (s *CartService) AddItem(ctx context.Context, userID entity.UID, item *model.Item) error {
	ctx, span := tracing.Tracer().Start(ctx, "AddItem")
	defer span.End()

	if err := item.Validate(); err != nil {
		return fmt.Errorf("invalid requested values: %w", err)
	}

	if userID <= 0 {
		return fmt.Errorf("invalid userID")
	}

	_, err := s.productService.GetProductBySku(ctx, item.Product.Sku)
	if err != nil {
		return fmt.Errorf("productService.GetProductBySku: %w", err)
	}

	count, err := s.lomsService.StocksInfo(ctx, item.Product.Sku)
	if err != nil {
		return fmt.Errorf("lomsService.StocksInfo: %w", err)
	}

	if count < item.Count {
		return model.ErrNotEnoughStocks
	}

	if err := s.repository.AddItem(ctx, userID, item); err != nil {
		return fmt.Errorf("repository.AddItemToCart: %w", err)
	}

	return nil
}

// GetUserCart gets all user cart's item ids, gets the item description from the product-service
// and return a list of the collected items.
// In case of failed request to product-service, return nothing and error.
func (s *CartService) GetItemsByUserID(ctx context.Context, userID entity.UID) (*model.Cart, error) {
	ctx, span := tracing.Tracer().Start(ctx, "GetItemsByUserID")
	defer span.End()

	if userID <= 0 {
		return nil, fmt.Errorf("userID invalid")
	}

	cart, err := s.repository.GetItemsByUserID(ctx, userID)
	if err != nil {
		return nil, fmt.Errorf("repository.AddItemToCart: %w", err)
	}

	if len(cart.Items) == 0 {
		return nil, model.ErrCartNotFound
	}

	ctx, cancel := context.WithCancel(ctx)
	defer cancel()

	resultCart := &model.Cart{
		UserID:     userID,
		Items:      make([]*model.Item, 0, len(cart.Items)),
		TotalPrice: 0,
	}

	products, err := s.productService.GetProducts(ctx, cart.Items)
	if err != nil {
		return nil, err
	}

	for _, product := range products {
		cnt := cart.ItemCount[product.Sku]

		resultCart.Items = append(resultCart.Items, &model.Item{
			Product: product,
			Count:   cnt,
		})

		resultCart.TotalPrice += cnt * uint32(product.Price)
	}

	slices.SortStableFunc(resultCart.Items, func(a, b *model.Item) int {
		return int(a.Product.Sku - b.Product.Sku)
	})

	return resultCart, nil
}

func (s *CartService) DeleteItem(ctx context.Context, userID entity.UID, sku entity.Sku) error {
	ctx, span := tracing.Tracer().Start(ctx, "DeleteItem")
	defer span.End()

	if userID <= 0 {
		return fmt.Errorf("userID invalid")
	}

	if sku <= 0 {
		return fmt.Errorf("sku invalid")
	}

	if err := s.repository.DeleteItem(ctx, userID, sku); err != nil {
		return fmt.Errorf("repository.DeleteItemFromUserCart: %w", err)
	}

	return nil
}

func (s *CartService) DeleteItemsByUserID(ctx context.Context, userID entity.UID) error {
	ctx, span := tracing.Tracer().Start(ctx, "DeleteItemsByUserID")
	defer span.End()

	if userID <= 0 {
		return fmt.Errorf("userID invalid")
	}

	if err := s.repository.DeleteItemsByUserID(ctx, userID); err != nil {
		return fmt.Errorf("repository.DeleteUserCart: %w", err)
	}

	return nil
}

func (s *CartService) CheckoutUserCart(ctx context.Context, userID entity.UID) (int64, error) {
	ctx, span := tracing.Tracer().Start(ctx, "CheckoutUserCart")
	defer span.End()

	if userID <= 0 {
		return 0, fmt.Errorf("userID invalid")
	}

	cart, err := s.GetItemsByUserID(ctx, entity.UID(userID))
	if err != nil {
		return 0, err
	}

	orderID, err := s.lomsService.OrderCreate(ctx, cart)
	if err != nil {
		return 0, fmt.Errorf("lomsService.OrderCreate: %w", err)
	}

	if err := s.DeleteItemsByUserID(ctx, userID); err != nil {
		return 0, err
	}

	return orderID, nil
}
