package service

import (
	"encoding/json"
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/gogf/gf/util/gconv"
	"github.com/guonaihong/gout"
	"github.com/syyongx/php2go"
	"go_sku_server/model"
	"go_sku_server/model/saveModel"
	"go_sku_server/pkg/common"
	"go_sku_server/pkg/config"
	"go_sku_server/pkg/e"
	"go_sku_server/pkg/logger"
	"go_sku_server/pkg/mysql"
	"go_sku_server/service/sorter"
	"reflect"
	"sort"
	"time"
)

type LySaveService struct {
}

/**
sku基本数据增加和修改
@author wangsong
@param
	GoodsName  			string `json:"goods_name" binding:"required"`
	BrandName 		 	string `json:"brand_name" binding:"required"`
	SupplierId  		int `json:"supplier_id" binding:"required"`
	Moq  				int `json:"moq" binding:"required"`//起订量
	Mpq  				int `json:"mpq" binding:"required"`//标准包装量
	Canal  				int `json:"canal" `//渠道标签
	BatchSn  			int `json:"batch_sn" `//批次
	Stock  				int `json:"stock" `//库存
	HkDeliveryTime  	string `json:"hk_delivery_time" `//香港货期
	CnDeliveryTime  	string `json:"cn_delivery_time" `//大陆货期
	LadderPrice  		string `json:"ladder_price" `//阶梯价钱
	CpTime  			int `json:"cp_time" `//茂则成本添加时间
	GoodsImages  		int `json:"goods_images" `//商品图片
	GoodsSn  			int `json:"goods_sn" `
	Encap  				int `json:"encap" `
	Pdf  				int `json:"pdf" `
	SpuBrief  			int `json:"spu_brief" `
	Attrs  				int `json:"attrs" `
*/
var skuCache SkuCacheService

func (S *LySaveService) SaveSku(lySaveRequest saveModel.LySaveRequest, ctx *gin.Context) (error, string) {

	lySkuEntity := lySaveRequest.ToLySkuEntity() //sku实体
	//供应商处理(处理后,将[新增/修改sku]所需供应商相关字段  赋值给 &lySkuEntity)
	err := S.supplierHandle(lySaveRequest.SupplierId, lySkuEntity.Canal, &lySkuEntity)
	if err != nil {
		return e.NewApiError("供应商处理失败" + err.Error()), ""
	}
	//阶梯价格处理
	err = S.ladderPriceHandle(lySaveRequest.LadderPrice, &lySkuEntity)
	if err != nil {
		return e.NewApiError("阶梯价格处理失败" + err.Error()), ""
	}
	//上下架状态处理
	S.statusHandle(&lySkuEntity)

	//spu处理(请求spu_server saveSpu接口,将spuId赋值给 &lySkuEntity)
	lySpuRequest := lySaveRequest.ToLySpuRequest()
	err = S.spuHandle(lySpuRequest, &lySkuEntity)
	if err != nil {
		return err, ""
	}

	//mongoDb 获取sku 唯一信息
	err, mongoSkuInfo := skuCache.MongoGetOnlySkuInfo(lySkuEntity.SpuId, lySkuEntity.SupplierId, lySkuEntity.Moq, lySkuEntity.Encoded, lySkuEntity.Canal)
	if err != nil {
		return e.NewApiError("mongo 获取sku唯一信息失败" + err.Error()), ""
	}
	if mongoSkuInfo.GoodsId != 0 { //更新

		lySkuEntity.GoodsId = gconv.String(mongoSkuInfo.GoodsId)
		err := S.update(lySkuEntity, lySaveRequest)
		if err != nil {
			return e.NewApiError("skuSave 修改sku失败" + err.Error()), ""
		}

		return nil, lySkuEntity.GoodsId
	} else { //插入sku
		err, skuId := S.insert(lySkuEntity, lySaveRequest)
		if err != nil {
			return e.NewApiError("skuSave insert sku失败" + err.Error()), ""
		}
		logger.Select("lysku_save").Info("新增完成:brand_name:" + lySaveRequest.BrandName + ";sku_id:" + skuId + ";good_name:" + lySaveRequest.GoodsName)
		return nil, skuId
	}

}

