package order_usecase

import (
	_const "chonho/const"
	"chonho/internal/api-v1/entity"
	"chonho/internal/api-v1/repository/address_repository"
	"chonho/internal/api-v1/repository/order_repository"
	"chonho/internal/api-v1/repository/product_repository"
	"chonho/internal/api-v1/usecase/notification_usecase"
	_error "chonho/pkg/c_error"
	"chonho/pkg/model"
	"encoding/json"
	"github.com/reactivex/rxgo/observable"
)

type UseCase interface {
	CreateOrder(userId int64, req entity.CreateOrderReq) ([]entity.CreateOrderRes, error)
	CancelOrder(userId int64, orderId int64) error
	ProvePayment(userId int64, orderId int64, req entity.ProvePaymentRequest) error
	ConfirmReceived(userId int64, orderId int64) error
	GetBuyerOrder(userId int64, orderId int64) (*entity.BuyerOrderDetail, error)
	GetBuyerOrders(userId int64, groupStatus int) ([]entity.BuyerOrderList, error)
}

type useCase struct {
	notify      notification_usecase.NotificationUseCase
	orderRepo   order_repository.OrderRepository
	productRepo product_repository.ProductRepository
	addressRepo address_repository.AddressRepository
}

func NewOrderUseCase(orderRepo order_repository.OrderRepository, productRepo product_repository.ProductRepository, addressRepo address_repository.AddressRepository, notification notification_usecase.NotificationUseCase) UseCase {
	return &useCase{
		orderRepo:   orderRepo,
		productRepo: productRepo,
		addressRepo: addressRepo,
		notify:      notification,
	}
}

func (uc *useCase) CreateOrder(userId int64, req entity.CreateOrderReq) ([]entity.CreateOrderRes, error) {
	addr, err := uc.addressRepo.First(address_repository.AddressFilter{
		Id:     req.ShippingAddressId,
		UserId: userId,
	})
	if err != nil {
		return nil, err
	}
	if addr == nil {
		return nil, _error.New(_const.OrderInvalidShippingAddress, "invalid shipping address")
	}

	var obs []observable.Observable
	var orderRes []entity.CreateOrderRes
	for index, _ := range req.StoreOrders {
		orderItem := req.StoreOrders[index]
		obs = append(obs, observable.Start(func() interface{} {
			return uc.createStoreOrder(userId, req.ShippingAddressId, orderItem)
		}))
	}
	results := <-observable.ZipAll(obs...)
	for _, result := range results.([]interface{}) {
		orderRes = append(orderRes, result.(entity.CreateOrderRes))
	}

	return orderRes, nil
}

func (uc *useCase) createStoreOrder(userId int64, shippingAddressId int64, storeOrder entity.StoreOrder) entity.CreateOrderRes {

	productIdItemMap, productIds := uc.combineCreateOrderItem(storeOrder)

	totalPrice := 0
	isQuoteAfterOrdering := false
	totalItem := 0
	// TODO validate product and cal total price
	products, err := uc.productRepo.FindStoreProduct(product_repository.ProductFilter{
		StoreId:    storeOrder.StoreId,
		ProductIds: productIds,
	})
	if err != nil {
		return entity.CreateOrderRes{
			StoreId: storeOrder.StoreId,
			Success: false,
			Error:   err.Error(),
		}
	}
	avaiProductMap := map[int64]entity.FullStoreProduct{}
	for _, product := range products {
		// check product inventory
		if product.Stock < productIdItemMap[product.ProductId].Quantity {
			return entity.CreateOrderRes{
				StoreId: storeOrder.StoreId,
				Success: false,
				Error:   _error.New(_const.ProductOutOfStock, "product out of stock").Error(),
			}
		}

		// check quote after ordering
		if product.IsQuoteAfterOrdering {
			isQuoteAfterOrdering = product.IsQuoteAfterOrdering
		}

		avaiProductMap[product.ProductId] = product
	}

	for productId, orderItem := range productIdItemMap {
		if product, exist := avaiProductMap[productId]; exist {
			totalPrice += product.Price * orderItem.Quantity
			totalItem += orderItem.Quantity
		} else {
			return entity.CreateOrderRes{
				StoreId: storeOrder.StoreId,
				Success: false,
				Error:   _error.New(_const.ProductOutOfStock, "product out of stock").Error(),
			}
		}
	}

	// TODO save order
	order := model.Order{
		UserId:               userId,
		StoreId:              storeOrder.StoreId,
		ShippingAddressId:    shippingAddressId,
		Price:                totalPrice,
		TotalItems:           totalItem,
		IsQuoteAfterOrdering: isQuoteAfterOrdering,
		Status:               _const.OrderStatusNew,
		PaymentStatus:        _const.PaymentStatusWaiting,
	}

	if err := uc.orderRepo.CreateOrder(&order, 0); err != nil {
		return entity.CreateOrderRes{
			StoreId: storeOrder.StoreId,
			Success: false,
			Error:   err.Error(),
		}
	}

	// TODO save order item
	var orderItems []model.OrderItem
	for productId, item := range productIdItemMap {

		tmpItem := model.OrderItem{
			OrderId:              order.Id,
			ProductId:            productId,
			Quantity:             float64(item.Quantity),
			QuoteQuantity:        float64(item.Quantity),
			Note:                 item.Note,
			Name:                 avaiProductMap[productId].Name,
			Thumbnail:            avaiProductMap[productId].Thumbnail,
			Price:                avaiProductMap[productId].Price,
			SubtotalPrice:        avaiProductMap[productId].Price * item.Quantity,
			Status:               _const.OrderItemStatusValid,
			DeliveryType:         avaiProductMap[productId].DeliveryType,
			DeliveryCommitTime:   avaiProductMap[productId].DeliveryCommitTime,
			IsQuoteAfterOrdering: avaiProductMap[productId].IsQuoteAfterOrdering,
		}

		productData, _ := json.Marshal(avaiProductMap[productId])
		tmpItem.Product = string(productData)

		orderItems = append(orderItems, tmpItem)

	}
	err = uc.orderRepo.AddOrderItem(&orderItems)
	if err != nil {
		return entity.CreateOrderRes{
			StoreId: storeOrder.StoreId,
			Success: false,
			Error:   err.Error(),
		}
	}

	// only hole inventory when seller confirm
	if isQuoteAfterOrdering {
		return entity.CreateOrderRes{
			OrderId:              order.Id,
			OrderCode:            order.Code,
			StoreId:              storeOrder.StoreId,
			IsQuoteAfterOrdering: isQuoteAfterOrdering,
			TotalPrice:           totalPrice,
			Status:               order.Status,
			Success:              true,
		}
	}

	// hold inventory => if fail canceled order
	productQuantityMap := map[int64]int{}
	for productId, item := range productIdItemMap {
		productQuantityMap[productId] = item.Quantity
	}
	err = uc.productRepo.HoldOrderProduct(storeOrder.StoreId, order.Id, productQuantityMap)
	if err != nil {
		uc.orderRepo.UpdateOrderInfo(order_repository.OrderFilter{
			Id: order.Id,
		}, map[string]interface{}{
			"status":          _const.OrderStatusCanceled,
			"canceled_reason": _const.OrderCanceledReasonProductOutOfStock,
		})
		return entity.CreateOrderRes{
			StoreId: storeOrder.StoreId,
			Success: false,
			Error:   err.Error(),
		}
	}

	go uc.notify.NotifyNewOrder(order.Id)

	return entity.CreateOrderRes{
		OrderId:              order.Id,
		OrderCode:            order.Code,
		StoreId:              storeOrder.StoreId,
		IsQuoteAfterOrdering: false,
		TotalPrice:           totalPrice,
		Status:               order.Status,
		Success:              true,
	}
}

