package jobs

import (
	"context"
	"errors"
	"gadmin/config"
	"gadmin/internal/admin/consts"
	"gadmin/internal/gorm/model"
	"gadmin/internal/gorm/query"
	"gadmin/utility/player"
	"github.com/jinzhu/now"
	"github.com/sirupsen/logrus"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"
	"os"
	"sync"
	"time"
)

var SyncOrders = new(jSyncOrders)

const DelaySecond = 7200 // 延迟拉取2小時内的订单，防止订单未支付而过后又支付完成的情况 （ios）
const DelayId = 50       // 延迟拉取前N个订单，防止订单未支付而过后又支付完成的情况 （安卓）

type jSyncOrders struct {
	sync.Mutex
}

// Run
// 存在问题：
// 1. 通过尾数U...关联时，如果存在多个相同的关联，无法准备识别应该关联哪个
// 2. 有个别是U无法准确关联 绝大部分是U+1
// 3. 过早的订单存在直接无法关联问题
func (j *jSyncOrders) Run() {
	logrus.Info("SyncOrders Run.....")

	if os.Getenv("GIN_MODE") != "release" && os.Getenv("ADMIN_IS_LOCAL") != "1" {
		logrus.Warnf("测试环境禁止同步")
		return
	}

	j.Lock()
	defer j.Unlock()
	//游戏服订单相关表
	//virtualpayorder表示游戏内订单 InGameOrders
	//wxpay_iosorder表示游戏外订单 OutOfGameOrders
	//wxpay_order存储订单状态
	for serverId, _ := range config.GDBGroup {
		j.syncInGameOrders(serverId)
		j.syncOutOfGameOrders(serverId)
	}
	//for serverId, _ := range config.GDBGroup {
	//	j.syncIos(serverId)
	//	j.syncAndroid(serverId)
	//}
}

func (j *jSyncOrders) syncInGameOrders(serverId int) {
	logrus.Infof("Load Sync In-Game-Orders; ServerId %d .....", serverId)

	//获取游戏服id
	DB, err := player.GetDBByServerID(serverId)
	if err != nil {
		logrus.Warningf("syncAndroid GetDBByServerID，err：%v", err)
		return
	}

	//PayOrderAndriod 原名virtualpayorder 表示游戏内订单
	//PayOrderIos 原名wxpay_iosorder 表示游戏外订单
	var (
		ctx       = context.TODO()
		o         = query.Use(config.GDBGroup[DB]).PayOrderAndriod
		m         = o.WithContext(ctx)
		orderList []model.PayOrderAndriod
		lastOrder model.Order
	)

	//获取游戏内最后一笔订单时间/id
	lastOrderSyncInfo, err := j.getLastOrderIdByOrderWay(serverId, consts.InGameOrders)
	if err != nil {
		logrus.Warnf("getLastOrderTime err . %v", err)
		return
	}

	//获取近两个小时内的订单
	if lastOrderSyncInfo.ID != 0 {
		m = m.Where(o.CreateTime.Gte(lastOrderSyncInfo.LastCreateTime.Add(time.Hour * -2)))
	}

	if err = m.Scan(&orderList); err != nil {
		logrus.Warnf("orderList Scan err . %v", err)
		return
	}

	if len(orderList) == 0 {
		logrus.Warnf("没有需要同步的订单")
		return
	}

	uIds := make([]int64, 0)
	uIdsMap := make(map[int64]struct{})
	for _, order := range orderList {
		if _, ok := uIdsMap[order.Playerid]; ok {
			continue
		}
		uIdsMap[order.Playerid] = struct{}{}
		uIds = append(uIds, order.Playerid)
	}

	firstOrders, err := o.WithContext(ctx).Select(o.Playerid).Group(o.Playerid).Where(o.Playerid.In(uIds...)).Having(o.Playerid.Count().Eq(1)).Find()
	if err != nil {
		return
	}

	newOrder := make(map[int64]struct{})
	for _, order := range firstOrders {
		newOrder[order.ID] = struct{}{}
	}

	for _, order := range orderList {
		var (
			isNew     int32 = 0
			status    int32 = consts.OrderStatusNo
			orderSn         = ""
			goodsID         = int32(order.GoodsID)
			orderDate       = ""
			payAt     time.Time
			createdAt time.Time
		)

		if _, ok := newOrder[order.ID]; ok {
			isNew = 1
		}

		status = consts.OrderStateSuccess

		payAt = order.CreateTime
		orderDate = payAt.Format("2006-01-02")
		createdAt = payAt

		if payAt.IsZero() {
			payAt = time.Now().In(time.Local)
		}

		channelID := player.GetUserChannel(order.Playerid)
		userCreatedAt := player.GetUserStamp(order.Playerid)
		paymentType := consts.OrderPaymentType
		if order.Platform == 1 {
			paymentType = consts.OrderPaymentTypeWx
		} else if order.Platform == 2 {
			paymentType = consts.OrderPaymentTypeDy
		}
		platform := consts.OrderPlatform
		// 0：苹果游戏外微信支付 1:安卓游戏内支付 2：安卓游戏外微信支付 3:苹果游戏内支付
		if order.PayMethod == 3 {
			platform = consts.OrderPlatformIos
		} else if order.PayMethod == 1 || order.PayMethod == 2 {
			platform = consts.OrderPlatformAndroid
		}

		lastOrder = model.Order{
			ServerID:      int32(serverId),
			Platform:      int32(platform),
			PaymentType:   int32(paymentType),
			RelatID:       int32(order.ID),
			PlayerID:      order.Playerid,
			GoodsID:       goodsID,
			OrderSn:       orderSn,
			OutTradeNo:    order.BillNo,
			Money:         float64(order.Balance-order.PayedBalance) / 100,
			IsNew:         isNew,
			Status:        status,
			Date:          orderDate,
			PayAt:         payAt,
			CreatedAt:     createdAt,
			ChannelID:     channelID,
			UserCreatedAt: userCreatedAt,
			Flag:          player.GetUserFlag(serverId, order.Playerid),
		}

		if err = j.saveOrder(lastOrder); err != nil {
			logrus.Warnf("Android saveOrder Scan err: %v; order:%+v; lastOrder:%+v;\n", err, order, lastOrder)
			return
		}
	}

	if err = j.setLastOrderTime(serverId, lastOrder, consts.InGameOrders, lastOrderSyncInfo.ID); err != nil {
		logrus.Warnf("setLastOrderTime Scan err . %v", err)
		return
	}
}

