package transform

import (
	"context"
	"fmt"
	"github.com/UpmeshLTD/urus-aio/infrastructure/common"
	"github.com/UpmeshLTD/urus-aio/infrastructure/db/entities"
	"github.com/UpmeshLTD/urus-aio/infrastructure/utils"
	"github.com/UpmeshLTD/urus-aio/paid-order/server/grpc/pb"
	"github.com/UpmeshLTD/urus-aio/shipment/external/shipment"
	"github.com/UpmeshLTD/urus-aio/shipment/external/shipment/factory"
	"github.com/UpmeshLTD/urus-aio/shipment/server/http/dto"
	"time"
)

func GetFulfillmentResponseToShipmentEntity(fulfillment *pb.GetFulfillmentResponse, order *pb.OrderDetail, workspaceID string) *entities.Shipment {
	items := make([]entities.ShipmentItem, 0, len(order.OrderItems))
	for _, v := range order.OrderItems {
		items = append(items, entities.ShipmentItem{
			Code:        v.ItemCode,
			Price:       v.ItemPrice,
			Description: v.ItemName,
			Quantity:    int(v.ItemQuantity),
		})
	}

	var subLevelLocations []entities.SubLevelLocation
	for _, v := range order.DeliveryAddress.SubLevelLocations {
		subLevelLocations = append(subLevelLocations, entities.SubLevelLocation{
			InternationalName: v.InternationalName,
			LocalizedName:     v.LocalizedName,
		})
	}

	res := &entities.Shipment{
		BaseUuidEntity:  entities.BaseUuidEntity{},
		CartID:          order.CartId,
		ShippingGroupID: order.ShippingGroupId,
		FulfillmentID:   fulfillment.Id,
		ReceiverInfo: entities.ShipmentReceiverInfo{
			Name:              order.GeneralInformation.CustomerName,
			RecipientName:     order.DeliveryDetails.RecipientName,
			Email:             order.GeneralInformation.CustomerEmail,
			ContactNumber:     order.DeliveryDetails.RecipientPhoneNumber,
			CountryCode:       "",
			PostalCode:        order.DeliveryAddress.PostalCode,
			AddressLine1:      order.DeliveryAddress.Address1,
			AddressLine2:      order.DeliveryAddress.Address2,
			UnitNumber:        order.DeliveryAddress.UnitNumber,
			SubLevelLocations: subLevelLocations,
			Instructions:      order.DeliveryDetails.DeliveryInstruction,
		},
		Items:         items,
		WorkspaceID:   workspaceID,
		BuyerID:       order.GeneralInformation.CustomerId,
		Status:        entities.ShipmentStatusFulfillmentRequestSent,
		CourierStatus: entities.CourierShipmentStatusFulfillmentRequestSent,
		StateDate:     time.Now(),
		PaidOrderID:   order.Id,
	}

	if fulfillment.CourierId != nil {
		res.CourierID = *fulfillment.CourierId

		code := utils.GenerateCode(entities.MappingCourierIdAndPrefix[*fulfillment.CourierId])
		res.Code = &code
	}
	if fulfillment.DeliveryId != nil {
		res.DeliveryPlanID = *fulfillment.DeliveryId
	}
	if fulfillment.PickupPointId != nil {
		res.PickupPointID = *fulfillment.PickupPointId
	}
	if fulfillment.PickupInstruction != nil {
		res.PickupInstruction = *fulfillment.PickupInstruction
	}
	if fulfillment.PickupTime != nil {
		pickupTime := fulfillment.PickupTime.AsTime()
		if !pickupTime.IsZero() {
			res.PickupTime = &pickupTime
		}
	}
	if fulfillment.PickupStartTime != nil {
		pickupStartTime := fulfillment.PickupStartTime.AsTime()
		if !pickupStartTime.IsZero() {
			res.PickupStartTime = &pickupStartTime
		}
	}
	if fulfillment.PickupEndTime != nil {
		pickupEndTime := fulfillment.PickupEndTime.AsTime()
		if !pickupEndTime.IsZero() {
			res.PickupEndTime = &pickupEndTime
		}
	}
	if order.Dimension != nil {
		res.WeightDimension = entities.ShipmentWeightDimension{}
		if order.Dimension.Weight != nil {
			res.WeightDimension.Weight = *order.Dimension.Weight
		}
		if order.Dimension.Width != nil {
			res.WeightDimension.Width = *order.Dimension.Width
		}
		if order.Dimension.Length != nil {
			res.WeightDimension.Length = *order.Dimension.Length
		}
		if order.Dimension.Height != nil {
			res.WeightDimension.Height = *order.Dimension.Height
		}
	}

	return res
}

