package service

import (
	"fmt"
	"github.com/pkg/errors"
	"log"
	"net/http"
	"sync"

	gin2 "github.com/UpmeshLTD/urus-aio/infrastructure/server/transport/http/gin"
	paidorderpb "github.com/UpmeshLTD/urus-aio/paid-order/server/grpc/pb"
	external "github.com/UpmeshLTD/urus-aio/shipment/external/shipment"

	"github.com/UpmeshLTD/urus-aio/infrastructure/db/entities"
	"github.com/UpmeshLTD/urus-aio/infrastructure/server/transport/http/gin/middleware"
	"github.com/UpmeshLTD/urus-aio/infrastructure/utils"
	"github.com/UpmeshLTD/urus-aio/shipment/server/http/dto"
	"github.com/UpmeshLTD/urus-aio/shipment/service/transform"
	"github.com/gin-gonic/gin"
	"google.golang.org/grpc/metadata"
	"gorm.io/gorm"
)

type iShipment interface {
	GetShipment(ctx *gin.Context)
	GetShipments(ctx *gin.Context)
	GetTrackingUrls(ctx *gin.Context)
	GetShipmentStatus(ctx *gin.Context)
	GetShipmentLabels(ctx *gin.Context)
	GenerateShipmentLabels(ctx *gin.Context)
	GetShipmentOrdersStatus(ctx *gin.Context)

	CreateShipment(ctx *gin.Context)
	UpdateShipment(ctx *gin.Context)
	CancelShipment(ctx *gin.Context)
	ArchiveShipment(ctx *gin.Context)
	GetArchivedShipments(ctx *gin.Context)

	NotifyStaffToPickupParcel(ctx *gin.Context)
	NotifyStaffToPickupParcelInBatch(ctx *gin.Context)

	ReArrangeShipments(ctx *gin.Context)

	DevUpdateShipment(ctx *gin.Context)
}

// CreateShipment godoc
// @Summary CreateShipment.
// @Description CreateShipment.
// @Tags Shipment
// @Accept */*
// @Param request body dto.CreateShipmentRequest true "request"
// @Success 200 {object} dto.CreateShipmentResponse
// @Security BearerToken
// @Router /shipments [POST]
func (s *serviceImpl) CreateShipment(c *gin.Context) {
	req := &dto.CreateShipmentRequest{}
	if err := gin2.BindData(c, &req, gin2.BindType_JSON); err != nil {
		gin2.ResponseBadRequest(c, err)
		return
	}

	workspaceID := middleware.GetWorkspaceId(c)
	md := metadata.New(map[string]string{
		"authorization": c.Request.Header.Get("Authorization"),
	})
	fulfillment, err := s.paidOrderClient.GetFulfillment(metadata.NewOutgoingContext(c, md), &paidorderpb.GetFulfillmentRequest{
		FulfillmentId: req.FulfillmentID,
		WorkspaceId:   workspaceID,
	})
	if err != nil {
		log.Println("[CreateShipment] Failed to get fulfillment detail", err)
		gin2.ResponseInternalServerError(c, errors.New("something went wrong! cannot get detail of fulfillment"))
		return
	}

	sellerCourier, err := s.courierDomain.GetSellerCourier(c, workspaceID, *fulfillment.CourierId)
	if err != nil {
		gin2.ResponseInternalServerError(c, errors.New("courier not setting yet"))
		return
	}

	successes, fails := s.shipmentDomain.Create(c, transform.FulfillmentDetailToShipmentEntities(fulfillment, workspaceID), sellerCourier)

	// Flash Express: Notify courier to start pickup
	if len(successes) > 0 && (sellerCourier.Courier.Code == entities.CourierCodeFlashExpressTH || sellerCourier.Courier.Code == entities.CourierCodeFlashExpressPH) {
		go func() {
			s.shipmentDomain.NotifyFlashExpressCourier(c, sellerCourier, &successes[0].PickupPoint)
		}()
	}

	// Mark seller courier as not verified when receive error invalid authentication
	if len(fails) > 0 && fails[0].Error.Code == external.ErrorCodeCourierAuthenticationInvalid {
		if err := s.courierDomain.UpdateSellerCourierVerifyStatus(c, sellerCourier.ID, false); err != nil {
			fmt.Println("[CreateShipment] Failed to update seller courier verify status to false.", err)
		}
	}

	// Remove draft fulfillment
	if len(fails) == 0 {
		_ = s.paidOrderClient.UpdateFulfillmentDraftStatus(getCtx(c), &paidorderpb.UpdateFulfillmentDraftStatusRequest{
			Id:    fulfillment.Id,
			Draft: false,
		})
	}

	acceptLanguage := middleware.GetAcceptLanguage(c)
	userCountryID := utils.ConvertAcceptLanguageToCountryID(acceptLanguage)
	localized := sellerCourier.Courier.CountryID == userCountryID

	gin2.ResponseSuccess(c, dto.CreateShipmentResponse{
		Successes: transform.ShipmentEntitiesToDTOs(successes, acceptLanguage),
		Fails:     transform.CreateShipmentErrorsToFailedShipmentDTOs(fails, localized),
	})
}

