package service

import (
	"errors"
	"fmt"
	"github.com/UpmeshLTD/urus-aio/infrastructure/db/entities"
	e "github.com/UpmeshLTD/urus-aio/infrastructure/errors"
	m "github.com/UpmeshLTD/urus-aio/infrastructure/errors/messages"
	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/infrastructure/utils"
	"github.com/UpmeshLTD/urus-aio/shipment/external/shipment"
	kerryexpress_th "github.com/UpmeshLTD/urus-aio/shipment/external/shipment/partners/kerryexpress-th"
	"github.com/UpmeshLTD/urus-aio/shipment/server/http/dto"
	"github.com/UpmeshLTD/urus-aio/shipment/service/transform"
	"github.com/gin-gonic/gin"
)

type iSellerCourier interface {
	GetSellerCouriers(c *gin.Context)
	GetAccountAssociateStatus(c *gin.Context)
	CreateSellerCourier(c *gin.Context)
	UpdateSellerCourier(c *gin.Context)
	UpdateSellerCourierStatus(c *gin.Context)
	DeleteSellerCourier(c *gin.Context)
}

// GetSellerCouriers godoc
// @Summary Get list of all couriers of seller.
// @Description Get list of all couriers of seller.
// @Tags Seller Courier
// @Accept */*
// @Produce json
// @Param userworkspace header string true "User workspace")
// @Param GetSellerCouriersRequest query dto.GetSellerCouriersRequest false "GetSellerCouriersRequest"
// @Success 200 {object} dto.GetCouriersResponse
// @Security BearerToken
// @Router /seller-couriers [GET]
func (s *serviceImpl) GetSellerCouriers(c *gin.Context) {
	req := &dto.GetSellerCouriersRequest{}
	if err := gin2.BindData(c, req, gin2.BindType_Query); err != nil {
		gin2.ResponseBadRequest(c, err)
		return
	}

	result, err := s.courierDomain.GetCouriersByWorkspaceID(c, transform.GetSellerCouriersRequestDTOToEntity(req, middleware.GetWorkspaceId(c)))
	if err != nil {
		gin2.ResponseInternalServerError(c, err)
		return
	}

	for _, v := range result {
		v.CourierAuthenticationInfo = utils.Decrypt(s.config.GetShipmentProviderConfig().ShipmentEncryptPassphrase, v.CourierAuthenticationInfo, []byte(v.Salt))
	}

	gin2.ResponseSuccess(c, transform.GetSellerCouriersResponseEntityToDTO(result))
}