func (j *jSyncOrders) syncOutOfGameOrders(serverId int) {
	logrus.Infof("Load Sync Out-Of-Game-Orders; ServerId: %d .....", serverId)
	lastOrderSyncInfo, err := j.getLastOrderIdByOrderWay(serverId, consts.OutOfGameOrders)
	if err != nil {
		logrus.Warnf("getLastOrderTime err . %v", err)
		return
	}

	DB, err := player.GetDBByServerID(serverId)
	if err != nil {
		logrus.Warningf("syncIos GetDBByServerID，err：%v", err)
		return
	}

	var (
		ctx       = context.TODO()
		o         = query.Use(config.GDBGroup[DB]).PayOrderIos
		m         = o.WithContext(ctx)
		orderList []model.PayOrderIos
		lastOrder model.Order
	)

	//获取近两个小时内的订单
	if lastOrderSyncInfo.ID != 0 {
		m = m.Where(o.CreateTime.Gte(lastOrderSyncInfo.LastCreateTime.Add(time.Hour * -2)))
	}

	if err = m.Scan(&orderList); err != nil {
		logrus.Warnf("orderList Scan err . %v", err)
		return
	}

	if len(orderList) == 0 {
		logrus.Warnf("没有需要同步的订单")
		return
	}

	uIds := make([]int64, 0)
	uIdsMap := make(map[int64]struct{})
	for _, order := range orderList {
		if _, ok := uIdsMap[order.PlayerID]; ok {
			continue
		}
		uIdsMap[order.PlayerID] = struct{}{}
		uIds = append(uIds, order.PlayerID)
	}

	//查找第一次下单的用户
	newOrders := make(map[int64]struct{})
	firstOrders, err := o.WithContext(ctx).Select(o.PlayerID).Group(o.PlayerID).Where(o.PlayerID.In(uIds...)).Having(o.PlayerID.Count().Eq(1)).Find()
	if err != nil {
		return
	}

	for _, order := range firstOrders {
		newOrders[order.PlayerID] = struct{}{}
	}

	for _, order := range orderList {
		var (
			isNew int32 = 0
		)

		if _, ok := newOrders[order.PlayerID]; ok {
			isNew = 1
		}

		PayAt := time.Unix(0, 0).In(time.Local)
		if order.SuccessTime != "" {
			PayAt, _ = now.ParseInLocation(time.Local, order.SuccessTime)
		}
		if order.TradeState != "SUCESS" {
			continue
		}

		channelID := player.GetUserChannel(order.PlayerID)
		userCreatedAt := player.GetUserStamp(order.PlayerID)

		paymentType := consts.OrderPaymentType
		if order.Platform == 1 {
			paymentType = consts.OrderPaymentTypeWx
		} else if order.Platform == 2 {
			paymentType = consts.OrderPaymentTypeDy
		}
		platform := consts.OrderPlatform
		//order.Phone 1苹果 2安卓
		if order.Phone == 1 {
			platform = consts.OrderPlatformIos
		} else if order.Phone == 2 {
			platform = consts.OrderPlatformAndroid
		}
		lastOrder = model.Order{
			ServerID:      int32(serverId),
			Platform:      int32(platform),
			RelatID:       int32(order.ID),
			PaymentType:   int32(paymentType),
			PlayerID:      order.PlayerID,
			GoodsID:       order.GoodsID,
			OrderSn:       order.OutTradeNo,
			OutTradeNo:    order.TransactionID,
			Money:         float64(order.Total) / 100,
			IsNew:         isNew,
			Status:        consts.OrderStatusSuccess,
			Date:          order.CreateTime.Format("2006-01-02"),
			PayAt:         PayAt,
			CreatedAt:     order.CreateTime,
			ChannelID:     channelID,
			UserCreatedAt: userCreatedAt,
			Flag:          player.GetUserFlag(serverId, order.PlayerID),
		}

		if err = j.saveOrder(lastOrder); err != nil {
			logrus.Warnf("IOS saveOrder Scan err: %v; order:%+v; lastOrder:%+v;\n", err, order, lastOrder)
			return
		}
	}

	if err = j.setLastOrderTime(serverId, lastOrder, consts.OutOfGameOrders, lastOrderSyncInfo.ID); err != nil {
		logrus.Warnf("setLastOrderTime Scan err . %v", err)
		return
	}
}

