package thirdpay

import (
	"PaymentCenter/app/constants/common"
	"PaymentCenter/app/constants/errorcode"
	"PaymentCenter/app/http/entities/backend"
	"PaymentCenter/app/http/entities/front"
	"PaymentCenter/app/models/orderrequestlogmodel"
	"PaymentCenter/app/models/ordersmodel"
	"PaymentCenter/app/models/paychannelmodel"
	"PaymentCenter/app/services"
	"PaymentCenter/app/services/thirdpay/do"
	"PaymentCenter/app/third/paymentService"
	"PaymentCenter/app/third/paymentService/payCommon"
	"PaymentCenter/app/utils"
	"PaymentCenter/config"
	"context"
	"encoding/json"
	"fmt"
	"strconv"
	"time"
	"xorm.io/builder"
)

// 收银台获取付款链接
func GetPayLinkService(req front.GetPayLinkRequest) (result, message string, code int) {
	defer func() {
		if err := recover(); err != nil {
			utils.Log(nil, "", "GetPayLinkService,获取支付链接失败", fmt.Sprintf("错误原因:%s", err))
			code = errorcode.SystemError
		}
	}()
	var (
		payChannel      = &paychannelmodel.PayChannel{}
		order           = &ordersmodel.Orders{}
		orderPayRequest front.PayReqsV2
	)
	// 支付方式校验
	payChannel, code = services.PayChannelFindOne(payChannel, builder.Eq{"id": req.PayChannelId})
	if code != errorcode.Success {
		return
	}
	if !payChannel.ExpireTime.IsZero() && payChannel.ExpireTime.Unix() < time.Now().Unix() {
		code = errorcode.PayChannelExpired
		return
	}
	// 订单校验
	order, code = services.OrderFindOne(order, builder.Eq{"id": req.OrderId})
	if code != errorcode.Success {
		return
	}
	// 订单状态校验
	code = services.OrderStatusCheck(*order)
	if code != errorcode.Success {
		return
	}
	// 订单的支付方式是否一致, 不一致则更新
	if order.PayChannelId != payChannel.Id {
		order.PayChannelId = payChannel.Id
		code = services.OrderUpdate(order)
		if code != errorcode.Success {
			return
		}
	}
	// 获取订单的请求参数
	orderRequestLog := orderrequestlogmodel.OrderRequestLog{
		OutTradeNo: order.OutTradeNo,
		AppId:      order.AppId,
	}
	has, err := services.OrderRequestLogs(&orderRequestLog)
	if err != nil {
		code = handErr(err)
		return
	}
	if !has {
		code = errorcode.OrderPayRequestLogNotExist
		return
	}
	err = json.Unmarshal([]byte(orderRequestLog.MerchantRequest), &orderPayRequest)
	if err != nil {
		code = handErr(err)
		return
	}
	// 支付方式需要用户授权
	if payChannel.ChannelType == common.PAY_CHANNEL_WECHAT_JSAPI && orderPayRequest.OpenId == "" {
		result, code = do.GetWxAuthUrl(payChannel.AppId, order.Id)
		return
	}
	// 请求第三方支付获取付款链接
	payParam := paymentService.PayOrderRequest{
		PayChannelId:  payChannel.Id,
		OrderId:       order.Id,
		ChannelType:   payChannel.ChannelType,
		Description:   orderPayRequest.Desc,
		Amount:        orderPayRequest.Amount,
		PayerClientIp: req.ClientIp,
		ReturnUrl:     orderPayRequest.ReturnUrl,
		OpenId:        orderPayRequest.OpenId,
	}
	// 判断是否是支持的支付渠道,找到对应的配置
	var payFunc func(commonPayInfo *paymentService.PayOrderRequest, channel *paychannelmodel.PayChannel) error
	var ok bool
	if payFunc, ok = do.PayWayList[payChannel.ChannelType]; !ok {
		code = errorcode.PayChannelNotBuild
		return
	}
	err = payFunc(&payParam, payChannel)
	if err != nil {
		code = errorcode.PayChannelExtJsonError
		return
	}

	res := paymentService.PaymentService(context.Background(), payParam)
	// 下单失败
	if res.Code != payCommon.PAY_SUCCESS_CODE {
		code = errorcode.PrePayFail
		message = res.ErrorMsg
		return
	}
	// 更新订单状态
	order.Status = common.ORDER_STATUS_PAYING
	code = services.OrderUpdate(order, "status")
	if code != errorcode.Success {
		utils.Log(nil, "成功下单,更新订单状态失败", order)
	}

	result = res.Result
	code = res.Code
	return
}