func FulfillmentDetailToShipmentEntities(fulfillment *pb.GetFulfillmentResponse, workspaceID string) []*entities.Shipment {
	shipments := make([]*entities.Shipment, 0)
	for _, order := range fulfillment.OrderDetails {
		shipments = append(shipments, GetFulfillmentResponseToShipmentEntity(fulfillment, order, workspaceID))
	}
	return shipments
}

func GetShipmentResponseEntityToDTO(entity *entities.Shipment, localized bool) *dto.GetShipmentResponse {
	return &dto.GetShipmentResponse{
		Data: ShipmentEntityToDTO(entity, localized),
	}
}

var shipmentStatusMap = map[entities.ShipmentStatus]dto.ShipmentStatus{
	entities.ShipmentStatusFulfillmentRequestSent: dto.ShipmentStatusFulfillmentRequestSent,
	entities.ShipmentStatusCourierStaffAssigned:   dto.ShipmentStatusCourierStaffAssigned,
	entities.ShipmentStatusPickedUp:               dto.ShipmentStatusPickedUp,
	entities.ShipmentStatusOnDelivery:             dto.ShipmentStatusOnDelivery,
	entities.ShipmentStatusDelivered:              dto.ShipmentStatusDelivered,
	entities.ShipmentStatusFailedDelivery:         dto.ShipmentStatusFailedDelivery,
	entities.ShipmentStatusCancelled:              dto.ShipmentStatusCancelled,
}

var shipmentStatusDTOToEntityMap = map[dto.ShipmentStatus]entities.ShipmentStatus{
	dto.ShipmentStatusFulfillmentRequestSent: entities.ShipmentStatusFulfillmentRequestSent,
	dto.ShipmentStatusCourierStaffAssigned:   entities.ShipmentStatusCourierStaffAssigned,
	dto.ShipmentStatusPickedUp:               entities.ShipmentStatusPickedUp,
	dto.ShipmentStatusOnDelivery:             entities.ShipmentStatusOnDelivery,
	dto.ShipmentStatusDelivered:              entities.ShipmentStatusDelivered,
	dto.ShipmentStatusFailedDelivery:         entities.ShipmentStatusFailedDelivery,
}

func ShipmentEntityToDTO(entity *entities.Shipment, localized bool) *dto.Shipment {
	items := make([]*dto.ShipmentItem, 0, len(entity.Items))
	for _, v := range entity.Items {
		items = append(items, &dto.ShipmentItem{
			Code:        v.Code,
			Description: v.Description,
			Quantity:    v.Quantity,
		})
	}

	subLevelLocations := make([]dto.ReceiverInfoSubLevelLocation, 0)
	for _, v := range entity.ReceiverInfo.SubLevelLocations {
		subLevelLocations = append(subLevelLocations, dto.ReceiverInfoSubLevelLocation{
			InternationalName: v.InternationalName,
			LocalizedName:     v.LocalizedName,
		})
	}

	status, ok := shipmentStatusMap[entity.Status]
	if !ok {
		status = dto.ShipmentStatus("")
	}

	res := &dto.Shipment{
		ID:                   entity.ID.String(),
		Status:               status,
		CourierStatus:        entity.CourierStatus,
		CourierWebhookStatus: entity.CourierWebhookStatus,
		Courier: dto.Courier{
			ID:         entity.Courier.ID.ToInt64(),
			Name:       entity.Courier.Name,
			CountryID:  entity.Courier.CountryID,
			Deactivate: entity.Courier.Deactivate,
			Code:       entity.Courier.Code,
			Type:       entity.Courier.Type,
		},
		WeightDimension: dto.ShipmentWeightDimension{
			Weight: entity.WeightDimension.Weight,
			Width:  entity.WeightDimension.Width,
			Length: entity.WeightDimension.Length,
			Height: entity.WeightDimension.Height,
		},
		Fee:               entity.Fee,
		PickupTime:        entity.PickupTime,
		PickupStartTime:   entity.PickupStartTime,
		PickupEndTime:     entity.PickupEndTime,
		PickupInstruction: entity.PickupInstruction,
		CartID:            entity.CartID,
		ShippingGroupID:   entity.ShippingGroupID,
		ShipmentCode:      entity.Code,
		BuyerInfo: dto.ShipmentBuyerInfo{
			RecipientName:     entity.ReceiverInfo.RecipientName,
			Name:              entity.ReceiverInfo.Name,
			Email:             entity.ReceiverInfo.Email,
			ContactNumber:     entity.ReceiverInfo.ContactNumber,
			CountryCode:       entity.ReceiverInfo.CountryCode,
			PostalCode:        entity.ReceiverInfo.PostalCode,
			Instructions:      entity.ReceiverInfo.Instructions,
			UnitNumber:        entity.ReceiverInfo.UnitNumber,
			AddressLine1:      entity.ReceiverInfo.AddressLine1,
			AddressLine2:      entity.ReceiverInfo.AddressLine2,
			SubLevelLocations: subLevelLocations,
		},
		Items:      items,
		Printable:  entity.LabelMetadata != nil,
		CreatedAt:  &entity.CreatedAt,
		StateDate:  &entity.StateDate,
		ArchivedAt: entity.ArchivedAt,
	}

	// If courier status is `cancelled` then return detailed cancellation info
	if entity.Status == entities.ShipmentStatusCancelled {
		res.CancelReasonCode = entity.CancelReasonCode
		res.CancelReasonText = entity.CancelReasonText
	}

	pickupPoint := ToPickupPointDTO(&entity.PickupPoint)
	if pickupPoint != nil {
		res.PickupPoint = *pickupPoint
	}

	deliveryPlan := DeliveryPlanEntityToDTO(&entity.CourierDeliveryPlan)
	if deliveryPlan != nil {
		res.DeliveryPlan = *deliveryPlan
	}

	if entity.CourierTrackingID != nil {
		res.CourierTrackingID = *entity.CourierTrackingID
	}

	return res
}

