package service

import (
	"errors"
	"fmt"
	"github.com/UpmeshLTD/urus-aio/infrastructure/db/entities"
	"github.com/UpmeshLTD/urus-aio/infrastructure/utils"

	gin2 "github.com/UpmeshLTD/urus-aio/infrastructure/server/transport/http/gin"

	"github.com/UpmeshLTD/urus-aio/infrastructure/server/transport/http/gin/middleware"
	"github.com/UpmeshLTD/urus-aio/paid-order/service/dto"
	"github.com/UpmeshLTD/urus-aio/paid-order/service/transform"
	"github.com/gin-gonic/gin"
)

type iPaidOrder interface {
	GetListPaidOrders(c *gin.Context)
	GetArrangedShipPaidOrders(c *gin.Context)
	ArrangeShipPaidOrders(c *gin.Context)
	ArchivePaidOrder(c *gin.Context)
	CreatePaidOrders(c *gin.Context)
	CountDraftOrders(c *gin.Context)
}

// GetListPaidOrders godoc
// @Summary Get list of paid orders.
// @Description Get list of paid orders.
// @Tags Paid Order
// @Accept */*
// @Produce json
// @Param userworkspace header string true "User workspace")
// @Param GetPaidOrdersRequest query dto.GetPaidOrdersRequest true "GetPaidOrdersRequest"
// @Success 200 {object} dto.GetPaidOrdersResponse
// @Security BearerToken
// @Router /paid-orders [GET]
func (s *serviceImpl) GetListPaidOrders(c *gin.Context) {
	req := &dto.GetPaidOrdersRequest{}
	if err := gin2.BindData(c, req, gin2.BindType_Query); err != nil {
		gin2.ResponseBadRequest(c, err)
		return
	}

	result, total, err := s.paidOrderDomain.GetList(getCtx(c), transform.GetPaidOrdersRequestDTOToParamsEntities(req, middleware.GetWorkspaceId(c)))
	if err != nil {
		gin2.ResponseInternalServerError(c, err)
		return
	}

	gin2.ResponseSuccess(c, &dto.GetPaidOrdersResponse{
		Data:  transform.PaidOrderEntitiesToDTOs(c, result),
		Total: total,
	})
}

// ArrangeShipPaidOrders godoc
// @Summary Arrange ship paid orders
// @Description Arrange ship paid orders
// @Tags Paid Order
// @Accept */*
// @Produce json
// @Param userworkspace header string true "User workspace")
// @Param request body dto.ArrangeShipRequest true "ArrangeShipRequest"
// @Success 204
// @Security BearerToken
// @Router /paid-orders/arrange-ship [POST]
func (s *serviceImpl) ArrangeShipPaidOrders(c *gin.Context) {
	workspaceID := middleware.GetWorkspaceId(c)
	req := &dto.ArrangeShipRequest{}
	if err := c.BindJSON(req); err != nil {
		gin2.ResponseBadRequest(c, err)
		return
	}

	if len(req.PaidOrders) == 0 {
		gin2.ResponseBadRequest(c, errors.New("empty PaidOrders"))
		return
	}

	if len(req.PaidOrders) > s.config.GetAppConfig().MassShipLimit {
		gin2.ResponseBadRequest(c, fmt.Errorf("The number of paid orders can't exceed %v", s.config.GetAppConfig().MassShipLimit))
		return
	}

	// Check paid_orders already arranged ship
	createdPaidOrders, _, err := s.paidOrderDomain.GetList(c, &entities.GetPaidOrdersParams{
		Fields: &[]string{"ID", "CartID", "ShippingGroupID", "FulfillmentID", "ShipmentID"},
		CartIDs: func() (arr []string) {
			for _, v := range req.PaidOrders {
				arr = append(arr, v.CartID)
			}
			return arr
		}(),
	})
	if err != nil {
		gin2.ResponseInternalServerError(c, err)
		return
	}

	createdPaidOrdersMap := map[string]*entities.PaidOrder{}
	if len(createdPaidOrders) > 0 {
		for _, v := range createdPaidOrders {
			// If the paid order has a shipment and the shipping group is null
			if v.ShipmentID != nil && v.ShippingGroupID == nil {
				gin2.ResponseBadRequest(c, fmt.Errorf("Cart %s already have arranged ship", v.CartID))
				return
			}
			createdPaidOrdersMap[v.CartShippingGroupKey()] = v
		}
		for _, v := range req.PaidOrders {
			if po, has := createdPaidOrdersMap[v.CartShippingGroupKey()]; has && po.ShipmentID != nil {
				if !utils.IsEmpty(v.ShippingGroupID) {
					gin2.ResponseBadRequest(c, fmt.Errorf("Cart %s within shipping group %s already have arranged ship", v.CartID, *v.ShippingGroupID))
					return
				}
				gin2.ResponseBadRequest(c, fmt.Errorf("Cart %s already have arranged ship", v.CartID))
				return
			}
		}
	}

	// Create fulfillment
	fulfillment, err := s.fulfillmentDomain.Create(c, &entities.Fulfillment{
		WorkspaceID: workspaceID,
	})
	if err != nil {
		gin2.ResponseInternalServerError(c, err)
		return
	}

	fulfillmentID := fulfillment.ID.String()

	// Create paid_orders
	createdPaidOrderIDs := make([]string, 0)
	newPaidOrders := make([]*entities.PaidOrder, 0)
	for _, v := range req.PaidOrders {
		if po, has := createdPaidOrdersMap[v.CartShippingGroupKey()]; has {
			createdPaidOrderIDs = append(createdPaidOrderIDs, po.ID)
		} else {
			newPaidOrders = append(newPaidOrders, &entities.PaidOrder{
				CartID:          v.CartID,
				ShippingGroupID: v.ShippingGroupID,
				WorkspaceID:     workspaceID,
				FulfillmentID:   &fulfillmentID,
			})
		}
	}

	if len(createdPaidOrderIDs) > 0 {
		if err := s.paidOrderDomain.UpdateFulfillmentIDBatch(c, createdPaidOrderIDs, &fulfillmentID); err != nil {
			gin2.ResponseInternalServerError(c, err)
			return
		}
	}

	if len(newPaidOrders) > 0 {
		if err := s.paidOrderDomain.CreateBatch(c, newPaidOrders); err != nil {
			gin2.ResponseInternalServerError(c, err)
			return
		}
	}

	gin2.ResponseSuccess(c, &dto.ArrangeShipResponse{
		FulfillmentID: fulfillmentID,
	})
}

