package biz

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	v1 "voucher/api/v1"
	"voucher/internal/biz/bo"
	"voucher/internal/biz/cmb"
	"voucher/internal/biz/mixrepos"
	"voucher/internal/biz/repo"
	"voucher/internal/biz/vo"
	"voucher/internal/conf"
	"voucher/internal/data"
	"voucher/internal/pkg/lock"

	"github.com/go-kratos/kratos/v2/log"
	"gorm.io/gorm"
)

type MultiBiz struct {
	bc                  *conf.Bootstrap
	rdb                 *data.Rdb
	Cmb                 *cmb.Cmb
	ProductRepo         repo.ProductRepo
	OrderRepo           repo.OrderRepo
	MultiNotifyDataRepo repo.MultiNotifyDataRepo
	MultiNotifyLogRepo  repo.MultiNotifyLogRepo
	CmbMixRepo          mixrepos.CmbMixRepo
}

func NewMultiBiz(
	bc *conf.Bootstrap,
	rdb *data.Rdb,
	cmb *cmb.Cmb,
	productRepo repo.ProductRepo,
	orderRepo repo.OrderRepo,
	multiNotifyDataRepo repo.MultiNotifyDataRepo,
	multiNotifyLogRepo repo.MultiNotifyLogRepo,
	cmbMixRepo mixrepos.CmbMixRepo,
) *MultiBiz {
	return &MultiBiz{
		bc:                  bc,
		rdb:                 rdb,
		Cmb:                 cmb,
		ProductRepo:         productRepo,
		OrderRepo:           orderRepo,
		MultiNotifyDataRepo: multiNotifyDataRepo,
		MultiNotifyLogRepo:  multiNotifyLogRepo,
		CmbMixRepo:          cmbMixRepo,
	}
}

func (biz *MultiBiz) Notify(ctx context.Context, ip, source string, req *bo.WechatVoucherNotifyBo) error {

	// 商品数据量较少，先查询商品是否存在，过滤多余的通知信息
	_, err := biz.ProductRepo.GetByMchStockId(ctx, req.PlainText.StockCreatorMchid, req.PlainText.StockID)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil
		}
		return fmt.Errorf("商品查询错误 error: %w", err)
	}

	cl := vo.MultiNotifyLockKey.BuildCache([]string{
		source,
		req.PlainText.StockID,
		req.PlainText.CouponID,
	})

	return lock.NewMutex(biz.rdb.Rdb, cl.TTL).Lock(ctx, cl.Key, func(ctx context.Context) error {

		if err = req.ValidateMultiNotify(); err != nil {
			return fmt.Errorf("multi validate req error: %v", err)
		}

		order, err := biz.order(ctx, req)
		if err != nil {
			return err
		}

		if order.ActivityId == "" {
			return fmt.Errorf("批次活动ID为空，不是多笔立减金，请检查")
		}

		if err = biz.Run(ctx, ip, source, req, order); err != nil {
			return err
		}

		return nil
	})

}

func (biz *MultiBiz) order(ctx context.Context, req *bo.WechatVoucherNotifyBo) (*bo.OrderBo, error) {

	order, err := biz.OrderRepo.GetByCouponId(ctx, req.PlainText.StockCreatorMchid, req.PlainText.StockID, req.PlainText.CouponID)
	if err != nil {
		return nil, fmt.Errorf("订单查询错误 error: %w", err)
	}

	return order, nil
}

func (biz *MultiBiz) Run(ctx context.Context, ip, source string, req *bo.WechatVoucherNotifyBo, order *bo.OrderBo) error {

	if order.ActivityId == "" {
		return fmt.Errorf("批次活动ID为空，不是多笔立减金，请检查")
	}

	mnd, err := biz.MultiNotifyDataRepo.GetByNotifyID(ctx, source, req.ID)
	if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
		return fmt.Errorf("查询通知数据错误 error: %v", err)
	}

	if mnd != nil {
		if !req.PlainText.IsCombineOrder && mnd.NoticeNum > 0 {
			log.Warnf("[%s] multi notify log already exists,req:%+v", source, req)
			return nil
		}
	} else {
		mnd, err = biz.mndCreate(ctx, ip, source, req, order)
		if err != nil {
			return fmt.Errorf("创建通知数据错误 error: %v", err)
		}
	}

	return biz.run(ctx, req, mnd, order)
}