func ShipmentEntitiesToDTOs(entities []*entities.Shipment, acceptLanguage string) []*dto.Shipment {
	userCountryID := utils.ConvertAcceptLanguageToCountryID(acceptLanguage)
	values := make([]*dto.Shipment, 0, len(entities))
	for _, v := range entities {
		values = append(values, ShipmentEntityToDTO(v, userCountryID == v.Courier.CountryID))
	}
	return values
}

func CreateShipmentErrorsToFailedShipmentDTOs(errs []*shipment.CreateShipmentError, localized bool) []*dto.FailedShipment {
	results := make([]*dto.FailedShipment, 0, len(errs))
	for _, v := range errs {
		results = append(results, &dto.FailedShipment{
			Code:            v.Error.Code.Value(),
			Message:         v.Error.Message,
			CartID:          v.Shipment.CartID,
			ShippingGroupID: v.Shipment.ShippingGroupID,
			ShipmentCode:    v.Shipment.Code,
			Detail: dto.FailedShipmentDetail{
				PickupPoint:  ToPickupPointDTO(&v.Shipment.PickupPoint),
				ReceiverInfo: ShipmentReceiverInfoEntityToDTO(&v.Shipment.ReceiverInfo, localized),
			},
		})
	}
	return results
}

func GetShipmentsParamsDTOToEntity(req *dto.GetShipmentsRequest, workspaceID string) *entities.GetShipmentsParams {
	return &entities.GetShipmentsParams{
		WorkspaceID:   &workspaceID,
		Status:        &req.Status,
		SearchKeyword: req.SearchKeyword,
		Pagination: &common.Pagination{
			Limit: req.Limit,
			Page:  req.Page,
			Sort:  req.Sort,
		},
	}
}

func GetTrackingUrlsEntitiesToDTOs(ctx context.Context, entities []*entities.Shipment, esf factory.IExternalShipmentFactory) []*dto.TrackingUrl {
	values := make([]*dto.TrackingUrl, 0, len(entities))
	for _, v := range entities {
		if v.CourierTrackingID == nil {
			continue
		}
		es, err := esf.Get(v.Courier.Code)
		if err != nil {
			continue
		}
		values = append(values, &dto.TrackingUrl{
			ShipmentID:      v.ID.String(),
			CartID:          v.CartID,
			ShippingGroupID: v.ShippingGroupID,
			TrackingUrl:     es.GetTrackingUrl(ctx, *v.CourierTrackingID),
		})
	}
	return values
}

func GetShipmentLabelsResponseEntityToDTO(data []*entities.GetShippingLabelsData) *dto.GetShipmentLabelsResponse {
	res := make([]*dto.GetShipmentLabelData, 0, len(data))
	for _, v := range data {
		res = append(res, &dto.GetShipmentLabelData{
			ShipmentID: v.ID,
			URL:        v.URL,
			Error:      v.Error,
		})
	}
	return &dto.GetShipmentLabelsResponse{
		Data: res,
	}
}

