package service

import (
	"errors"
	"gorm.io/gorm"

	"github.com/UpmeshLTD/urus-aio/infrastructure/db/entities"
	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 iDeliveryAddress interface {
	GetDeliveryAddress(c *gin.Context)
	GetDeliveryAddressBySeller(c *gin.Context)
	CreateDeliveryAddressByBuyer(c *gin.Context)
	CreateDeliveryAddressByGuest(c *gin.Context)
	CreateDeliveryAddressBySeller(c *gin.Context)
	UpdateRecipientName(c *gin.Context)
	UpdateRecipientPhoneNumber(c *gin.Context)
	UpdateAddress(c *gin.Context)
	GetDeliveryAddressLogs(c *gin.Context)
}

// GetDeliveryAddress godoc
// @Summary Get delivery address for buyer
// @Description Get delivery address for buyer
// @Tags Delivery Address
// @Accept */*
// @Produce json
// @Param GetDeliveryAddressRequest query dto.GetDeliveryAddressRequest true "GetDeliveryAddressRequest"
// @Success 200 {object} dto.DeliveryAddress
// @Router /delivery-addresses/{cartId} [GET]
func (s *serviceImpl) GetDeliveryAddress(c *gin.Context) {
	req := &dto.GetDeliveryAddressRequest{}
	if err := gin2.BindData(c, &req, gin2.BindType_URI); err != nil {
		gin2.ResponseBadRequest(c, err)
		return
	}

	deliveryAddress, err := s.deliveryAddressDomain.Get(getCtx(c), req.CartID)
	if err != nil {
		gin2.ResponseInternalServerError(c, err)
		return
	}

	gin2.ResponseSuccess(c, transform.DeliveryAddressEntityToDTO(deliveryAddress))
}

// CreateDeliveryAddressByBuyer Buyer create delivery address godoc
// @Summary Create delivery address for buyer
// @Description Create or update a delivery address for buyer
// @Tags Delivery Address
// @Accept json
// @Produce json
// @Param request body dto.CreateDeliveryAddressRequest true "request"
// @Success 200 {object} dto.DeliveryAddress
// @Security BearerToken
// @Router /delivery-addresses [POST]
func (s *serviceImpl) CreateDeliveryAddressByBuyer(c *gin.Context) {
	req := &dto.CreateDeliveryAddressRequest{}
	if err := gin2.BindData(c, &req, gin2.BindType_JSON); err != nil {
		gin2.ResponseBadRequest(c, err)
		return
	}

	pos, err := s.paidOrderDomain.GetByCartIDs(c, []string{req.CartID})
	if err != nil {
		gin2.ResponseInternalServerError(c, err)
		return
	}
	if len(pos) > 0 {
		gin2.ResponseBadRequest(c, errors.New("delivery address already exists"))
		return
	}

	da, err := s.deliveryAddressDomain.Get(c, req.CartID)
	if err != nil && err != gorm.ErrRecordNotFound {
		gin2.ResponseInternalServerError(c, err)
		return
	}

	if da != nil {
		entity := transform.CreateDeliveryAddressRequestToEntity(req, middleware.GetBuyerId(c))
		entity.AddressID = da.AddressID
		if err := s.deliveryAddressDomain.Update(c, entity); err != nil {
			gin2.ResponseInternalServerError(c, err)
			return
		}
	} else {
		if err := s.deliveryAddressDomain.Create(c, transform.CreateDeliveryAddressRequestToEntity(req, middleware.GetBuyerId(c))); err != nil {
			gin2.ResponseInternalServerError(c, err)
			return
		}
	}

	da, err = s.deliveryAddressDomain.Get(c, req.CartID)
	if err != nil {
		gin2.ResponseInternalServerError(c, err)
		return
	}

	gin2.ResponseSuccess(c, transform.DeliveryAddressEntityToDTO(da))
}

// CreateDeliveryAddressByGuest Guest create delivery address godoc
// @Summary Create or update delivery address for guest
// @Description Create or update delivery address for guest
// @Tags Delivery Address
// @Accept json
// @Produce json
// @Param request body dto.CreateDeliveryAddressRequest true "request"
// @Success 200 {object} dto.DeliveryAddress
// @Router /guest/delivery-addresses [POST]
func (s *serviceImpl) CreateDeliveryAddressByGuest(c *gin.Context) {
	req := &dto.CreateDeliveryAddressRequest{}
	if err := gin2.BindData(c, &req, gin2.BindType_JSON); err != nil {
		gin2.ResponseBadRequest(c, err)
		return
	}

	pos, err := s.paidOrderDomain.GetByCartIDs(c, []string{req.CartID})
	if err != nil {
		gin2.ResponseInternalServerError(c, err)
		return
	}
	if len(pos) > 0 {
		gin2.ResponseBadRequest(c, errors.New("delivery address already exists"))
		return
	}

	da, err := s.deliveryAddressDomain.Get(c, req.CartID)
	if err != nil && err != gorm.ErrRecordNotFound {
		gin2.ResponseInternalServerError(c, err)
		return
	}

	if da != nil {
		entity := transform.CreateDeliveryAddressRequestToEntity(req, "")
		entity.AddressID = da.AddressID
		if err := s.deliveryAddressDomain.Update(c, entity); err != nil {
			gin2.ResponseInternalServerError(c, err)
			return
		}
	} else {
		if err := s.deliveryAddressDomain.Create(c, transform.CreateDeliveryAddressRequestToEntity(req, "")); err != nil {
			gin2.ResponseInternalServerError(c, err)
			return
		}
	}

	da, err = s.deliveryAddressDomain.Get(c, req.CartID)
	if err != nil {
		gin2.ResponseInternalServerError(c, err)
		return
	}

	gin2.ResponseSuccess(c, transform.DeliveryAddressEntityToDTO(da))
}

// CreateDeliveryAddressBySeller Seller create delivery address godoc
// @Summary Create or update delivery address for Seller
// @Description Create or update a delivery address for Seller
// @Tags Delivery Address
// @Accept json
// @Produce json
// @Param userworkspace header string true "User workspace")
// @Param request body dto.CreateDeliveryAddressOptionalRequest true "request"
// @Success 200 {object} dto.DeliveryAddress
// @Security BearerToken
// @Router /delivery-addresses/by-seller [POST]
func (s *serviceImpl) CreateDeliveryAddressBySeller(c *gin.Context) {
	req := &dto.CreateDeliveryAddressOptionalRequest{}
	if err := gin2.BindData(c, &req, gin2.BindType_JSON); err != nil {
		gin2.ResponseBadRequest(c, err)
		return
	}

	pos, err := s.paidOrderDomain.GetByCartIDs(c, []string{req.CartID})
	if err != nil {
		gin2.ResponseInternalServerError(c, err)
		return
	}
	if len(pos) > 0 {
		gin2.ResponseBadRequest(c, errors.New("delivery address already exists"))
		return
	}

	da, err := s.deliveryAddressDomain.Get(c, req.CartID)
	if err != nil && err != gorm.ErrRecordNotFound {
		gin2.ResponseInternalServerError(c, err)
		return
	}

	if da != nil {
		entity := transform.CreateDeliveryAddressOptionalRequestToEntity(req, middleware.GetWorkspaceId(c))
		entity.AddressID = da.AddressID
		if err := s.deliveryAddressDomain.Update(c, entity); err != nil {
			gin2.ResponseInternalServerError(c, err)
			return
		}
	} else {
		if err := s.deliveryAddressDomain.Create(c, transform.CreateDeliveryAddressOptionalRequestToEntity(req, middleware.GetWorkspaceId(c))); err != nil {
			gin2.ResponseInternalServerError(c, err)
			return
		}
	}

	da, err = s.deliveryAddressDomain.Get(c, req.CartID)
	if err != nil {
		gin2.ResponseInternalServerError(c, err)
		return
	}

	gin2.ResponseSuccess(c, transform.DeliveryAddressEntityToDTO(da))
}

// GetDeliveryAddressBySeller Seller get delivery address godoc
// @Summary Get delivery address for seller
// @Description Get delivery address for seller
// @Tags Delivery Address
// @Accept */*
// @Produce json
// @Param userworkspace header string true "User workspace")
// @Param cartId path string true "Cart ID"
// @Success 200 {object} dto.DeliveryAddress
// @Security BearerToken
// @Router /delivery-addresses/{cartId}/by-seller [GET]
func (s *serviceImpl) GetDeliveryAddressBySeller(c *gin.Context) {
	req := &dto.GetDeliveryAddressRequest{}
	if err := gin2.BindData(c, &req, gin2.BindType_URI); err != nil {
		gin2.ResponseBadRequest(c, err)
		return
	}

	deliveryAddress, err := s.deliveryAddressDomain.Get(getCtx(c), req.CartID)
	if err != nil {
		gin2.ResponseInternalServerError(c, err)
		return
	}
	gin2.ResponseSuccess(c, transform.DeliveryAddressEntityToDTO(deliveryAddress))
}

// UpdateRecipientName Seller update recipient name godoc
// @Summary Seller update recipient name
// @Description Seller update recipient name
// @Tags Delivery Address
// @Accept json
// @Produce json
// @Param userworkspace header string true "User workspace")
// @Param cartId path string true "Cart ID"
// @Param request body dto.UpdateRecipientNameRequest true "request"
// @Success 200 {object} dto.DeliveryAddress
// @Security BearerToken
// @Router /delivery-addresses/{cartId}/recipient-name [PUT]
func (s *serviceImpl) UpdateRecipientName(c *gin.Context) {
	req := &dto.UpdateRecipientNameRequest{}
	if err := gin2.BindData(c, &req, gin2.BindType_JSON, gin2.BindType_URI); err != nil {
		gin2.ResponseBadRequest(c, err)
		return
	}

	if req.CountryCode == "" {
		req.CountryCode = s.config.GetAppConfig().Region
	}

	da, err := s.deliveryAddressDomain.Get(c, req.CartID)
	if err != nil && err != gorm.ErrRecordNotFound {
		gin2.ResponseInternalServerError(c, err)
		return
	}

	if da == nil {
		if err := s.deliveryAddressDomain.Create(c, transform.UpdateRecipientNameRequestToDeliveryAddressEntity(req)); err != nil {
			gin2.ResponseInternalServerError(c, err)
			return
		}
	} else {
		if err := s.deliveryAddressDomain.UpdatePatch(c, da, transform.UpdateRecipientNameRequestToDeliveryAddressParams(req)); err != nil {
			gin2.ResponseInternalServerError(c, err)
			return
		}
		if err := s.deliveryAddressLogDomain.Create(c, da); err != nil {
			gin2.ResponseInternalServerError(c, err)
			return
		}
	}

	da, err = s.deliveryAddressDomain.Get(c, req.CartID)
	if err != nil {
		gin2.ResponseInternalServerError(c, err)
		return
	}

	gin2.ResponseSuccess(c, transform.DeliveryAddressEntityToDTO(da))
}

// UpdateRecipientPhoneNumber Seller update recipient phone number godoc
// @Summary Seller update recipient phone number
// @Description Seller update recipient phone number
// @Tags Delivery Address
// @Accept json
// @Produce json
// @Param userworkspace header string true "User workspace")
// @Param cartId path string true "Cart ID"
// @Param request body dto.UpdateRecipientPhoneNumberRequest true "request"
// @Success 200 {object} dto.DeliveryAddress
// @Security BearerToken
// @Router /delivery-addresses/{cartId}/recipient-phone [PUT]
func (s *serviceImpl) UpdateRecipientPhoneNumber(c *gin.Context) {
	req := &dto.UpdateRecipientPhoneNumberRequest{}
	if err := gin2.BindData(c, &req, gin2.BindType_JSON, gin2.BindType_URI); err != nil {
		gin2.ResponseBadRequest(c, err)
		return
	}

	if req.CountryCode == "" {
		req.CountryCode = s.config.GetAppConfig().Region
	}

	da, err := s.deliveryAddressDomain.Get(c, req.CartID)
	if err != nil && err != gorm.ErrRecordNotFound {
		gin2.ResponseInternalServerError(c, err)
		return
	}

	if da == nil {
		if err := s.deliveryAddressDomain.Create(c, transform.UpdateRecipientPhoneNumberRequestToDeliveryAddressEntity(req)); err != nil {
			gin2.ResponseInternalServerError(c, err)
			return
		}
	} else {
		err = s.deliveryAddressDomain.UpdatePatch(getCtx(c), da, transform.UpdateRecipientPhoneNumberRequestToDeliveryAddressParams(req))
		if err != nil {
			gin2.ResponseInternalServerError(c, err)
			return
		}
		if err := s.deliveryAddressLogDomain.Create(c, da); err != nil {
			gin2.ResponseInternalServerError(c, err)
			return
		}
	}

	da, err = s.deliveryAddressDomain.Get(c, req.CartID)
	if err != nil {
		gin2.ResponseInternalServerError(c, err)
		return
	}

	gin2.ResponseSuccess(c, transform.DeliveryAddressEntityToDTO(da))
}

// UpdateAddress Seller update recipient address godoc
// @Summary Seller update recipient address
// @Description Seller update recipient address
// @Tags Delivery Address
// @Accept json
// @Produce json
// @Param userworkspace header string true "User workspace")
// @Param cartId path string true "Cart ID"
// @Param request body dto.UpdateAddressRequest true "request"
// @Success 200 {object} dto.DeliveryAddress
// @Security BearerToken
// @Router /delivery-addresses/{cartId}/address [PUT]
func (s *serviceImpl) UpdateAddress(c *gin.Context) {
	req := &dto.UpdateAddressRequest{}
	if err := gin2.BindData(c, &req, gin2.BindType_JSON, gin2.BindType_URI); err != nil {
		gin2.ResponseBadRequest(c, err)
		return
	}

	da, err := s.deliveryAddressDomain.Get(c, req.CartID)
	if err != nil && err != gorm.ErrRecordNotFound {
		gin2.ResponseInternalServerError(c, err)
		return
	}

	if da == nil {
		if err := s.deliveryAddressDomain.Create(c, transform.UpdateAddressRequestToDeliveryAddressEntity(req)); err != nil {
			gin2.ResponseInternalServerError(c, err)
			return
		}
	} else {
		err = s.deliveryAddressDomain.UpdatePatch(c, da, transform.UpdateAddressRequestToDeliveryAddressParams(req))
		if err != nil {
			gin2.ResponseInternalServerError(c, err)
			return
		}
		if err := s.deliveryAddressLogDomain.Create(c, da); err != nil {
			gin2.ResponseInternalServerError(c, err)
			return
		}
	}

	da, err = s.deliveryAddressDomain.Get(c, req.CartID)
	if err != nil {
		gin2.ResponseInternalServerError(c, err)
		return
	}

	gin2.ResponseSuccess(c, transform.DeliveryAddressEntityToDTO(da))
}

// GetDeliveryAddressLogs Get delivery address logs godoc
// @Summary Get delivery address logs
// @Description Get delivery address logs
// @Tags Delivery Address
// @Accept json
// @Produce json
// @Param userworkspace header string true "User workspace")
// @Param cartId path string true "Cart id"
// @Success 200 {object} dto.GetDeliveryAddressLogsResponse
// @Security BearerToken
// @Router /delivery-addresses/{cartId}/logs [GET]
func (s *serviceImpl) GetDeliveryAddressLogs(c *gin.Context) {
	req := &dto.GetDeliveryAddressLogsRequest{}
	if err := gin2.BindData(c, &req, gin2.BindType_URI, gin2.BindType_Query); err != nil {
		gin2.ResponseBadRequest(c, err)
		return
	}

	da, err := s.deliveryAddressDomain.Get(c, req.CartID)
	if err != nil {
		gin2.ResponseInternalServerError(c, err)
		return
	}

	logs, err := s.deliveryAddressLogDomain.GetList(c, &entities.GetDeliveryAddressLogsParams{
		CartID:  da.CartID,
		SortBy:  req.SortBy,
		OrderBy: req.OrderBy,
	})
	if err != nil {
		gin2.ResponseInternalServerError(c, err)
		return
	}

	gin2.ResponseSuccess(c, &dto.GetDeliveryAddressLogsResponse{
		Data: transform.DeliveryAddressLogEntitiesToDTOs(logs),
	})
}