//func (j *jSyncOrders) syncAndroid(serverId int) {
//	logrus.Info("load SyncAndroid .....")
//	lastId, err := j.getLastOrderId(serverId, consts.OrderPlatformAndroid)
//	if err != nil {
//		logrus.Warnf("getLastOrderTime err . %v", err)
//		return
//	}
//
//	DB, err := player.GetDBByServerID(serverId)
//	if err != nil {
//		logrus.Warningf("syncAndroid GetDBByServerID，err：%v", err)
//		return
//	}
//
//	var (
//		o         = query.Use(config.GDBGroup[DB]).PayOrderAndriod
//		m         = o.WithContext(context.TODO())
//		orderLst  []model.PayOrderAndriod
//		lastOrder model.Order
//	)
//
//	if lastId > 0 {
//		m = m.Where(o.ID.Gte(lastId))
//	}
//
//	if err = m.Scan(&orderLst); err != nil {
//		logrus.Warnf("orderLst Scan err . %v", err)
//		return
//	}
//
//	if len(orderLst) == 0 {
//		logrus.Warnf("没有需要同步的订单")
//		return
//	}
//
//	for _, order := range orderLst {
//		var (
//			isNew     int32 = 0
//			status    int32 = consts.OrderStatusNo
//			dao             = query.Use(config.GDBGroup[DB]).PayOrderAndriod
//			orderSn         = ""
//			goodsID         = int32(order.GoodsID)
//			orderDate       = ""
//			payAt     time.Time
//			createdAt time.Time
//		)
//
//		first, err := dao.Where(dao.Playerid.Eq(order.Playerid)).First()
//		if err == nil && first != nil {
//			if first.ID == order.ID {
//				isNew = 1
//			}
//		}
//
//		status = consts.OrderStateSuccess
//
//		payAt = order.CreateTime
//		orderDate = payAt.Format("2006-01-02")
//		createdAt = payAt
//
//		if payAt.IsZero() {
//			payAt = time.Now().In(time.Local)
//		}
//
//		channelID := player.GetUserChannel(order.Playerid)
//		userCreatedAt := player.GetUserStamp(order.Playerid)
//
//		lastOrder = model.Order{
//			ServerID:      int32(serverId),
//			Platform:      consts.OrderPlatformAndroid,
//			RelatID:       int32(order.ID),
//			PaymentType:   consts.OrderPaymentTypeWx,
//			PlayerID:      order.Playerid,
//			GoodsID:       goodsID,
//			OrderSn:       orderSn,
//			OutTradeNo:    order.BillNo,
//			Money:         float64(order.Balance-order.PayedBalance) / 100,
//			IsNew:         isNew,
//			Status:        status,
//			Date:          orderDate,
//			PayAt:         payAt,
//			CreatedAt:     createdAt,
//			ChannelID:     channelID,
//			UserCreatedAt: userCreatedAt,
//			Flag:          player.GetUserFlag(serverId, order.Playerid),
//		}
//
//		if err = j.saveOrder(lastOrder); err != nil {
//			logrus.Warnf("Android saveOrder Scan err: %v; order:%+v; lastOrder:%+v;\n", err, order, lastOrder)
//			return
//		}
//	}
//
//	if err = j.setLastOrderTime(serverId, consts.OrderPlatformAndroid, lastOrder); err != nil {
//		logrus.Warnf("setLastOrderTime Scan err . %v", err)
//		return
//	}
//}
//
//func (j *jSyncOrders) syncIos(serverId int) {
//	logrus.Info("load SyncIos .....")
//	lastTime, err := j.getLastOrderTime(serverId, consts.OrderPlatformIos)
//	if err != nil {
//		logrus.Warnf("getLastOrderTime err . %v", err)
//		return
//	}
//
//	DB, err := player.GetDBByServerID(serverId)
//	if err != nil {
//		logrus.Warningf("syncIos GetDBByServerID，err：%v", err)
//		return
//	}
//
//	var (
//		o         = query.Use(config.GDBGroup[DB]).PayOrderIos
//		m         = o.WithContext(context.TODO())
//		orderLst  []model.PayOrderIos
//		lastOrder model.Order
//	)
//
//	if lastTime.Unix() > 0 {
//		m = m.Where(o.CreateTime.Gte(lastTime.Add(-time.Second * DelaySecond)))
//	}
//
//	if err = m.Scan(&orderLst); err != nil {
//		logrus.Warnf("orderLst Scan err . %v", err)
//		return
//	}
//
//	if len(orderLst) == 0 {
//		logrus.Warnf("没有需要同步的订单")
//		return
//	}
//
//	for _, order := range orderLst {
//		var (
//			isNew  int32 = 0
//			status int32 = consts.OrderStatusNo
//			dao          = query.Use(config.GDBGroup[DB]).PayOrderIos
//		)
//
//		first, err := query.Use(config.GDBGroup[DB]).PayOrderIos.Select(dao.ALL).Where(dao.TradeState.Eq("SUCESS"), dao.PlayerID.Eq(order.PlayerID)).First()
//		if err == nil && first != nil {
//			if first.ID == order.ID {
//				isNew = 1
//			}
//		}
//
//		payAt, err := now.ParseInLocation(time.Local, order.SuccessTime)
//		if err != nil {
//			payAt = time.Now().In(time.Local)
//			logrus.Errorf("order successtime parse err: %v; payAt: %v;\n", err, payAt)
//		}
//
//		if order.TradeState == "SUCESS" {
//			status = consts.OrderStatusSuccess
//		} else if order.TradeState == "CLOSED" {
//			status = consts.OrderStatusCancel
//		} else if order.TradeState == "NOTENOUGH" {
//			status = consts.OrderStatusNotEnough
//		} else {
//			status = consts.OrderStatusOther
//		}
//
//		channelID := player.GetUserChannel(order.PlayerID)
//		userCreatedAt := player.GetUserStamp(order.PlayerID)
//
//		lastOrder = model.Order{
//			ServerID:      int32(serverId),
//			Platform:      consts.OrderPlatformIos,
//			RelatID:       int32(order.ID),
//			PaymentType:   consts.OrderPaymentTypeWx,
//			PlayerID:      order.PlayerID,
//			GoodsID:       order.GoodsID,
//			OrderSn:       order.OutTradeNo,
//			OutTradeNo:    order.TransactionID,
//			Money:         float64(order.Total) / 100,
//			IsNew:         isNew,
//			Status:        status,
//			Date:          order.CreateTime.Format("2006-01-02"),
//			PayAt:         payAt,
//			CreatedAt:     order.CreateTime,
//			ChannelID:     channelID,
//			UserCreatedAt: userCreatedAt,
//			Flag:          player.GetUserFlag(serverId, order.PlayerID),
//		}
//
//		if err = j.saveOrder(lastOrder); err != nil {
//			logrus.Warnf("IOS saveOrder Scan err: %v; order:%+v; lastOrder:%+v;\n", err, order, lastOrder)
//			return
//		}
//	}
//
//	if err = j.setLastOrderTime(serverId, consts.OrderPlatformIos, lastOrder); err != nil {
//		logrus.Warnf("setLastOrderTime Scan err . %v", err)
//		return
//	}
//}