// GetShipment godoc
// @Summary Get Shipment by id.
// @Description Get Shipment by id.
// @Tags Shipment
// @Accept */*
// @Param id path int true "Shipment ID"
// @Success 200 {object} dto.GetShipmentResponse
// @Security BearerToken
// @Router /shipments/{id} [GET]
func (s *serviceImpl) GetShipment(c *gin.Context) {
	req := &dto.GetShipmentRequest{}

	if err := gin2.BindData(c, &req, gin2.BindType_URI); err != nil {
		gin2.ResponseErrorWithCode(c, http.StatusBadRequest, err)
		return
	}

	res, err := s.shipmentDomain.Get(getCtx(c), req.ShipmentID, middleware.GetWorkspaceId(c))
	if err != nil {
		gin2.ResponseInternalServerError(c, err)
		return
	}

	acceptLanguage := middleware.GetAcceptLanguage(c)
	userCountryID := utils.ConvertAcceptLanguageToCountryID(acceptLanguage)
	localized := userCountryID == res.Courier.CountryID
	gin2.ResponseSuccess(c, transform.GetShipmentResponseEntityToDTO(res, localized))
}

// GetShipments godoc
// @Summary Get Shipments.
// @Description Get Shipments.
// @Tags Shipment
// @Accept */*
// @Param GetShipmentsRequest query dto.GetShipmentsRequest false "GetShipmentsRequest"
// @Success 200 {object} dto.GetShipmentsResponse
// @Security BearerToken
// @Router /shipments [GET]
func (s *serviceImpl) GetShipments(c *gin.Context) {
	req := &dto.GetShipmentsRequest{}

	if err := gin2.BindData(c, &req, gin2.BindType_Query); err != nil {
		gin2.ResponseErrorWithCode(c, http.StatusBadRequest, err)
		return
	}

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

	gin2.ResponseSuccess(c, &dto.GetShipmentsResponse{
		Data:  transform.ShipmentEntitiesToDTOs(res, middleware.GetAcceptLanguage(c)),
		Total: total,
	})
}