// ArchivePaidOrder godoc
// @Summary Archive a paid order
// @Description Archive a paid order
// @Tags Paid Order
// @Param userworkspace header string true "User workspace")
// @Param cartId path string true "Cart ID"
// @Accept */*
// @Success 204
// @Security BearerToken
// @Router /paid-orders/{cartId}/archive [POST]
func (s *serviceImpl) ArchivePaidOrder(c *gin.Context) {
	req := &dto.ArchivePaidOrderRequest{}
	if err := gin2.BindData(c, req, gin2.BindType_URI); err != nil {
		gin2.ResponseBadRequest(c, err)
		return
	}

	err := s.paidOrderDomain.Delete(getCtx(c), req.CartID)
	if err != nil {
		gin2.ResponseInternalServerError(c, err)
		return
	}
	gin2.ResponseNoContent(c)
}

// CreatePaidOrders godoc
// @Summary Create paid orders
// @Description Create paid orders
// @Tags Paid Order
// @Accept */*
// @Produce json
// @Param userworkspace header string true "User workspace")
// @Param request body dto.CreatePaidOrdersRequest true "CreatePaidOrdersRequest"
// @Success 204
// @Security BearerToken
// @Router /paid-orders [POST]
func (s *serviceImpl) CreatePaidOrders(c *gin.Context) {
	req := &dto.CreatePaidOrdersRequest{}
	if err := gin2.BindData(c, req, gin2.BindType_JSON); err != nil {
		gin2.ResponseBadRequest(c, err)
		return
	}

	workspaceID := middleware.GetWorkspaceId(c)

	cartIDs := make([]string, 0)
	for _, po := range req.PaidOrders {
		cartIDs = append(cartIDs, po.CartID)
	}

	carts, err := s.cartService.GetCartByIDs(c, c.Request.Header.Get("Authorization"), cartIDs)
	if err != nil {
		gin2.ResponseInternalServerError(c, err)
		return
	}

	maps := map[string]bool{}
	for _, c := range carts {
		if c.GeneralInformation.SellerID == workspaceID {
			maps[c.CartID] = true
		}
	}

	for _, po := range req.PaidOrders {
		if _, has := maps[po.CartID]; !has {
			gin2.ResponseBadRequest(c, errors.New(fmt.Sprintf("cart %s not found", po.CartID)))
			return
		}
	}

	err = s.paidOrderDomain.CreateBatch(c, transform.CreatePaidOrdersRequestToEntities(req, workspaceID))
	if err != nil {
		gin2.ResponseInternalServerError(c, err)
		return
	}
	gin2.ResponseNoContent(c)
}

// GetArrangedShipPaidOrders godoc
// @Summary Get arranged ship paid orders
// @Description Get arranged ship paid orders
// @Tags Paid Order
// @Accept */*
// @Produce json
// @Param userworkspace header string true "User workspace")
// @Param request body dto.GetArrangedShipPaidOrdersRequest true "GetArrangedShipPaidOrdersRequest"
// @Success 204
// @Security BearerToken
// @Router /paid-orders/arranged-ship [POST]
func (s *serviceImpl) GetArrangedShipPaidOrders(c *gin.Context) {
	req := &dto.GetArrangedShipPaidOrdersRequest{}
	if err := gin2.BindData(c, req, gin2.BindType_JSON); err != nil {
		gin2.ResponseBadRequest(c, err)
		return
	}

	result, err := s.paidOrderDomain.GetArranged(c, transform.GetArrangedShipPaidOrdersRequestToParamsEntity(req))
	if err != nil {
		gin2.ResponseInternalServerError(c, err)
		return
	}
	gin2.ResponseSuccess(c, transform.ArrangedShipPaidOrdersToDTO(result))
}

// CountDraftOrders godoc
// @Summary Count draft orders.
// @Description Count draft orders.
// @Tags Paid Order
// @Accept */*
// @Produce json
// @Param userworkspace header string true "User workspace")
// @Success 200 {object} dto.CountDraftOrdersResponse
// @Security BearerToken
// @Router /paid-orders/count [GET]
func (s *serviceImpl) CountDraftOrders(c *gin.Context) {
	wsId := middleware.GetWorkspaceId(c)
	total, err := s.paidOrderDomain.GetCount(getCtx(c), &entities.GetPaidOrdersParams{
		WorkspaceID: &wsId,
		OnlyDraft:   true,
	})
	if err != nil {
		gin2.ResponseInternalServerError(c, err)
		return
	}

	gin2.ResponseSuccess(c, &dto.CountDraftOrdersResponse{
		Count: total,
	})
}
