package module

import (
	"fmt"
	"math"
	"strings"
	"sync"

	"github.com/aliyun/aliyun-tablestore-go-sdk/tablestore"
	"gitlab.alibaba-inc.com/pai_biz_arch/pairec/context"
	"gitlab.alibaba-inc.com/pai_biz_arch/pairec/log"
	"gitlab.alibaba-inc.com/pai_biz_arch/pairec/persist/tablestoredb"
	"gitlab.alibaba-inc.com/pai_biz_arch/pairec/recconf"
	"gitlab.alibaba-inc.com/pai_biz_arch/pairec/utils"
)

type FeatureTablestoreDao struct {
	*FeatureBaseDao
	tablestore         *tablestoredb.TableStore
	table              string
	userFeatureKeyName string
	itemFeatureKeyName string
	userSelectFields   string
	itemSelectFields   string
}

func NewFeatureTablestoreDao(config recconf.FeatureDaoConfig) *FeatureTablestoreDao {
	dao := &FeatureTablestoreDao{
		FeatureBaseDao:     NewFeatureBaseDao(&config),
		table:              config.TableStoreTableName,
		userFeatureKeyName: config.UserFeatureKeyName,
		itemFeatureKeyName: config.ItemFeatureKeyName,
		userSelectFields:   config.UserSelectFields,
		itemSelectFields:   config.ItemSelectFields,
		// itemStmtMap:        make(map[string]*sql.Stmt),
	}
	tablestore, err := tablestoredb.GetTableStore(config.TableStoreName)
	if err != nil {
		log.Error(fmt.Sprintf("%v", err))
		return nil
	}
	dao.tablestore = tablestore
	return dao
}

func (d *FeatureTablestoreDao) FeatureFetch(user *User, items []*Item, context *context.RecommendContext) {
	if d.featureStore == Feature_Store_User {
		d.userFeatureFetch(user, context)
	} else {
		d.itemsFeatureFetch(items, context)
	}
}

func (d *FeatureTablestoreDao) userFeatureFetch(user *User, context *context.RecommendContext) {
	comms := strings.Split(d.featureKey, ":")
	if len(comms) < 2 {
		log.Error(fmt.Sprintf("requestId=%s\tuid=%s\terror=featureKey error(%s)", context.RecommendId, user.Id, d.featureKey))
		return
	}

	key := user.StringProperty(comms[1])
	if key == "" {
		log.Error(fmt.Sprintf("requestId=%s\tmodule=FeatureTablestoreDao\terror=property not found(%s)", context.RecommendId, comms[1]))
		return
	}

	getRowRequest := new(tablestore.GetRowRequest)
	criteria := new(tablestore.SingleRowQueryCriteria)
	putPk := new(tablestore.PrimaryKey)
	putPk.AddPrimaryKeyColumn(d.userFeatureKeyName, key)

	criteria.PrimaryKey = putPk
	if d.userSelectFields != "" {
		criteria.ColumnsToGet = strings.Split(d.userSelectFields, ",")
	}
	getRowRequest.SingleRowQueryCriteria = criteria
	getRowRequest.SingleRowQueryCriteria.TableName = d.table
	getRowRequest.SingleRowQueryCriteria.MaxVersion = 1
	getResp, err := d.tablestore.Client.GetRow(getRowRequest)
	if err != nil {
		log.Error(fmt.Sprintf("requestId=%s\tmodule=UserCustomRecallTableStoreDao\terror=%v", context.RecommendId, err))
		return
	}

	for _, column := range getResp.Columns {
		name := column.ColumnName
		switch val := column.Value.(type) {
		case string:
			user.AddProperty(name, val)
		case int:
			user.AddProperty(name, val)
		case float64:
			user.AddProperty(name, val)
		default:
			user.AddProperty(name, val)
		}
	}
}

func (d *FeatureTablestoreDao) itemsFeatureFetch(items []*Item, context *context.RecommendContext) {
	fk := d.featureKey
	if fk != "item:id" {
		comms := strings.Split(d.featureKey, ":")
		if len(comms) < 2 {
			log.Error(fmt.Sprintf("requestId=%s\tevent=itemsFeatureFetch\terror=featureKey error(%s)", context.RecommendId, d.featureKey))
			return
		}

		fk = comms[1]
	}

	cpuCount := utils.MaxInt(int(math.Ceil(float64(len(items))/float64(100))), 1)
	maps := make(map[int][]*Item)
	for i, item := range items {
		maps[i%cpuCount] = append(maps[i%cpuCount], item)
	}

	requestCh := make(chan []*Item, cpuCount)
	defer close(requestCh)

	for _, itemlist := range maps {
		requestCh <- itemlist
	}

	var wg sync.WaitGroup
	for i := 0; i < cpuCount; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			select {
			case itemlist := <-requestCh:
				// var keys []interface{}
				key2Item := make(map[string]*Item, len(itemlist))
				batchGetReq := &tablestore.BatchGetRowRequest{}
				mqCriteria := &tablestore.MultiRowQueryCriteria{}
				for _, item := range itemlist {
					var key string
					if fk == "item:id" {
						key = string(item.Id)
					} else {
						key = item.StringProperty(fk)
					}
					key2Item[key] = item

					// keys = append(keys, key)
					pkToGet := new(tablestore.PrimaryKey)
					pkToGet.AddPrimaryKeyColumn(d.itemFeatureKeyName, key)
					mqCriteria.AddRow(pkToGet)
				}
				mqCriteria.MaxVersion = 1
				if d.itemSelectFields != "" {
					mqCriteria.ColumnsToGet = strings.Split(d.itemSelectFields, ",")
				}
				mqCriteria.TableName = d.table
				batchGetReq.MultiRowQueryCriteria = append(batchGetReq.MultiRowQueryCriteria, mqCriteria)
				batchGetResponse, err := d.tablestore.Client.BatchGetRow(batchGetReq)

				if err != nil {
					log.Error(fmt.Sprintf("requestId=%s\tmodule=FeatureTablestoreDao\terror=%v", context.RecommendId, err))
					return
				}

				rowsResult, ok := batchGetResponse.TableToRowsResult[d.table]
				if !ok {
					log.Error(fmt.Sprintf("requestId=%s\tmodule=FeatureTablestoreDao\ttable row result empty", context.RecommendId))
					return
				}

				for _, row := range rowsResult {
					if !row.IsSucceed {
						continue
					}
					var key string
					if len(row.PrimaryKey.PrimaryKeys) > 0 {
						pkColumn := row.PrimaryKey.PrimaryKeys[0]
						key = pkColumn.Value.(string)
					}
					if key == "" {
						continue
					}
					item := key2Item[key]
					properties := make(map[string]interface{}, len(row.Columns))

					for _, column := range row.Columns {
						name := column.ColumnName
						switch val := column.Value.(type) {
						case string:
							properties[name] = val
						case int:
							properties[name] = val
						case float64:
							properties[name] = val
						default:
							properties[name] = val
						}

					}

					item.AddProperties(properties)
				}

			default:
			}
		}()
	}
	wg.Wait()
}