// GetTrackingUrls godoc
// @Summary Get Tracking URLs
// @Description Get Tracking URLs
// @Tags Shipment
// @Accept json
// @Produce json
// @Param request body dto.GetTrackingUrlsRequest true "request"
// @Success 200 {object} dto.GetTrackingUrlsResponse
// @Security BearerToken
// @Router /shipments/tracking-urls [POST]
func (s *serviceImpl) GetTrackingUrls(c *gin.Context) {
	req := &dto.GetTrackingUrlsRequest{}
	if err := gin2.BindData(c, &req, gin2.BindType_JSON); err != nil {
		gin2.ResponseErrorWithCode(c, http.StatusBadRequest, err)
		return
	}

	if len(req.CartIDs) == 0 {
		gin2.ResponseBadRequest(c, errors.New("cart ids is empty"))
		return
	}

	onlyArchived := false
	res, _, err := s.shipmentDomain.GetList(c, &entities.GetShipmentsParams{
		CartIDs:      req.CartIDs,
		OnlyArchived: &onlyArchived,
	})
	if err != nil {
		gin2.ResponseInternalServerError(c, err)
		return
	}

	gin2.ResponseSuccess(c, &dto.GetTrackingUrlsResponse{
		Data: transform.GetTrackingUrlsEntitiesToDTOs(c, res, s.externalShipmentFactory),
	})
}

// GetShipmentStatus godoc
// @Summary Get status history of a shipment
// @Description Get status history of a shipment
// @Tags Shipment
// @Accept */*
// @Produce json
// @Param userworkspace header string true "User workspace")
// @Param id path string true "Shipment ID"
// @Success 200 {object} dto.GetShipmentStatusResponse
// @Security BearerToken
// @Router /shipments/{id}/status [GET]
func (s *serviceImpl) GetShipmentStatus(c *gin.Context) {
	req := &dto.GetShipmentStatusRequest{}

	if err := gin2.BindData(c, &req, gin2.BindType_URI); err != nil {
		gin2.ResponseBadRequest(c, err)
		return
	}

	shipment, err := s.shipmentDomain.Get(c, req.ShipmentID, middleware.GetWorkspaceId(c))
	if err != nil {
		gin2.ResponseInternalServerError(c, err)
		return
	}
	if shipment.CourierTrackingID == nil {
		gin2.ResponseSuccess(c, transform.GetShipmentStatusLogEntityToDTO(shipment, []*entities.ShipmentStatusLog{}))
		return
	}

	logs, err := s.shipmentStatusLogDomain.GetList(getCtx(c), *shipment.CourierTrackingID)
	if err != nil {
		gin2.ResponseInternalServerError(c, err)
		return
	}

	gin2.ResponseSuccess(c, transform.GetShipmentStatusLogEntityToDTO(shipment, logs))
}

// GetShipmentOrdersStatus godoc
// @Summary GetShipmentOrdersStatus
// @Description GetShipmentOrdersStatus
// @Tags Shipment
// @Accept */*
// @Produce json
// @Param request body dto.GetShipmentOrdersStatusRequest true "request"
// @Success 200
// @Router /shipments/status [POST]
// @Security BearerToken
func (s *serviceImpl) GetShipmentOrdersStatus(c *gin.Context) {
	req := &dto.GetShipmentOrdersStatusRequest{}
	if err := gin2.BindData(c, &req, gin2.BindType_JSON); err != nil {
		gin2.ResponseBadRequest(c, err)
		return
	}

	if len(req.CartIDs) == 0 {
		gin2.ResponseBadRequest(c, errors.New("cart ids is empty"))
		return
	}

	// For orders that have created a shipment
	onlyArchived := false
	shipments, _, err := s.shipmentDomain.GetList(c, &entities.GetShipmentsParams{
		CartIDs:      req.CartIDs,
		OnlyArchived: &onlyArchived,
	})
	if err != nil {
		gin2.ResponseInternalServerError(c, err)
		return
	}

	data := transform.ShipmentsEntityToShipmentOrdersStatusDTO(shipments)

	// For orders that have not created a shipment
	pos, err := s.paidOrderClient.GetPaidOrders(c, transform.GetShipmentOrdersStatusRequestToGRPCGetPaidOrdersRequest(req))
	if err != nil {
		gin2.ResponseInternalServerError(c, err)
		return
	}

	maps := map[string]bool{}
	for _, v := range data {
		maps[utils.GenerateKeyFromCartIdAndShippingGroupId(v.CartID, v.ShippingGroupID)] = true
	}

	for _, v := range pos.PaidOrders {
		if _, has := maps[utils.GenerateKeyFromCartIdAndShippingGroupId(v.CartId, v.ShippingGroupId)]; !has {
			data = append(data, dto.ShipmentOrderStatus{
				CartID:          v.CartId,
				ShippingGroupID: v.ShippingGroupId,
				Status:          "to_ship",
				StatusAt:        v.CreatedAt.AsTime(),
			})
		}
	}

	gin2.ResponseSuccess(c, &dto.GetShipmentOrdersStatusResponse{
		Data: data,
	})
}