// getLastOrderTime 获取下次同步订单的截止时间，主要用于ios
func (j *jSyncOrders) getLastOrderTime(serverId int, platform int32) (last time.Time, err error) {
	var (
		q      = query.Use(config.DB).OrdersSync
		models model.OrdersSync
	)

	if err = q.Where(q.Platform.Eq(platform), q.ServerID.Eq(int32(serverId))).Scan(&models); err != nil {
		return
	}

	return models.LastCreateTime, nil
}

// getLastOrderTime 获取下次同步订单的截止ID，主要用于安卓
func (j *jSyncOrders) getLastOrderId(serverId int, platform int32) (last int64, err error) {
	var (
		q      = query.Use(config.DB).OrdersSync
		models model.OrdersSync
	)

	if err = q.Where(q.Platform.Eq(platform), q.ServerID.Eq(int32(serverId))).Scan(&models); err != nil {
		return
	}
	if models.LastID == 0 {
		return 0, nil
	}
	return models.LastID - DelayId, nil
}

func (j *jSyncOrders) getLastOrderIdByOrderWay(serverId int, orderWay int) (lastOrder *model.OrdersSync, err error) {
	var (
		q = query.Use(config.DB).OrdersSync
	)
	res, err := q.Where(q.ServerID.Eq(int32(serverId)), q.OrderWay.Eq(int32(orderWay))).First()
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return &model.OrdersSync{}, nil
		}
		return
	}
	return res, nil
}