// CreateSellerCourier godoc
// @Summary CreateSellerCourier
// @Description Create a new seller courier
// @Tags Seller Courier
// @Accept json
// @Produce json
// @Param userworkspace header string true "User workspace")
// @Param request body dto.CreateSellerCourierRequest true "request"
// @Success 200 {object} dto.SellerCourier
// @Security BearerToken
// @Router /seller-couriers [POST]
func (s *serviceImpl) CreateSellerCourier(c *gin.Context) {
	req := &dto.CreateSellerCourierRequest{}
	if err := gin2.BindData(c, req, gin2.BindType_JSON); err != nil {
		gin2.ResponseBadRequest(c, err)
		return
	}

	courier, err := s.courierDomain.GetCourierByID(c, entities.ID{ID: req.CourierID})
	if err != nil {
		gin2.ResponseBadRequest(c, err)
		return
	}

	// KerryTH: validate merchant ID only numbers
	if courier.Code == entities.CourierCodeKerryExpressTH {
		if !utils.IsDigit(req.MerchantID) {
			gin2.ResponseBadRequest(c, e.Default(c, m.MerchantIDInvalidDigitNumbers))
			return
		}

		if len(req.MerchantID) > kerryexpress_th.MaxLengthMerchantID {
			gin2.ResponseBadRequest(c, errors.New(fmt.Sprintf("The length of merchant ID can't exceed %d digits", kerryexpress_th.MaxLengthMerchantID)))
			return
		}
	}

	ces, err := s.externalShipmentFactory.Get(courier.Code)
	if err != nil {
		gin2.ResponseInternalServerError(c, err)
		return
	}

	workspaceId := middleware.GetWorkspaceId(c)
	courierAuthenticationInfo := dto.CourierAuthenticationInfo{
		FlashExpress: dto.AuthenticationInfoFlashExpress{},
		FlashExpressPH: dto.AuthenticationInfoFlashExpressPH{
			MerchantCode: req.MerchantCode,
			Key:          req.Key,
		},
		JnT: dto.AuthenticationInfoJnT{
			ShopID:   req.ShopID,
			ShopName: req.ShopName,
		},
		JnTMalaysia: dto.AuthenticationInfoJnTMalaysia{
			CustomerCode: req.CustomerCode,
			Password:     req.Password,
		},
		NinjaVan: dto.AuthenticationInfoNinjaVan{
			SellerID:   req.SellerID,
			SellerName: req.SellerName,
		},
		JnTSingapore: dto.AuthenticationInfoJnTSingapore{
			MerchantCode: req.MerchantCode,
		},
		KerryExpressTH: dto.AuthenticationInfoKerryExpressTH{
			MerchantID: req.MerchantID,
		},
	}

	// FlashPH: validate merchant code
	if courier.Code == entities.CourierCodeFlashExpressPH {
		if err := ces.SetCourierWebhook(c, &shipment.SetCourierWebhookParams{
			Enabled: true,
		}, &courierAuthenticationInfo); err != nil {
			gin2.ResponseBadRequest(c, err)
			return
		}
	}

	encryptedInfo, salt, err := ces.EncryptCourierAuthenticationInfo(courierAuthenticationInfo, workspaceId)
	if err != nil {
		gin2.ResponseInternalServerError(c, err)
		return
	}

	// Update seller courier if already exists
	sellerCourier, _ := s.courierDomain.GetSellerCourier(c, workspaceId, courier.ID.ToInt64())
	if sellerCourier != nil {
		if err := s.courierDomain.UpdateSellerCourier(c, sellerCourier.ID, &entities.SellerCourier{
			CourierAuthenticationInfo: encryptedInfo,
			Salt:                      salt,
		}); err == nil {
			gin2.ResponseSuccess(c, transform.SellerCourierEntityToDTO(sellerCourier))
			return
		}
	}

	res, err := s.courierDomain.SaveSellerCourier(c, transform.CreateSellerCourierRequestDTOToEntity(req, workspaceId, encryptedInfo, salt, true))
	if err != nil || res == nil {
		gin2.ResponseInternalServerError(c, err)
		return
	}

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

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

	sellerCourier, err := s.courierDomain.GetSellerCourierByID(c, entities.ID{ID: req.ID})
	if err != nil {
		gin2.ResponseBadRequest(c, err)
	}

	workspaceId := middleware.GetWorkspaceId(c)
	if sellerCourier.WorkspaceID != workspaceId {
		gin2.ResponseBadRequest(c, errors.New("record not found"))
	}

	// KerryTH: validate merchant ID only numbers
	if sellerCourier.Courier.Code == entities.CourierCodeKerryExpressTH {
		if !utils.IsDigit(req.MerchantID) {
			gin2.ResponseBadRequest(c, e.Default(c, m.MerchantIDInvalidDigitNumbers))
			return
		}

		if len(req.MerchantID) > kerryexpress_th.MaxLengthMerchantID {
			gin2.ResponseBadRequest(c, errors.New(fmt.Sprintf("The length of merchant ID can't exceed %d digits", kerryexpress_th.MaxLengthMerchantID)))
			return
		}
	}

	ces, err := s.externalShipmentFactory.Get(sellerCourier.Courier.Code)
	if err != nil {
		gin2.ResponseInternalServerError(c, err)
		return
	}

	courierAuthenticationInfo := dto.CourierAuthenticationInfo{
		FlashExpress: dto.AuthenticationInfoFlashExpress{},
		FlashExpressPH: dto.AuthenticationInfoFlashExpressPH{
			MerchantCode: req.MerchantCode,
			Key:          req.Key,
		},
		JnT: dto.AuthenticationInfoJnT{
			ShopID:   req.ShopID,
			ShopName: req.ShopName,
		},
		JnTMalaysia: dto.AuthenticationInfoJnTMalaysia{
			CustomerCode: req.CustomerCode,
			Password:     req.Password,
		},
		NinjaVan: dto.AuthenticationInfoNinjaVan{
			SellerID:   req.SellerID,
			SellerName: req.SellerName,
		},
		JnTSingapore: dto.AuthenticationInfoJnTSingapore{
			MerchantCode: req.MerchantCode,
		},
	}

	// FlashPH: validate merchant code
	if sellerCourier.Courier.Code == entities.CourierCodeFlashExpressPH {
		if err := ces.SetCourierWebhook(c, &shipment.SetCourierWebhookParams{
			Enabled: true,
		}, &courierAuthenticationInfo); err != nil {
			gin2.ResponseBadRequest(c, err)
			return
		}
	}

	encryptedInfo, salt, err := ces.EncryptCourierAuthenticationInfo(courierAuthenticationInfo, workspaceId)
	if err != nil {
		gin2.ResponseInternalServerError(c, err)
		return
	}

	if err := s.courierDomain.UpdateSellerCourier(c, entities.IDFromInt64(req.ID), &entities.SellerCourier{
		CourierAuthenticationInfo: encryptedInfo,
		Salt:                      salt,
	}); err != nil {
		gin2.ResponseInternalServerError(c, err)
		return
	}

	gin2.ResponseNoContent(c)
}

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

	sellerCourier, err := s.courierDomain.GetSellerCourierByID(c, entities.ID{ID: req.ID})
	if err != nil {
		gin2.ResponseBadRequest(c, err)
	}

	workspaceId := middleware.GetWorkspaceId(c)
	if sellerCourier.WorkspaceID != workspaceId {
		gin2.ResponseBadRequest(c, errors.New("record not found"))
	}

	if err := s.courierDomain.UpdateSellerCourierStatus(c, entities.IDFromInt64(req.ID), req.Deactivate); err != nil {
		gin2.ResponseInternalServerError(c, err)
		return
	}

	gin2.ResponseNoContent(c)
}