func (biz *MultiBiz) RetryRunByMultiNotifyDataId(ctx context.Context, multiNotifyDataId int64) error {

	mnd, err := biz.MultiNotifyDataRepo.GetByID(ctx, multiNotifyDataId)
	if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
		return fmt.Errorf("查询通知数据错误 error: %v", err)
	}

	order, err := biz.OrderRepo.GetByOrderNo(ctx, mnd.OrderNo)
	if err != nil {
		return fmt.Errorf("订单查询错误 error: %v", err)
	}

	var req *bo.WechatVoucherNotifyBo
	if err = json.Unmarshal([]byte(mnd.OriginalData), &req); err != nil {
		return fmt.Errorf("通知数据 json unmarshal 错误 error: %v", err)
	}

	return biz.run(ctx, req, mnd, order)
}

func (biz *MultiBiz) run(ctx context.Context, req *bo.WechatVoucherNotifyBo, mnd *bo.MultiNotifyDataBo, order *bo.OrderBo) error {
	if req.PlainText.IsCombineOrder {
		return biz.runCombine(ctx, req, mnd, order)
	}

	return biz.runSingle(ctx, req, mnd, order)
}

func (biz *MultiBiz) runSingle(ctx context.Context, req *bo.WechatVoucherNotifyBo, mnd *bo.MultiNotifyDataBo, order *bo.OrderBo) error {
	// 如果核销金额为空，不再推送下游
	if mnd.ConsumeAmount == 0 {
		log.Warnf("[%s] multi notify log consume amount is 0,req:%+v", mnd.NotifyID, req)
		return nil
	}

	nl, request, err := biz.nlCreate(ctx, req, mnd, order)
	if err != nil {
		return fmt.Errorf("创建通知日志错误 error: %v", err)
	}

	if err = biz.Request(ctx, mnd, nl, request); err != nil {
		return fmt.Errorf("请求错误 error: %v", err)
	}

	return biz.updateOrderStatus(ctx, req, order)
}

func (biz *MultiBiz) runCombine(ctx context.Context, req *bo.WechatVoucherNotifyBo, mnd *bo.MultiNotifyDataBo, order *bo.OrderBo) error {
	for _, subOrder := range req.PlainText.CombineOrderInfo.SubOrders {
		exists, err := biz.MultiNotifyLogRepo.ExistsSuccessByDataIDAndTransactionID(ctx, mnd.ID, subOrder.TransactionID)
		if err != nil {
			return fmt.Errorf("查询合单子单通知记录错误 error: %v", err)
		}
		if exists {
			log.Warnf("[%s] combine sub order already notified,transaction_id:%s", mnd.NotifyID, subOrder.TransactionID)
			continue
		}

		nl, request, err := biz.nlCreateBySubOrder(ctx, req, mnd, order, subOrder)
		if err != nil {
			return fmt.Errorf("创建合单子单通知日志错误 error: %v", err)
		}

		if err = biz.Request(ctx, mnd, nl, request); err != nil {
			return fmt.Errorf("合单子单请求错误 error: %v", err)
		}
	}

	return biz.updateOrderStatus(ctx, req, order)
}

func (biz *MultiBiz) updateOrderStatus(ctx context.Context, req *bo.WechatVoucherNotifyBo, order *bo.OrderBo) error {
	consumeTime := req.PlainText.ConsumeInformation.ConsumeTime

	if req.PlainText.Status.IsUsed() {

		if order.Status.IsUse() {
			if err := biz.OrderRepo.MultiOverUsed(ctx, order.ID, consumeTime, "再次核销完成"); err != nil {
				return fmt.Errorf("订单再次核销完成修改发生错误 error: %v", err)
			}
		} else {
			if err := biz.OrderRepo.MultiOverUsed(ctx, order.ID, consumeTime, "核销完成"); err != nil {
				return fmt.Errorf("订单核销完成修改发生错误 error: %v", err)
			}
		}

	} else {
		if err := biz.OrderRepo.MultiLastUsed(ctx, order.ID, consumeTime); err != nil {
			return fmt.Errorf("订单核销修改发生错误 error: %v", err)
		}
	}

	return nil
}