// NotifyStaffToPickupParcel godoc
// @Summary Notify staff to pickup parcel
// @Description Notify staff to pickup parcel
// @Tags Shipment
// @Accept */*
// @Produce json
// @Param userworkspace header string true "User workspace")
// @Param id path string true "Shipment ID"
// @Success 200
// @Security BearerToken
// @Router /shipments/{id}/notify-pickup [POST]
func (s *serviceImpl) NotifyStaffToPickupParcel(c *gin.Context) {
	req := &dto.NotifyPickupRequest{}
	if err := gin2.BindData(c, &req, gin2.BindType_URI); err != nil {
		gin2.ResponseBadRequest(c, err)
		return
	}

	workspaceID := middleware.GetWorkspaceId(c)

	sm, err := s.shipmentDomain.Get(getCtx(c), req.ShipmentID, workspaceID)
	if err != nil {
		gin2.ResponseInternalServerError(c, err)
		return
	}

	sellerCourier, err := s.courierDomain.GetSellerCourier(c, workspaceID, sm.Courier.ID.ToInt64())
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			gin2.ResponseInternalServerError(c, errors.New("seller have not integrated with courier yet"))
			return
		}
		gin2.ResponseInternalServerError(c, err)
		return
	}

	// Flash Express: Notify courier to start pickup
	if sm.Courier.Code == entities.CourierCodeFlashExpressTH {
		s.shipmentDomain.NotifyFlashExpressCourier(c, sellerCourier, &sm.PickupPoint)
	}

	gin2.ResponseNoContent(c)
}

// NotifyStaffToPickupParcelInBatch godoc
// @Summary Notify staff to pickup parcel in batch
// @Description Notify staff to pickup parcel in batch
// @Tags Shipment
// @Accept */*
// @Produce json
// @Param request body dto.NotifyPickupInBatchRequest true "request"
// @Success 200
// @Security BearerToken
// @Router /shipments/notify-pickup [POST]
func (s *serviceImpl) NotifyStaffToPickupParcelInBatch(c *gin.Context) {
	req := &dto.NotifyPickupInBatchRequest{}
	if err := gin2.BindData(c, &req, gin2.BindType_JSON); err != nil {
		gin2.ResponseBadRequest(c, err)
		return
	}

	if len(req.IDs) == 0 {
		gin2.ResponseNoContent(c)
		return
	}

	workspaceID := middleware.GetWorkspaceId(c)

	// Get shipments
	shipments, err := s.shipmentDomain.GetByIDs(c, req.IDs, workspaceID)
	if err != nil {
		gin2.ResponseErrorWithCode(c, http.StatusBadRequest, err)
		return
	}

	if len(shipments) == 0 {
		gin2.ResponseNoContent(c)
		return
	}

	// Group pickup points by courier ID
	courierPickupPoints := map[int64][]*entities.PickupPoint{}
	for _, v := range shipments {
		courierID := v.Courier.ID.ToInt64()
		if v.Courier.Code == entities.CourierCodeFlashExpressTH || v.Courier.Code == entities.CourierCodeFlashExpressPH {
			courierPickupPoints[courierID] = append(courierPickupPoints[courierID], &v.PickupPoint)
		}
	}

	// Get seller couriers and group by courier ID
	sellerCouriers := map[int64]*entities.SellerCourier{}
	for courierID, _ := range courierPickupPoints {
		sellerCourier, err := s.courierDomain.GetSellerCourier(c, workspaceID, courierID)
		if err != nil || sellerCourier == nil || !sellerCourier.Verified {
			gin2.ResponseInternalServerError(c, errors.New("seller have not integrated with courier yet"))
			return
		}
		sellerCouriers[courierID] = sellerCourier
	}

	// Notify courier to start pickup
	for courierID, pickupPoints := range courierPickupPoints {
		for _, v := range pickupPoints {
			if sellerCouriers[courierID] != nil {
				s.shipmentDomain.NotifyFlashExpressCourier(c, sellerCouriers[courierID], v)
			}
		}
	}

	gin2.ResponseNoContent(c)
}