/**
   供应商处理流程(内部方法)
	1.判断供应商是否存在,是否被禁用
	2.如果供应商为专卖,参数 cannal(供应商编码)是必传的
    3.得到 新增sku所需的 TypeId 和 Encoded(渠道员内部编码)
*/
func (S *LySaveService) supplierHandle(SupplierId int, Canal string, lySkuEntity *saveModel.LySkuEntity) error {

	lySupplier := LySupplier{}

	has, err, supplierInfo := lySupplier.GetSupplirInfo(SupplierId)
	if err != nil {
		return e.NewApiError("获取基石供应商详情失败" + err.Error())
	}
	if !has {
		fmt.Sprintf("没有在基石获取到此供应商,供应商ID为:%s", SupplierId)
	}

	if supplierInfo.Status != 1 {
		return e.NewApiError("供应商被禁用")
	}

	//专卖处理(供应商系统)
	if supplierInfo.TypeId == 2 {
		if Canal == "" {
			return e.NewApiError("专卖 cannal参数为必传")
		}
		has, err, poolSupplierInfo := lySupplier.GetPoolSupplirInfo(Canal)

		if err != nil {
			return e.NewApiError("获取联营供应商详情失败" + err.Error())
		}
		if !has {

			errmsg := fmt.Sprintf("没有获取到渠道%s的内部编码", Canal)
			return e.NewApiError(errmsg)
		}
		lySkuEntity.Encoded = poolSupplierInfo.ChannelUid //渠道开发员 内部编码赋值给sku
	}
	lySkuEntity.GoodsType = supplierInfo.TypeId

	return nil
}

/**
阶梯价格处理
	1.对阶梯价格排序
	2.获取 ladderPrice
	3.获取最便宜的价格
*/
func (S *LySaveService) ladderPriceHandle(ladderPrice []model.LadderPrice, lySkuEntity *saveModel.LySkuEntity) error {

	num := len(ladderPrice)

	if len(ladderPrice) > 0 { //有传阶梯价

		//获取最便宜的价格
		sort.Sort(sorter.LadderPriceSorter(ladderPrice)) //按照购买数量,小到大排序,数量越大优惠越多,所以[num-1]是最便宜的

		if ladderPrice[num-1].PriceUs >= 0 { //如果有最低美元价,就直接读最低美元价
			lySkuEntity.SinglePrice = ladderPrice[num-1].PriceUs //获取最便宜的价钱
		} else { //没有美元价,就将最低人民币折算成美元
			lySkuEntity.SinglePrice = php2go.Round(ladderPrice[num-1].PriceCn / 6.8)
		}
		//将阶梯json字符串给到sku实体里,sku只需要字符串阶梯 存数据库
		priceBytes, err := json.Marshal(ladderPrice)
		if err != nil {
			return e.NewApiError("阶梯价格json化报错" + err.Error())
		} else {
			lySkuEntity.LadderPrice = string(priceBytes)
		} //状态处理
	}
	return nil
}

/**
spu处理(内部方法)
	1.调用spu_server saveSpu接口,获取到spuId
返回参数
| 参数        | 示例值   |  参数描述  |
| :--------   | :-----  | :----  |
| errorcode     | 10001 | 错误码 0代表成功响应 |
| errmsg     |  | 错误提示 |
| data        |  | 返回的data |

*/
func (S *LySaveService) spuHandle(lySpuRequest saveModel.LySpuRequest, lySkuEntity *saveModel.LySkuEntity) error {

	lySpuResponse := saveModel.LySpuResponse{}

	err := gout.POST(config.Get("spu_server.api_domain").String() + "/saveSpu") /*.Debug(true)*/ .SetJSON(&lySpuRequest).BindJSON(&lySpuResponse).Do()
	//err:=gout.POST("http://192.168.1.237:8005/saveSpu").Debug(false).SetJSON(&lySpuRequest).BindJSON(&lySpuResponse).Do()

	if err != nil {
		return e.NewApiError("调用spu server saveSpu接口报错" + err.Error())
	}
	if lySpuResponse.ErrorCode == 0 {
		lySkuEntity.SpuId = lySpuResponse.Data.SpuId
	} else {
		return e.NewApiError("sku Throw out" + lySpuResponse.ErrMsg)
	}
	return nil
}

/**
上下架状态处理
处理所需字段
	moq 			起订量
	stock			库存
	ladder_price	阶梯价
处理流程
	满足 字段 moq stock ladder_price都有值 ,并且 moq < =stock ; 那么 sku状态 就是 审核通过(status=1)
    否则 就是下架状态(status=3)
*/
func (S *LySaveService) statusHandle(lySkuEntity *saveModel.LySkuEntity) {

	lySkuEntity.GoodsStatus = 1

	if lySkuEntity.LadderPrice == "" {
		lySkuEntity.GoodsStatus = 3 //下架
	}
	if lySkuEntity.Moq == 0 || lySkuEntity.Stock == 0 || lySkuEntity.Moq > lySkuEntity.Stock {
		lySkuEntity.GoodsStatus = 3 //下架
	}
}