func (biz *MultiBiz) mndCreate(ctx context.Context, ip, source string, req *bo.WechatVoucherNotifyBo, order *bo.OrderBo) (*bo.MultiNotifyDataBo, error) {

	originalData, err := req.Str()
	if err != nil {
		return nil, fmt.Errorf("通知数据 json str 错误 error: %v", err)
	}

	consumeAmount := int32(req.PlainText.ConsumeInformation.ConsumeAmount)
	consumeTime := &req.PlainText.ConsumeInformation.ConsumeTime
	transactionID := req.PlainText.ConsumeInformation.TransactionID
	if req.PlainText.IsCombineOrder {
		consumeAmount = int32(req.PlainText.CombineOrderInfo.CombineConsumeAmount)
	}

	return biz.MultiNotifyDataRepo.Create(ctx, &bo.MultiNotifyDataBo{
		Source:        source,
		IP:            ip,
		NotifyID:      req.ID,
		OrderNo:       order.OrderNo,
		OutBizNo:      order.OutBizNo,
		CouponID:      req.PlainText.CouponID,
		StockID:       req.PlainText.StockID,
		ConsumeAmount: consumeAmount,
		ConsumeTime:   consumeTime,
		TransactionID: transactionID,
		EventType:     req.EventType,
		Status:        req.PlainText.Status,
		OriginalData:  originalData,
	})
}

func (biz *MultiBiz) nlCreateBySubOrder(ctx context.Context, req *bo.WechatVoucherNotifyBo, mnd *bo.MultiNotifyDataBo, order *bo.OrderBo, subOrder bo.CombineSubOrder) (*bo.MultiNotifyLogBo, *v1.CmbRequest, error) {
	if biz.bc.Cmb.MultiNotifyUrl == "" {
		return nil, nil, fmt.Errorf("CMB多笔立减金通知地址为空")
	}

	consumeTime := subOrder.ConsumeTime
	nl := &bo.MultiNotifyLogBo{
		MultiNotifyDataID: mnd.ID,
		OrderNo:           mnd.OrderNo,
		OutBizNo:          mnd.OutBizNo,
		CouponID:          mnd.CouponID,
		ActivityNo:        order.ProductNo,
		StockID:           mnd.StockID,
		EventType:         mnd.EventType,
		Status:            req.PlainText.Status,
		ConsumeAmount:     int32(subOrder.ConsumeAmount),
		ConsumeTime:       &consumeTime,
		TransactionID:     subOrder.TransactionID,
		RequestURL:        biz.bc.Cmb.MultiNotifyUrl,
		RequestStatus:     vo.MultiNotifyLogStatusWait.GetValue(),
		OrderCreateTime:   order.CreateTime,
		CouponCreateTime:  &req.PlainText.CreateTime,
	}

	request, cmbRequestBo, err := biz.GetRequest(ctx, nl, order)
	if err != nil {
		return nil, nil, err
	}

	b, _ := json.Marshal(request)
	nl.OriginReq = cmbRequestBo.BizContent
	nl.Request = string(b)

	res, err := biz.MultiNotifyLogRepo.Create(ctx, nl)
	if err != nil {
		return nil, nil, fmt.Errorf("创建通知日志错误 error: %v", err)
	}
	res.ConsumeTime = nl.ConsumeTime

	return res, request, nil
}

func (biz *MultiBiz) nlCreate(ctx context.Context, req *bo.WechatVoucherNotifyBo, mnd *bo.MultiNotifyDataBo, order *bo.OrderBo) (*bo.MultiNotifyLogBo, *v1.CmbRequest, error) {

	if biz.bc.Cmb.MultiNotifyUrl == "" {
		return nil, nil, fmt.Errorf("CMB多笔立减金通知地址为空")
	}

	nl := &bo.MultiNotifyLogBo{
		MultiNotifyDataID: mnd.ID,
		OrderNo:           mnd.OrderNo,
		OutBizNo:          mnd.OutBizNo,
		CouponID:          mnd.CouponID,
		ActivityNo:        order.ProductNo,
		StockID:           mnd.StockID,
		EventType:         mnd.EventType,
		Status:            req.PlainText.Status,
		ConsumeAmount:     mnd.ConsumeAmount,
		ConsumeTime:       mnd.ConsumeTime,
		TransactionID:     req.PlainText.ConsumeInformation.TransactionID,
		//RequestURL:        order.NotifyUrl,
		RequestURL:       biz.bc.Cmb.MultiNotifyUrl,
		RequestStatus:    vo.MultiNotifyLogStatusWait.GetValue(),
		OrderCreateTime:  order.CreateTime,
		CouponCreateTime: &req.PlainText.CreateTime,
	}

	request, cmbRequestBo, err := biz.GetRequest(ctx, nl, order)
	if err != nil {
		return nil, nil, err
	}

	b, _ := json.Marshal(request)
	nl.OriginReq = cmbRequestBo.BizContent
	nl.Request = string(b)

	res, err := biz.MultiNotifyLogRepo.Create(ctx, nl)
	if err != nil {
		return nil, nil, fmt.Errorf("创建通知日志错误 error: %v", err)
	}
	// 创建出来的核销时间精度有差距，重新赋值返回出去
	res.ConsumeTime = mnd.ConsumeTime

	return res, request, nil
}

