package rank

import (
	"reflect"
	"sync"

	"github.com/alibaba/pairec/v2/algorithm/eas"
	"github.com/alibaba/pairec/v2/algorithm/eas/easyrec"
	"github.com/alibaba/pairec/v2/algorithm/response"
	"github.com/alibaba/pairec/v2/module"
	"github.com/alibaba/pairec/v2/utils"
)

type IAlgoData interface {
	GetFeatures() interface{}
	SetError(err error)
	Error() error
	GetItems() []*module.Item
	GetAlgoResult() map[string][]response.AlgoResponse
	SetAlgoResult(algoName string, results []response.AlgoResponse)
}

type IAlgoDataGenerator interface {
	AddFeatures(item *module.Item, itemFeatures map[string]interface{}, userFeatures map[string]interface{})
	GeneratorAlgoData() IAlgoData
	// GeneratorAlgoDataDebug generator algo data with debug mode
	GeneratorAlgoDataDebug() IAlgoData
	GeneratorAlgoDataDebugWithLevel(level int) IAlgoData
	HasFeatures() bool
	SetItemFeatures([]string)
}

func CreateAlgoDataGenerator(processor string, contextFeatures []string) IAlgoDataGenerator {

	if processor == eas.Eas_Processor_EASYREC {
		return NewEasyrecAlgoDataGenerator(contextFeatures)
	} else {
		return NewAlgoDataGenerator()
	}
}

type AlgoDataBase struct {
	Items []*module.Item
	// RequestData []map[string]interface{}
	AlgoResult map[string][]response.AlgoResponse
	Err        error
	Mutex      sync.Mutex
}

func (a *AlgoDataBase) SetError(err error) {
	a.Err = err
}
func (a *AlgoDataBase) Error() error {
	return a.Err
}
func (a *AlgoDataBase) SetAlgoResult(algoName string, results []response.AlgoResponse) {

	a.Mutex.Lock()
	defer a.Mutex.Unlock()
	a.AlgoResult[algoName] = results
}
func (a *AlgoDataBase) GetAlgoResult() map[string][]response.AlgoResponse {
	return a.AlgoResult
}

func (a *AlgoDataBase) GetItems() []*module.Item {
	return a.Items
}

type AlgoData struct {
	*AlgoDataBase
	RequestData []map[string]interface{}
}

func (d *AlgoData) GetFeatures() interface{} {
	return d.RequestData
}

type EasyrecAlgoData struct {
	*AlgoDataBase
	easyrecRequest *easyrec.PBRequest
}

func (d *EasyrecAlgoData) GetFeatures() interface{} {
	return d.easyrecRequest
}

type AlgoDataGenerator struct {
	requestData []map[string]interface{}
	requestItem []*module.Item
}

// SetItemFeatures implements IAlgoDataGenerator.
func (g *AlgoDataGenerator) SetItemFeatures([]string) {
}

func NewAlgoDataGenerator() *AlgoDataGenerator {

	return &AlgoDataGenerator{
		requestData: make([]map[string]interface{}, 0, 100),
		requestItem: make([]*module.Item, 0, 100),
	}
}
func (g *AlgoDataGenerator) AddFeatures(item *module.Item, itemFeatures map[string]interface{}, userFeatures map[string]interface{}) {
	if item != nil {
		g.requestItem = append(g.requestItem, item)
	}

	features := make(map[string]interface{}, len(itemFeatures)+len(userFeatures))
	for k, v := range userFeatures {
		features[k] = v
	}
	for k, v := range itemFeatures {
		features[k] = v
	}

	g.requestData = append(g.requestData, features)
}
func (g *AlgoDataGenerator) GeneratorAlgoData() IAlgoData {
	copydata := make([]map[string]interface{}, len(g.requestData))
	copyItems := make([]*module.Item, len(g.requestItem))
	copy(copyItems, g.requestItem)
	copy(copydata, g.requestData)

	algoData := &AlgoData{
		AlgoDataBase: &AlgoDataBase{
			Items:      copyItems,
			AlgoResult: make(map[string][]response.AlgoResponse),
		},
		RequestData: copydata,
	}

	g.requestData = g.requestData[:0]
	g.requestItem = g.requestItem[:0]
	return algoData
}
func (g *AlgoDataGenerator) GeneratorAlgoDataDebug() IAlgoData {
	return g.GeneratorAlgoData()
}

func (g *AlgoDataGenerator) GeneratorAlgoDataDebugWithLevel(level int) IAlgoData {
	return g.GeneratorAlgoData()
}

func (g *AlgoDataGenerator) HasFeatures() bool {
	return len(g.requestData) > 0
}

type feature struct {
	name      string
	valueType reflect.Type
}

func (f *feature) defaultValue() interface{} {
	switch f.valueType.Kind() {
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		return int(0)
	case reflect.Float32, reflect.Float64:
		return float64(0)
	case reflect.String:
		return ""
	default:
		return ""
	}
}

type EasyrecAlgoDataGenerator struct {
	requestItem           []*module.Item
	contextFeatures       map[string][]interface{}
	parseFeature          bool
	itemFeatures          []*feature
	userFeatures          map[string]interface{}
	inputItemFeatures     []*feature
	inputItemFeatureMap   map[string][]interface{}
	parseInputItemFeature bool
}