// GetShipmentLabels godoc
// @Summary Get shipment labels
// @Description Get shipment labels
// @Tags Shipment
// @Accept */*
// @Param request body dto.GetShipmentLabelsRequest true "request"
// @Success 200 {object} dto.GetShipmentLabelsResponse
// @Security BearerToken
// @Router /shipments/labels [POST]
func (s *serviceImpl) GetShipmentLabels(c *gin.Context) {
	workspaceID := middleware.GetWorkspaceId(c)
	req := &dto.GetShipmentLabelsRequest{}
	if err := c.BindJSON(&req); err != nil {
		gin2.ResponseErrorWithCode(c, http.StatusBadRequest, err)
		return
	}

	// Get shipments
	shipments, err := s.shipmentDomain.GetByIDs(c, req.ShipmentIDs, workspaceID)
	if err != nil {
		gin2.ResponseErrorWithCode(c, http.StatusBadRequest, err)
		return
	}

	var res []*entities.GetShippingLabelsData
	var wg sync.WaitGroup
	wg.Add(len(shipments))
	for _, v := range shipments {
		go func(v *entities.Shipment) {
			defer wg.Done()
			if v.WorkspaceID != workspaceID {
				res = append(res, &entities.GetShippingLabelsData{
					ID:    v.ID.String(),
					Error: "permission denied",
				})
			} else {
				sellerCourier, _ := s.courierDomain.GetSellerCourier(c, v.WorkspaceID, v.CourierID)
				url, err := s.shipmentDomain.GetLabelUrl(c, v, sellerCourier)
				data := &entities.GetShippingLabelsData{
					ID:  v.ID.String(),
					URL: url,
				}
				if err != nil {
					data.Error = err.Error()
				}
				res = append(res, data)
			}
		}(v)
	}
	wg.Wait()

	gin2.ResponseSuccess(c, transform.GetShipmentLabelsResponseEntityToDTO(res))
}

// UpdateShipment godoc
// @Summary UpdateShipment
// @Description Update a shipment
// @Tags Shipment
// @Accept json
// @Produce json
// @Param userworkspace header string true "User workspace")
// @Param id path int true "Shipment ID"
// @Param request body dto.UpdateShipmentRequest true "Update Shipment"
// @Success 204
// @Security BearerToken
// @Router /shipments/{id} [PUT]
func (s *serviceImpl) UpdateShipment(c *gin.Context) {
	req := &dto.UpdateShipmentRequest{}
	if err := gin2.BindData(c, &req, gin2.BindType_JSON, gin2.BindType_URI); err != nil {
		gin2.ResponseBadRequest(c, err)
		return
	}

	workspaceID := middleware.GetWorkspaceId(c)
	shipmentEntity, err := s.shipmentDomain.Get(c, req.ID, workspaceID)
	if err != nil {
		gin2.ResponseInternalServerError(c, errors.New("shipment not found"))
		return
	}

	sellerCourier, err := s.courierDomain.GetSellerCourier(c, workspaceID, shipmentEntity.CourierID)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			gin2.ResponseInternalServerError(c, errors.New("seller have not integrated with courier yet"))
			return
		}
		gin2.ResponseInternalServerError(c, err)
		return
	}

	if err := s.shipmentDomain.Update(c, shipmentEntity, &entities.UpdateShipmentParams{
		PickupStartTime: req.PickupStartTime,
		PickupEndTime:   req.PickupEndTime,
	}, sellerCourier); err != nil {
		gin2.ResponseInternalServerError(c, errors.New(err.Message))
		return
	}

	gin2.ResponseNoContent(c)
}