// DeleteSellerCourier godoc
// @Summary DeleteSellerCourier
// @Description Delete a seller courier
// @Tags Seller Courier
// @Accept json
// @Produce json
// @Param userworkspace header string true "User workspace")
// @Param id path int true "Seller Courier ID"
// @Success 204
// @Security BearerToken
// @Router /seller-couriers/{id} [DELETE]
func (s *serviceImpl) DeleteSellerCourier(c *gin.Context) {
	req := &dto.DeleteSellerCourierRequest{}
	if err := gin2.BindData(c, req, gin2.BindType_URI); err != nil {
		gin2.ResponseBadRequest(c, err)
		return
	}

	sellerCourier, err := s.courierDomain.GetSellerCourierByID(c, entities.ID{ID: req.ID})
	if err != nil {
		gin2.ResponseBadRequest(c, err)
	}

	workspaceId := middleware.GetWorkspaceId(c)
	if sellerCourier.WorkspaceID != workspaceId {
		gin2.ResponseBadRequest(c, errors.New("record not found"))
	}

	if sellerCourier.Courier.Code == entities.CourierCodeFlashExpressPH {
		go func() {
			ces, err := s.externalShipmentFactory.Get(sellerCourier.Courier.Code)
			if err != nil {
				return
			}

			info, err := ces.DecryptCourierAuthenticationInfo(sellerCourier.CourierAuthenticationInfo, sellerCourier.Salt)
			if err != nil {
				return
			}

			_ = ces.SetCourierWebhook(c, &shipment.SetCourierWebhookParams{
				Enabled: false,
			}, &dto.CourierAuthenticationInfo{
				FlashExpressPH: dto.AuthenticationInfoFlashExpressPH{
					MerchantCode: info.FlashExpressPH.MerchantCode,
					Key:          info.FlashExpressPH.Key,
				},
			})
		}()
	}

	if err := s.courierDomain.DeleteSellerCourier(c, entities.IDFromInt64(req.ID)); err != nil {
		gin2.ResponseInternalServerError(c, err)
		return
	}

	gin2.ResponseNoContent(c)
}

// GetAccountAssociateStatus godoc
// @Summary Get account associate status.
// @Description Get account associate status.
// @Tags Seller Courier
// @Accept */*
// @Produce json
// @Param id path int true "Seller Courier ID"
// @Success 200 {object} dto.GetAccountAssociateStatusResponse
// @Security BearerToken
// @Router /seller-couriers/{id}/associate-status [GET]
func (s *serviceImpl) GetAccountAssociateStatus(c *gin.Context) {
	req := &dto.GetAccountAssociateStatusRequest{}
	if err := gin2.BindData(c, req, gin2.BindType_URI); err != nil {
		gin2.ResponseBadRequest(c, err)
		return
	}

	sc, err := s.courierDomain.GetSellerCourierByID(c, entities.ID{ID: req.SellerCourierID})
	if err != nil {
		gin2.ResponseBadRequest(c, err)
	}

	ces, err := s.externalShipmentFactory.Get(sc.Courier.Code)
	if err != nil {
		gin2.ResponseInternalServerError(c, err)
		return
	}

	status, err := ces.GetAccountAssociateStatus(c, sc)
	if err != nil {
		gin2.ResponseInternalServerError(c, err)
		return
	}

	if status {
		// update verified status
		sc.Verified = true
		if err := s.courierDomain.UpdateSellerCourier(c, sc.ID, sc); err != nil {
			gin2.ResponseInternalServerError(c, err)
			return
		}
	}

	gin2.ResponseSuccess(c, dto.GetAccountAssociateStatusResponse{
		Associated: status,
	})
}