func NewEasyrecAlgoDataGenerator(contextFeatures []string) *EasyrecAlgoDataGenerator {
	generator := &EasyrecAlgoDataGenerator{
		requestItem:     make([]*module.Item, 0, 100),
		contextFeatures: make(map[string][]interface{}, 8),
		parseFeature:    true,
	}

	if len(contextFeatures) > 0 {
		for _, featureName := range contextFeatures {
			feature := &feature{
				name:      featureName,
				valueType: reflect.TypeOf(""),
			}
			generator.itemFeatures = append(generator.itemFeatures, feature)
		}
	}

	return generator
}

// SetItemFeatures implements IAlgoDataGenerator.
func (generator *EasyrecAlgoDataGenerator) SetItemFeatures(inputItemFeatures []string) {
	if len(inputItemFeatures) > 0 {
		generator.inputItemFeatureMap = make(map[string][]interface{}, 8)
		if inputItemFeatures[0] != "*" {
			generator.parseInputItemFeature = true
			for _, featureName := range inputItemFeatures {
				feature := &feature{
					name:      featureName,
					valueType: reflect.TypeOf(""),
				}
				generator.inputItemFeatures = append(generator.inputItemFeatures, feature)
			}
		}
	} else {
		generator.parseInputItemFeature = true
	}
}

func (g *EasyrecAlgoDataGenerator) AddFeatures(item *module.Item, itemFeatures map[string]interface{}, userFeatures map[string]interface{}) {
	if item != nil {
		g.requestItem = append(g.requestItem, item)
	}
	if !g.parseFeature {
		for k, v := range itemFeatures {
			feature := &feature{
				name:      k,
				valueType: reflect.TypeOf(v),
			}
			g.itemFeatures = append(g.itemFeatures, feature)
		}

		g.userFeatures = userFeatures
		g.parseFeature = true
	}
	if !g.parseInputItemFeature {
		var contextItemFeatureNames []string
		for _, v := range g.itemFeatures {
			contextItemFeatureNames = append(contextItemFeatureNames, v.name)
		}
		for k, v := range itemFeatures {
			if utils.IndexOfArray(contextItemFeatureNames, k) == -1 {
				feature := &feature{
					name:      k,
					valueType: reflect.TypeOf(v),
				}
				g.inputItemFeatures = append(g.inputItemFeatures, feature)
			}
		}
		g.parseInputItemFeature = true

	}
	if len(g.userFeatures) == 0 {
		g.userFeatures = userFeatures
	}

	for _, f := range g.itemFeatures {
		if v, ok := itemFeatures[f.name]; ok {
			g.contextFeatures[f.name] = append(g.contextFeatures[f.name], v)
		} else {
			g.contextFeatures[f.name] = append(g.contextFeatures[f.name], f.defaultValue())
		}
	}
	for _, f := range g.inputItemFeatures {
		if v, ok := itemFeatures[f.name]; ok {
			g.inputItemFeatureMap[f.name] = append(g.inputItemFeatureMap[f.name], v)
		} else {
			g.inputItemFeatureMap[f.name] = append(g.inputItemFeatureMap[f.name], f.defaultValue())
		}
	}
}

func (g *EasyrecAlgoDataGenerator) GeneratorAlgoData() IAlgoData {
	copyItems := make([]*module.Item, len(g.requestItem))
	copy(copyItems, g.requestItem)

	builder := easyrec.NewEasyrecRequestBuilder()
	for k, v := range g.userFeatures {
		builder.AddUserFeature(k, v)
	}
	for _, item := range g.requestItem {
		builder.AddItemId(string(item.Id))
	}
	for k, v := range g.contextFeatures {
		builder.AddContextFeature(k, v)
		g.contextFeatures[k] = g.contextFeatures[k][:0]
	}
	for k, v := range g.inputItemFeatureMap {
		builder.AddItemFeature(k, v)
		g.inputItemFeatureMap[k] = g.inputItemFeatureMap[k][:0]
	}

	algoData := &EasyrecAlgoData{
		AlgoDataBase: &AlgoDataBase{
			Items:      copyItems,
			AlgoResult: make(map[string][]response.AlgoResponse),
		},
		easyrecRequest: builder.EasyrecRequest(),
	}

	g.requestItem = g.requestItem[:0]
	return algoData
}

func (g *EasyrecAlgoDataGenerator) GeneratorAlgoDataDebug() IAlgoData {
	return g.GeneratorAlgoDataDebugWithLevel(1)
}

func (g *EasyrecAlgoDataGenerator) GeneratorAlgoDataDebugWithLevel(level int) IAlgoData {
	copyItems := make([]*module.Item, len(g.requestItem))
	copy(copyItems, g.requestItem)

	builder := easyrec.NewEasyrecRequestBuilderDebugWithLevel(level)
	for k, v := range g.userFeatures {
		builder.AddUserFeature(k, v)
	}
	for _, item := range g.requestItem {
		builder.AddItemId(string(item.Id))
	}
	for k, v := range g.contextFeatures {
		builder.AddContextFeature(k, v)
		g.contextFeatures[k] = g.contextFeatures[k][:0]
	}
	for k, v := range g.inputItemFeatureMap {
		builder.AddItemFeature(k, v)
		g.inputItemFeatureMap[k] = g.inputItemFeatureMap[k][:0]
	}

	algoData := &EasyrecAlgoData{
		AlgoDataBase: &AlgoDataBase{
			Items:      copyItems,
			AlgoResult: make(map[string][]response.AlgoResponse),
		},
		easyrecRequest: builder.EasyrecRequest(),
	}

	g.requestItem = g.requestItem[:0]
	return algoData
}

func (g *EasyrecAlgoDataGenerator) HasFeatures() bool {
	return len(g.requestItem) > 0
}