// CancelShipment godoc
// @Summary CancelShipment
// @Description Cancel a shipment
// @Tags Shipment
// @Accept json
// @Produce json
// @Param userworkspace header string true "User workspace")
// @Param id path int true "Shipment ID"
// @Param request body dto.CancelShipmentRequest true "Cancel Shipment"
// @Success 204
// @Security BearerToken
// @Router /shipments/{id}/cancel [POST]
func (s *serviceImpl) CancelShipment(c *gin.Context) {
	req := &dto.CancelShipmentRequest{}
	if err := gin2.BindData(c, &req, gin2.BindType_JSON, gin2.BindType_URI); err != nil {
		gin2.ResponseBadRequest(c, err)
		return
	}

	workspaceID := middleware.GetWorkspaceId(c)
	shipmentEntity, err := s.shipmentDomain.Get(c, req.ID, workspaceID)
	if err != nil {
		gin2.ResponseInternalServerError(c, errors.New("shipment not found"))
		return
	}

	sellerCourier, err := s.courierDomain.GetSellerCourier(c, workspaceID, shipmentEntity.CourierID)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			gin2.ResponseInternalServerError(c, errors.New("seller have not integrated with courier yet"))
			return
		}
		gin2.ResponseInternalServerError(c, err)
		return
	}

	if err := s.shipmentDomain.Cancel(c, shipmentEntity, &entities.CancelShipmentParams{
		Reason: req.Reason,
	}, sellerCourier); err != nil {
		gin2.ResponseInternalServerError(c, errors.New(err.Message))
		return
	}

	if sellerCourier.CourierID == entities.CourierIdFlashExpressPH &&
		shipmentEntity.Status == entities.ShipmentStatusCourierStaffAssigned {
		p, err := s.flashExpressPickupDomain.GetByCourierTrackingID(c, *shipmentEntity.CourierTrackingID)
		if err != nil {
			gin2.ResponseInternalServerError(c, err)
			return
		}

		// Cancel pickup
		if err := s.shipmentDomain.CancelNotifyFlashExpressCourier(c, sellerCourier, p, shipmentEntity); err != nil {
			gin2.ResponseInternalServerError(c, err)
			return
		}

		// Re-notify pickup
		if len(p.CourierTrackingIDs) > 1 {
			go func() {
				s.shipmentDomain.NotifyFlashExpressCourier(c, sellerCourier, &shipmentEntity.PickupPoint)
			}()
		}
	}

	gin2.ResponseNoContent(c)
}

// ArchiveShipment godoc
// @Summary ArchiveShipment
// @Description Archive a shipment
// @Tags Shipment
// @Accept */*
// @Produce json
// @Param userworkspace header string true "User workspace")
// @Param id path int true "Shipment ID"
// @Success 204
// @Security BearerToken
// @Router /shipments/{id}/archive [POST]
func (s *serviceImpl) ArchiveShipment(c *gin.Context) {
	req := &dto.ArchiveShipmentRequest{}
	if err := gin2.BindData(c, &req, gin2.BindType_URI); err != nil {
		gin2.ResponseBadRequest(c, err)
		return
	}

	entity, err := s.shipmentDomain.Get(c, req.ID, middleware.GetWorkspaceId(c))
	if err != nil {
		gin2.ResponseBadRequest(c, err)
		return
	}

	// Check status
	if entity.Status != entities.ShipmentStatusDelivered &&
		entity.Status != entities.ShipmentStatusCancelled &&
		entity.Status != entities.ShipmentStatusFailedDelivery {
		gin2.ResponseBadRequest(c, errors.New("cannot archive this shipment"))
		return
	}

	entity, err = s.shipmentDomain.Archive(c, entity)
	if err != nil {
		gin2.ResponseInternalServerError(c, err)
		return
	}

	if err := s.publisherDomain.PublishShipmentArchivedMessage(entity); err != nil {
		log.Println("[ArchiveShipment] Failed to publish a message shipment archived", "shipmentId", entity.ID.String())
	}

	gin2.ResponseNoContent(c)
}