func GetShipmentStatusLogEntityToDTO(shipment *entities.Shipment, logs []*entities.ShipmentStatusLog) *dto.GetShipmentStatusResponse {
	var courierTrackingID string
	if shipment.CourierTrackingID != nil {
		courierTrackingID = *shipment.CourierTrackingID
	}

	statusHistory := make([]*dto.GetShipmentStatusItemResponse, 0)
	for _, v := range logs {
		statusHistory = append(statusHistory, &dto.GetShipmentStatusItemResponse{
			Status:        v.Status.Value(),
			CourierStatus: v.CourierStatus,
			StateText:     v.StateText,
			StateDate:     v.StateDate,
			CreatedAt:     v.CreatedAt,
		})
	}

	return &dto.GetShipmentStatusResponse{
		ID:                shipment.ID.String(),
		CourierTrackingID: courierTrackingID,
		StatusHistory:     statusHistory,
	}
}

func DevUpdateShipmentDTOToEntity(dto *dto.DevUpdateShipmentRequest) *entities.DevUpdateShipmentParams {
	return &entities.DevUpdateShipmentParams{
		CourierTrackingID:    dto.CourierTrackingID,
		CourierStatus:        dto.CourierStatus,
		CancelReasonCode:     dto.CancelReasonCode,
		CancelReasonText:     dto.CancelReasonText,
		CourierWebhookStatus: dto.CourierWebhookStatus,
	}
}

func ShipmentReceiverInfoEntityToDTO(entity *entities.ShipmentReceiverInfo, localized bool) *dto.ReceiverInfo {
	subLevelLocations := make([]dto.ReceiverInfoSubLevelLocation, 0)
	for _, v := range entity.SubLevelLocations {
		subLevelLocations = append(subLevelLocations, dto.ReceiverInfoSubLevelLocation{
			InternationalName: v.InternationalName,
			LocalizedName:     v.LocalizedName,
		})
	}
	return &dto.ReceiverInfo{
		RecipientName:     entity.RecipientName,
		ContactNumber:     entity.ContactNumber,
		PostalCode:        entity.PostalCode,
		AddressLine1:      entity.AddressLine1,
		AddressLine2:      entity.AddressLine2,
		UnitNumber:        entity.UnitNumber,
		SubLevelLocations: subLevelLocations,
		Instructions:      entity.Instructions,
	}
}

func ShipmentsEntityToShipmentOrdersStatusDTO(entities []*entities.Shipment) []dto.ShipmentOrderStatus {
	statuses := make([]dto.ShipmentOrderStatus, 0, len(entities))
	for _, v := range entities {
		statuses = append(statuses, dto.ShipmentOrderStatus{
			CartID:            v.CartID,
			ShippingGroupID:   v.ShippingGroupID,
			Status:            v.Status.Value(),
			StatusAt:          v.StateDate,
			CourierID:         v.CourierID,
			CourierTrackingID: v.CourierTrackingID,
			CancelReason:      v.CancelReasonText,
		})
	}
	return statuses
}

func GetArchivedShipmentRequestToGetShipmentsParams(req *dto.GetArchivedShipmentsRequest, workspaceID string) *entities.GetShipmentsParams {
	archived := true

	sortBy := "archived_at"
	if req.SortBy == entities.SortByArchivedAt {
		sortBy = "archived_at"
	}
	if req.SortBy == entities.SortByCreatedAt {
		sortBy = "created_at"
	}

	orderBy := "desc"
	if req.OrderBy == entities.OrderByDESC {
		orderBy = "desc"
	}
	if req.OrderBy == entities.OrderByASC {
		orderBy = "asc"
	}

	return &entities.GetShipmentsParams{
		WorkspaceID:   &workspaceID,
		SearchKeyword: req.Search,
		OnlyArchived:  &archived,
		Pagination: &common.Pagination{
			Limit: req.Limit,
			Page:  req.Page,
			Sort:  fmt.Sprintf("%s %s", sortBy, orderBy),
		},
	}
}

func GetShipmentOrdersStatusRequestToGRPCGetPaidOrdersRequest(req *dto.GetShipmentOrdersStatusRequest) *pb.GetPaidOrdersRequest {
	return &pb.GetPaidOrdersRequest{
		CartIds: req.CartIDs,
	}
}