func (uc *useCase) combineCreateOrderItem(req entity.StoreOrder) (productIdItemMap map[int64]entity.OrderItem, productIds []int64) {
	productIdItemMap = map[int64]entity.OrderItem{}
	for _, item := range req.Items {
		productIds = append(productIds, item.ProductId)
		existQuantity := 0
		if i, exist := productIdItemMap[item.ProductId]; exist {
			existQuantity = i.Quantity
		}

		productIdItemMap[item.ProductId] = entity.OrderItem{
			ProductId: item.ProductId,
			Quantity:  existQuantity + item.Quantity,
			Note:      item.Note,
		}
	}
	return productIdItemMap, productIds
}

func (uc *useCase) ConfirmReceived(userId int64, orderId int64) error {
	// TODO check order status
	row, err := uc.orderRepo.UpdateOrderInfo(order_repository.OrderFilter{
		Id:     orderId,
		UserId: userId,
	}, map[string]interface{}{
		"status": _const.OrderStatusReceived,
	})

	if err != nil {
		return err
	}

	if row == 0 {
		return _error.New(_const.OrderNotFound, "order not found")
	}

	return nil
}

func (uc *useCase) CancelOrder(userId int64, orderId int64) error {
	// TODO check order status
	row, err := uc.orderRepo.UpdateOrderInfo(order_repository.OrderFilter{
		Id:     orderId,
		UserId: userId,
	}, map[string]interface{}{
		"status": _const.OrderStatusRequestCancel,
	})

	if err != nil {
		return err
	}

	if row == 0 {
		return _error.New(_const.OrderNotFound, "order not found")
	}
	go uc.notify.NotifyCancelOrder(orderId, notification_usecase.CancelFromBuyer)

	return nil
}

func (uc *useCase) ProvePayment(userId int64, orderId int64, req entity.ProvePaymentRequest) error {
	// TODO check order status, validate image url
	row, err := uc.orderRepo.UpdateOrderInfo(order_repository.OrderFilter{
		Id:     orderId,
		UserId: userId,
	}, map[string]interface{}{
		"payment_status": _const.PaymentStatusSuccess,
		"payment_proof":  req.ImageUrl,
	})
	if err != nil {
		return err
	}
	if row == 0 {
		return _error.New(_const.OrderNotFound, "order not found")
	}
	go uc.notify.NotifyProvePayment(orderId)
	return nil
}

func (uc *useCase) GetBuyerOrder(userId int64, orderId int64) (*entity.BuyerOrderDetail, error) {
	return uc.orderRepo.GetBuyerOrder(order_repository.BuyerOrderFilter{
		Id:     orderId,
		UserId: userId,
	})
}

func (uc *useCase) GetBuyerOrders(userId int64, groupStatus int) ([]entity.BuyerOrderList, error) {
	return uc.orderRepo.GetBuyerOrders(order_repository.BuyerOrderFilter{
		UserId:      userId,
		GroupStatus: groupStatus,
	})
}