// GetArchivedShipments godoc
// @Summary Get archived shipments
// @Description Get archived shipments
// @Tags Shipment
// @Accept */*
// @Param userworkspace header string true "User workspace")
// @Success 200 {object} dto.GetShipmentsResponse
// @Security BearerToken
// @Router /shipments/archived [GET]
func (s *serviceImpl) GetArchivedShipments(c *gin.Context) {
	req := &dto.GetArchivedShipmentsRequest{}
	if err := gin2.BindData(c, &req, gin2.BindType_Query); err != nil {
		gin2.ResponseErrorWithCode(c, http.StatusBadRequest, err)
		return
	}

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

	gin2.ResponseSuccess(c, &dto.GetShipmentsResponse{
		Data:  transform.ShipmentEntitiesToDTOs(res, middleware.GetAcceptLanguage(c)),
		Total: total,
	})
}

// ReArrangeShipments godoc
// @Summary ReArrangeShipments
// @Description Re-arrange shipments
// @Tags Shipment
// @Accept */*
// @Produce json
// @Param userworkspace header string true "User workspace")
// @Param request body dto.ReArrangeShipmentsRequest true "request"
// @Success 200 {object} dto.ReArrangeShipmentsResponse
// @Security BearerToken
// @Router /shipments/re-arrange [POST]
func (s *serviceImpl) ReArrangeShipments(c *gin.Context) {
	req := &dto.ReArrangeShipmentsRequest{}
	if err := gin2.BindData(c, &req, gin2.BindType_JSON); err != nil {
		gin2.ResponseBadRequest(c, err)
		return
	}

	workspaceID := middleware.GetWorkspaceId(c)

	shipments, err := s.shipmentDomain.GetByIDs(c, req.IDs, workspaceID)
	if err != nil {
		gin2.ResponseInternalServerError(c, err)
		return
	}

	if len(shipments) == 0 {
		gin2.ResponseBadRequest(c, errors.New("Shipments not found"))
		return
	}

	for _, shipment := range shipments {
		if shipment.Status != entities.ShipmentStatusCancelled && shipment.Status != entities.ShipmentStatusFailedDelivery {
			gin2.ResponseBadRequest(c, errors.New(fmt.Sprintf("Shipment %s is not allowed to re-arrange", shipment.ID.String())))
			return
		}
	}

	fulfillmentID, err := s.shipmentDomain.ReArrange(c, workspaceID, shipments)
	if err != nil {
		gin2.ResponseInternalServerError(c, errors.Wrap(err, "Failed to re-arrange ship"))
		return
	}

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

/*--------------------------------------------------------------------------------------------------------------------*/

// DevUpdateShipment godoc
// @Summary Development API for update shipment
// @Description Development API for update shipment
// @Tags Shipment
// @Accept */*
// @Produce json
// @Param request body dto.DevUpdateShipmentRequest true "request"
// @Success 200
// @Router /admin/shipments/_status [POST]
func (s *serviceImpl) DevUpdateShipment(c *gin.Context) {
	req := &dto.DevUpdateShipmentRequest{}
	if err := gin2.BindData(c, &req, gin2.BindType_JSON); err != nil {
		gin2.ResponseBadRequest(c, err)
		return
	}

	err := s.shipmentDomain.DevUpdateShipment(getCtx(c), transform.DevUpdateShipmentDTOToEntity(req))

	if err != nil {
		gin2.ResponseInternalServerError(c, err)
		return
	}
}