func (biz *MultiBiz) bizContent(nl *bo.MultiNotifyLogBo, order *bo.OrderBo) (string, error) {

	req := &v1.CmbMultiNotifyRequest{
		TransactionId: nl.OutBizNo,                                                // cmb业务号
		ActivityId:    nl.ActivityNo,                                              // 批次活动号
		CouponId:      nl.CouponID,                                                // 微信券券号
		AcquiredDate:  order.ReceiveSuccessTime.Format("2006-01-02 15:04:05.000"), // 券领取时间
		Status:        "1",                                                        // 券状态 0：可使用，1：已使用
		CouponStatus:  "0",                                                        // 0：可使用，1：已使用
		TransDate:     nl.ConsumeTime.Format("2006-01-02 15:04:05.000"),           // 核销时间，验券时间，格式yyyy-mm-dd hh:mm:ss.sss
		TransAmount:   fmt.Sprintf("%d", nl.ConsumeAmount),
		OrderId:       nl.TransactionID,    // 券核销支付单号
		Ticket:        nl.OrderNo,          // 券订单号，lsxd订单号
		OrgNo:         "LANSEXIONGDIMULTI", // cmb固定值
		Attach:        order.Attach,        // cmb拓展参数
		Ext:           "",
	}

	if nl.Status.IsUsed() {
		req.CouponStatus = "1"
	}

	bizJsonBytes, err := json.Marshal(req)
	if err != nil {
		return "", fmt.Errorf("json.Marshal CmbNotifyRequest error: %v", err)
	}

	return string(bizJsonBytes), nil
}

func (biz *MultiBiz) GetRequest(ctx context.Context, nl *bo.MultiNotifyLogBo, order *bo.OrderBo) (*v1.CmbRequest, *bo.CmbRequestBo, error) {

	bizContent, err := biz.bizContent(nl, order)
	if err != nil {
		return nil, nil, err
	}

	r := &bo.CmbRequestBo{
		FuncName:   vo.CmbNotifyFuncNameUpdateCodeStatusForMulti,
		BizContent: bizContent,
	}
	request, err := biz.CmbMixRepo.GetRequest(ctx, r)
	if err != nil {
		return nil, nil, err
	}

	return request, r, nil
}

func (biz *MultiBiz) Request(ctx context.Context, mmd *bo.MultiNotifyDataBo, nl *bo.MultiNotifyLogBo, request *v1.CmbRequest) error {

	if nl.RequestURL == "" {
		if err := biz.notifyFail(ctx, nl, "回调通知招行地址为空,不做通知"); err != nil {
			return err
		}
		// 回调通知地址为空，不返回错误，不做再次通知处理
		return nil
	}

	reply, err := biz.CmbMixRepo.Request(ctx, request, nl.RequestURL)
	if err != nil {
		if err3 := biz.notifyFail(ctx, nl, err.Error()); err3 != nil {
			return err3
		}
		return err
	}

	if err = biz.CmbMixRepo.VerifyResponse(ctx, reply); err != nil {

		errMsg := fmt.Sprintf("回调通知招行返回验证结果发生错误,resp:%+v error:%s", reply, err.Error())

		if err3 := biz.notifyFail(ctx, nl, errMsg); err3 != nil {
			return err3
		}
		return err
	}

	return biz.notifySuccess(ctx, mmd, nl, reply)
}

func (biz *MultiBiz) notifyFail(ctx context.Context, nl *bo.MultiNotifyLogBo, remark string) error {

	if err := biz.MultiNotifyLogRepo.Fail(ctx, nl.ID, remark); err != nil {
		return fmt.Errorf("更新通知日志失败状态发生错误 error: %v", err)
	}

	return nil
}

func (biz *MultiBiz) notifySuccess(ctx context.Context, mmd *bo.MultiNotifyDataBo, nl *bo.MultiNotifyLogBo, reply *v1.CmbReply) error {

	response, err := json.Marshal(reply)
	if err != nil {
		return fmt.Errorf("json.Marshal CmbReply error: %v", err)
	}

	if err = biz.MultiNotifyLogRepo.Success(ctx, nl.ID, string(response)); err != nil {
		return fmt.Errorf("更新通知日志成功状态发生错误 error: %v", err)
	}

	if err = biz.MultiNotifyDataRepo.AddNoticeNum(ctx, mmd.ID); err != nil {
		return fmt.Errorf("更新通知数据通知次数发生错误 error: %v", err)
	}

	return nil
}