// 预支付
type payUrl struct {
	ClientIp string
	param    front.PayReqsV2
	app      *services.AppCheck
	result   front.PayReqsV2Response
}

func NewPayUrl(param front.PayReqsV2) *payUrl {
	return &payUrl{
		param: param,
	}
}
func (this *payUrl) WithApp(app *services.AppCheck) *payUrl {
	this.app = app
	return this
}
func (this *payUrl) WithClientIp(ip string) *payUrl {
	this.ClientIp = ip
	return this
}

func (this *payUrl) saveOrder() {
	order := &ordersmodel.Orders{
		MerchantId: this.app.App.MerchantId,
		//PayChannelId: w.PayParam.Channel.Id,
		AppId:      this.app.App.Id,
		OutTradeNo: this.param.OutTradeNo,
		OrderType:  common.ORDER_TYPE_PAY,
		Amount:     this.param.Amount,
		ExtJson:    this.param.ExtJson,
		Desc:       this.param.Desc,
		Status:     common.ORDER_STATUS_WAITPAY,
	}
	this.result.Order, this.result.PayCode = services.OrderCreate(order)
}

func (this *payUrl) PayUrlV2Service() (result front.PayReqsV2Response, code int) {
	var (
		channelList []paychannelmodel.PayChannel
		order       = new(ordersmodel.Orders)
	)

	// redis 分布式锁 订单号幂等
	key := utils.GetRealKey("payUrl:" + this.param.OutTradeNo)
	lockValue := fmt.Sprintf("%d", time.Now().UnixNano())
	ok, err := utils.AcquireLock(key, lockValue, time.Second*time.Duration(3))
	if ok {
		defer utils.ReleaseLock(key, lockValue)
	} else {
		if err != nil {
			utils.Log(nil, "", "PayUrlV2Service,获取分布式锁失败", fmt.Sprintf("错误原因:%s", err.Error()))
		}
		return
	}

	// 商户是否有配置支付渠道
	channelList, _, code = services.PayChannelList(backend.PayChannelListRequest{MerchantId: this.app.App.MerchantId})
	if code != errorcode.Success {
		return
	}
	if len(channelList) == 0 {
		code = errorcode.PayChannelNotFound
		return
	}
	// 订单是否存在
	conn := builder.NewCond()
	conn = conn.And(builder.Eq{"out_trade_no": this.param.OutTradeNo})
	conn = conn.And(builder.Eq{"app_id": this.param.AppId})
	order, code = services.OrderFindOne(order, conn)
	if code == errorcode.Success {
		// 订单存在
		this.result.Order = order
		this.result.PayCode = services.OrderStatusCheck(*order)
	} else if code == errorcode.OrdersNotFound {
		// 订单不存在
		this.saveOrder()
	} else {
		return
	}
	code = this.result.PayCode
	if code == errorcode.Success {
		this.result.Url = config.GetConf().PayService.Host + common.PayPageAddress + "?no=" + strconv.FormatInt(this.result.Order.Id, 10)
	}

	return this.result, code
}

func PayPageCheckOrder(orderId string) (order ordersmodel.Orders, code int) {
	orderInfo := &ordersmodel.Orders{}
	orderInfo, code = services.OrderFindOne(&ordersmodel.Orders{}, builder.Eq{"id": orderId})
	if code != errorcode.Success {
		return
	}

	code = services.OrderStatusCheck(*orderInfo)
	return *orderInfo, code
}