package module

import (
	"errors"
	"fmt"
	"strconv"
	"strings"
	"sync"

	"github.com/gomodule/redigo/redis"
	"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/redisdb"
	"gitlab.alibaba-inc.com/pai_biz_arch/pairec/recconf"
	"gitlab.alibaba-inc.com/pai_biz_arch/pairec/utils"
)

type FeatureRedisDao struct {
	*FeatureBaseDao
	redis       *redisdb.Redis
	redisPrefix string
}

func NewFeatureRedisDao(config recconf.FeatureDaoConfig) *FeatureRedisDao {
	dao := &FeatureRedisDao{
		FeatureBaseDao: NewFeatureBaseDao(&config),
		redisPrefix:    config.RedisPrefix,
	}
	redis, err := redisdb.GetRedis(config.RedisName)
	if err != nil {
		log.Error(fmt.Sprintf("error=%v", err))
		return nil
	}
	dao.redis = redis
	return dao
}

func (d *FeatureRedisDao) 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 *FeatureRedisDao) 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\tuid=%s\terror=property not found(%s)", context.RecommendId, user.Id, comms[1]))
		return
	}

	key = d.redisPrefix + key

	conn := d.redis.Get()
	defer conn.Close()
	str, err := redis.String(conn.Do("GET", key))
	if err != nil {
		if errors.Is(err, redis.ErrNil) {
			log.Info(fmt.Sprintf("requestId=%s\tuid=%s\tmsg=user feature empty", context.RecommendId, user.Id))
		} else {
			log.Error(fmt.Sprintf("requestId=%s\tuid=%s\terror=get user feature error(%v)", context.RecommendId, user.Id, err))
		}
		return
	}
	keyParis := strings.Split(str, ",")
	if len(keyParis) == 0 {
		return
	}
	for _, pair := range keyParis {
		keyValues := strings.Split(pair, ":")
		if len(keyValues) == 2 {
			name := keyValues[0]
			val := keyValues[1]
			f, err := strconv.ParseFloat(val, 64)
			if err == nil {
				user.AddProperty(name, f)
			} else {
				user.AddProperty(name, val)
			}
		}
	}
}

func (d *FeatureRedisDao) 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(len(items)/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{}
				for _, item := range itemlist {
					var key string
					if fk == "item:id" {
						key = string(item.Id)
					} else {
						key = item.StringProperty(fk)
					}
					key = d.redisPrefix + key

					keys = append(keys, key)
				}

				conn := d.redis.Get()
				values, err := redis.Strings(conn.Do("MGET", keys...))
				if err != nil {
					log.Error(fmt.Sprintf("requestId=%s\tmodule=FeatureRedisDao\terror=%v", context.RecommendId, err))
					conn.Close()
					return
				}
				conn.Close()
				for i, str := range values {
					item := itemlist[i]
					// item.AddProperty("id", string(item.Id))
					if str == "" {
						continue
					}
					keyParis := strings.Split(str, ",")
					if len(keyParis) == 0 {
						continue
					}
					// var category string

					for _, pair := range keyParis {
						keyValues := strings.Split(pair, ":")
						if len(keyValues) == 2 {
							name := keyValues[0]
							val := keyValues[1]
							f, err := strconv.ParseFloat(val, 64)
							if err == nil {
								item.AddProperty(name, f)
							} else {
								item.AddProperty(name, val)
							}
						}
					}
				}
			default:
			}
		}()
	}
	wg.Wait()
}