func (j *jSyncOrders) setLastOrderTime(serverId int, data model.Order, orderWay int, lastSyncInfoId int64) (err error) {
	var (
		q = query.Use(config.DB).OrdersSync
	)
	syncData := &model.OrdersSync{
		LastID:         int64(data.RelatID),
		LastCreateTime: data.CreatedAt,
		UpdatedAt:      time.Now(),
	}
	if lastSyncInfoId != 0 {
		_, err := q.Where(q.ID.Eq(lastSyncInfoId)).Updates(syncData)
		if err != nil {
			return err
		}
	} else {
		syncData.ServerID = int32(serverId)
		syncData.OrderWay = int32(orderWay)
		err := q.Create(syncData)
		if err != nil {
			return err
		}

	}

	return
}

func (j *jSyncOrders) saveOrder(data model.Order) (err error) {
	//var (
	//	q      = query.Use(config.DB).Order
	//	models model.Order
	//)
	//
	//if err = q.Where(q.ServerID.Eq(data.ServerID), q.RelatID.Eq(data.RelatID), q.Platform.Eq(data.Platform)).Scan(&models); err != nil {
	//	return err
	//}
	//
	//if models.ID > 0 {
	//	if _, err = query.Use(config.DB).Order.Where(q.RelatID.Eq(data.RelatID), q.Platform.Eq(data.Platform)).Updates(&data); err != nil {
	//		return err
	//	}
	//	return nil
	//} else {
	//	return query.Use(config.DB).Order.Create(&data)
	//}

	return query.Use(config.DB).Order.Clauses(clause.OnConflict{UpdateAll: true}).Create(&data)
}