//更新
func (S *LySaveService) update(lySkuEntity saveModel.LySkuEntity, lySaveRequest saveModel.LySaveRequest) error {
	db, table := common.ResolveSpu(lySkuEntity.GoodsId) //解析skuID,返回对应的表和库,应该在生成skuID的时候返回表和库
	//修改数据库
	affected, err := mysql.Conn(db).Table(table).Where("goods_id=?", lySkuEntity.GoodsId).Update(lySkuEntity)
	if err != nil {
		return e.NewApiError("修改sku mysql数据失败 msg:" + err.Error())
	}
	if affected <= 0 { //没有什么可以修改的,直接完成就好了
		fmt.Println("没什么可以修改的")
		logger.Select("lysku_save").Info("不需要修改:brand_name:" + lySaveRequest.BrandName + ";sku_id:" + lySkuEntity.GoodsId + ";good_name:" + lySaveRequest.GoodsName)
		return nil
	} else {
		err = S.skuCacheSave(false, lySkuEntity.GoodsId, lySkuEntity, lySaveRequest)
		if err != nil {
			logger.Select("lysku_save").Info("修改完成:brand_name:" + lySaveRequest.BrandName + ";sku_id:" + lySkuEntity.GoodsId + ";good_name:" + lySaveRequest.GoodsName)
			return e.NewApiError("缓存处理失败" + err.Error())
		}
	}
	return nil

}

//插入
func (S *LySaveService) insert(lySkuEntity saveModel.LySkuEntity, lySaveRequest saveModel.LySaveRequest) (error, string) {

	skuId := common.StructureNumber("sku") //生成skuID
	lySkuEntity.GoodsId = skuId
	db, table := common.ResolveSpu(lySkuEntity.GoodsId) //解析skuID,返回对应的表和库,应该在生成skuID的时候返回表和库
	//插入到数据库
	_, err := mysql.Conn(db).Table(table).Insert(lySkuEntity)
	if err != nil {
		return e.NewApiError("sku插入数据库失败" + err.Error()), ""
	}
	err = S.skuCacheSave(true, skuId, lySkuEntity, lySaveRequest)
	if err != nil {
		return e.NewApiError("缓存处理失败" + err.Error()), ""
	}

	return nil, skuId
}

/**
更新完sku mysql数据库后,需要对sku相关的 mongo 和redis进行处理
1.更新 redis skuinfo (1.新增直接插入,2.修改需要将老的redis sku数据合并新的)
2.插入mongoDB sku唯一(修改不需要动)
3.推送(就是插入redis list update_list_sku)
*/
func (S *LySaveService) skuCacheSave(isAdd bool, skuId string, lySkuEntity saveModel.LySkuEntity, lySaveRequest saveModel.LySaveRequest) error {

	//插入redis,redis修改和新增代码量有点大,单独封装一个函数
	err := S.saveRedisSkuInfo(isAdd, skuId, lySkuEntity, lySaveRequest)
	if err != nil {
		return e.NewApiError("插入redis失败" + err.Error())
	}
	if isAdd {
		//插入到mongo
		skuMongo := lySkuEntity.ToMongoSku()
		err = skuCache.MongoInsertOnlySkuInfo(skuMongo)
		if err != nil {
			return e.NewApiError("插入redis失败" + err.Error())
		}
	}
	//实时推送(插入到 redis list update_list_sku )
	err = skuCache.RedisPushEsList(skuId)
	if err != nil {
		return e.NewApiError("实时推送失败" + err.Error())
	}
	return nil
}

/**
修改redis hash key为 sku的数据(其实就是插入)
为什么要合并?:有其他地方会存这个redis,会出现 len不是固定的,里面的字段类型也会不一样,要分开处理
1.新增
	新增,比较简单,就直接组装SkuRedis结构就行
2.修改
	读取之前(老的)的redis数据,跟新的redisSku合并
	合并规则:需要修改的,覆盖之前的,不需要修改的保持原样
*/
func (S *LySaveService) saveRedisSkuInfo(isAdd bool, skuId string, lySkuEntity saveModel.LySkuEntity, lySaveRequest saveModel.LySaveRequest) error {

	redisSkuInfo := lySkuEntity.ToRedisSku() //组装需要更新的redisSku数据
	redisSkuInfo.GoodsImages = lySaveRequest.GoodsImages
	redisSkuInfo.Canal = lySaveRequest.Canal
	redisSkuInfo.UpdateTime = int(time.Now().Unix())
	redisSkuInfo.LadderPrice = lySaveRequest.LadderPrice //LadderPrice 如果已经是字符串,json处理会有斜划线,所以再赋值一次
	if isAdd {
		oldByte, err := json.Marshal(redisSkuInfo)
		if err != nil {
			return nil
		}
		return skuCache.RedisInsertSkuInfo(skuId, string(oldByte))
	} else {
		//将老的redis的数据转成map
		var oldSkuInfoMap map[string]interface{}
		oldSkuInfoStr, err := skuCache.RedisGetSkuInfo(skuId) //获取老的sku数据
		json.Unmarshal([]byte(oldSkuInfoStr), &oldSkuInfoMap)
		if err != nil {
			return nil
		}
		//is_expire 给清理掉
		if _, ok := oldSkuInfoMap["is_expire"]; ok {
			delete(oldSkuInfoMap, "is_expire")
		}
		//合并,得到新的map
		err, newMap := S.structMergeMap(redisSkuInfo, oldSkuInfoMap)
		if err != nil {
			return e.NewApiError("结构和map合并失败" + err.Error())
		}
		//转化成json字符串
		newMapBytes, err := json.Marshal(newMap)
		if err != nil {
			return err
		}
		//插入redisSku
		err = skuCache.RedisInsertSkuInfo(skuId, string(newMapBytes))
		if err != nil {
			return nil
		}
	}
	return nil

}

