package service

import (
	"fmt"
	"sync"
	"time"

	"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/module"
	"gitlab.alibaba-inc.com/pai_biz_arch/pairec/recconf"
	"gitlab.alibaba-inc.com/pai_biz_arch/pairec/service/feature"
	"gitlab.alibaba-inc.com/pai_biz_arch/pairec/utils"
)

var featureService *FeatureService

type FeatureFunc func(user *module.User, items []*module.Item, context *context.RecommendContext) []*module.Item
type LoadFeatureFunc func(user *module.User, items []*module.Item, context *context.RecommendContext)

func init() {
	featureService = &FeatureService{
		featureSceneMap:      make(map[string][]*feature.Feature),
		featureSceneAsyncMap: make(map[string]bool),
		featureFuncMap:       make(map[string]FeatureFunc),
		loadFeatureFuncMap:   make(map[string]LoadFeatureFunc),
	}
}

func DefaultFeatureService() *FeatureService {
	return featureService
}
func RegisterFeatureFunc(sceneName string, f FeatureFunc) {
	DefaultFeatureService().AddFeatureFunc(sceneName, f)
}

func RegisterLoadFeatureFunc(sceneName string, f LoadFeatureFunc) {
	DefaultFeatureService().AddLoadFeatureFunc(sceneName, f)
}

type FeatureService struct {
	featureSceneMap      map[string][]*feature.Feature
	featureSceneAsyncMap map[string]bool
	featureFuncMap       map[string]FeatureFunc
	loadFeatureFuncMap   map[string]LoadFeatureFunc
}

func (s *FeatureService) AddFeatureFunc(sceneName string, f FeatureFunc) {
	s.featureFuncMap[sceneName] = f
}

func (s *FeatureService) AddLoadFeatureFunc(sceneName string, f LoadFeatureFunc) {
	s.loadFeatureFuncMap[sceneName] = f
}

// LoadFeatures load user or item feature use feature.Feature
func (s *FeatureService) LoadFeatures(user *module.User, items []*module.Item, context *context.RecommendContext) []*module.Item {
	start := time.Now()
	var sceneName string
	if context.ExperimentResult != nil {
		sceneName = context.ExperimentResult.GetExperimentParams().GetString("features.scene.name", "")
	}
	if sceneName == "" {
		sceneName = context.GetParameter("scene").(string)
	}
	if features, ok := s.featureSceneMap[sceneName]; ok {
		async := s.featureSceneAsyncMap[sceneName]
		if async {
			var wg sync.WaitGroup
			for _, fea := range features {
				wg.Add(1)
				go func(fea *feature.Feature) {
					defer wg.Done()
					fea.LoadFeatures(user, items, context)
				}(fea)
			}
			if loadFeatureFunc, exist := s.loadFeatureFuncMap[sceneName]; exist {
				wg.Add(1)
				go func() {
					defer wg.Done()
					loadFeatureFunc(user, items, context)
				}()
			}

			wg.Wait()
		} else {
			for _, feature := range features {
				feature.LoadFeatures(user, items, context)
			}

			if loadFeatureFunc, exist := s.loadFeatureFuncMap[sceneName]; exist {
				loadFeatureFunc(user, items, context)
			}

		}

		featureFunc, ok := s.featureFuncMap[sceneName]
		if ok {
			items = featureFunc(user, items, context)
		}
	}

	log.Info(fmt.Sprintf("requestId=%s\tmodule=LoadFeatures\tcost=%d", context.RecommendId, utils.CostTime(start)))

	return items
}

func (s *FeatureService) LoadFeaturesForGeneralRank(user *module.User, items []*module.Item, context *context.RecommendContext) {
	start := time.Now()
	sceneName := context.GetParameter("scene").(string)
	if features, ok := s.featureSceneMap[sceneName]; ok {
		async := s.featureSceneAsyncMap[sceneName]
		if async {
			var wg sync.WaitGroup
			for _, fea := range features {
				wg.Add(1)
				go func(fea *feature.Feature) {
					defer wg.Done()
					fea.LoadFeatures(user, items, context)
				}(fea)
			}

			wg.Wait()
		} else {
			for _, feature := range features {
				feature.LoadFeatures(user, items, context)
			}

		}

		// featureFunc, ok := s.featureFuncMap[sceneName]
		// if ok {
		// featureFunc(user, items, context)
		// }
	}

	log.Info(fmt.Sprintf("requestId=%s\tmodule=LoadFeaturesForGeneralRank\tcost=%d", context.RecommendId, utils.CostTime(start)))
}

func LoadFeatureConfig(config *recconf.RecommendConfig) {
	for name, sceneConf := range config.FeatureConfs {
		var features []*feature.Feature
		for _, conf := range sceneConf.FeatureLoadConfs {
			f := feature.LoadWithConfig(conf)
			features = append(features, f)
		}

		featureService.featureSceneAsyncMap[name] = false
		if sceneConf.AsynLoadFeature {
			featureService.featureSceneAsyncMap[name] = true
		}
		featureService.featureSceneMap[name] = features
	}
}