/**
struct 跟map Map合并,返回一个新的map(后续可以整理成公共函数)
*/
func (S *LySaveService) structMergeMap(redisSkuInfo model.LySkuRedisInfo, oldSkuInfoMap map[string]interface{}) (error, map[string]interface{}) {

	//将新的redisSku数据struct  和 老的redis的数据map 合并成map并转换成json返回
	GetValue := func(fieldValue reflect.Value) interface{} {
		switch fieldValue.Kind() {
		case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int, reflect.Int64:
			return fieldValue.Int()
		case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint, reflect.Uint64:
			return fieldValue.Uint()
		case reflect.Float32, reflect.Float64:
			return fieldValue.Float()
		case reflect.String:
			return fieldValue.String()
		case reflect.Bool:
			return fieldValue.Bool()
		default:
			return "other"
			//case reflect.Slice,:
		}
	}

	//组装需要更新的redisSku数据 struct
	var newMap = make(map[string]interface{})
	v := reflect.ValueOf(redisSkuInfo)
	t := reflect.TypeOf(redisSkuInfo)
	for i := 0; i < v.NumField(); i++ {
		name := t.Field(i).Tag.Get("json")
		fieldValue := v.Field(i)
		//name:=v.Type().Name()
		_, ok := oldSkuInfoMap[name]

		if v.Field(i).IsZero() && ok { // struct 有些字段为0值,还是要用老的redis的数据
			newMap[name] = oldSkuInfoMap[name]
		} else {
			if name == "ladder_price" {
				newMap[name] = redisSkuInfo.LadderPrice //ladder_price 单独处理
			} else {
				mapValue := GetValue(fieldValue)
				newMap[name] = mapValue
			}

		}
		delete(oldSkuInfoMap, name) //删除老的数据已经合并的字段
	}
	//将老数据独有的字段,放进newMap
	if len(oldSkuInfoMap) > 0 {
		for oldk, oldv := range oldSkuInfoMap {
			newMap[oldk] = oldv
		}
	}
	return nil, newMap
}

/**
修改
	先写的SkuSave ,SkuEdit 基本是调用 SkuSave 之前写好的方法
*/
func (S *LySaveService) SkuEdit(lyEditRequest saveModel.LyEditRequest) (error, string) {

	lySkuEntity := lyEditRequest.ToLySkuEntity() //sku实体
	lySkuEntity.GoodsId = lyEditRequest.GoodsId

	lySaveRequest := lyEditRequest.ToLySaveRequest() //转换成lySaveRequest 因为公用 savesku封装的的方法,公用方法需要传lySaveRequest(后续参数改成接口)

	//阶梯价格处理
	err := S.ladderPriceHandle(lySaveRequest.LadderPrice, &lySkuEntity)
	if err != nil {
		return e.NewApiError("SkuEdit 阶梯价格处理失败" + err.Error()), ""
	}
	oldRedisStr, err := skuCache.RedisGetSkuInfo(lyEditRequest.GoodsId)
	if err != nil {
		return e.NewApiError("修改sku:查询redis sku失败" + err.Error()), ""
	}
	if oldRedisStr == "" { //判断是否存在
		return e.NewApiError("sku不存在,skuID:" + lyEditRequest.GoodsId + err.Error()), ""
	}
	err = S.update(lySkuEntity, lySaveRequest)
	if err != nil {
		return e.NewApiError("SkuEdit 修改sku失败" + err.Error()), ""
	}
	return nil, lySkuEntity.GoodsId
}