package controller

import (
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"time"

	"gitlab.alibaba-inc.com/pai_biz_arch/pairec-experiment/client_golang/model"
	"gitlab.alibaba-inc.com/pai_biz_arch/pairec/abtest"
	"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/service"
	"gitlab.alibaba-inc.com/pai_biz_arch/pairec/utils"
	"gitlab.alibaba-inc.com/pai_biz_arch/pairec/web"
	"gitlab.alibaba-inc.com/pai_biz_arch/pairec/web/proxy"
)

type VideoFeedRecommendParam struct {
	Mid      string                 `json:"mid"` // user id
	SceneId  string                 `json:"scene_id"`
	Size     int                    `json:"size"` // get recommend items size
	Debug    bool                   `json:"debug"`
	Features map[string]interface{} `json:"features"`
}

func (r *VideoFeedRecommendParam) GetParameter(name string) interface{} {
	if name == "uid" {
		return r.Mid
	} else if name == "scene" {
		return r.SceneId
	} else if name == "category" {
		return "default"
	} else if name == "features" {
		return r.Features
	}

	return nil
}

type RecommendResponse struct {
	web.Response
	Size         int         `json:"size"`
	ExperimentId string      `json:"experiment_id"`
	Items        []*ItemData `json:"items"`
}
type ItemData struct {
	ItemId     string             `json:"item_id"`
	ItemType   string             `json:"item_type,omitempty"`
	RetrieveId string             `json:"retrieve_id"`
	Score      float64            `json:"score"`
	AlgoScores map[string]float64 `json:"algo_scores,omitempty"`
}

func (r *RecommendResponse) ToString() string {
	j, _ := json.Marshal(r)
	return string(j)
}

type VideoFeedController struct {
	web.Controller
	param   VideoFeedRecommendParam
	context *context.RecommendContext
}

func (c *VideoFeedController) Process(w http.ResponseWriter, r *http.Request) {
	c.Start = time.Now()
	var err error
	c.RequestBody, err = ioutil.ReadAll(r.Body)
	if err != nil {
		c.SendError(w, web.ERROR_PARAMETER_CODE, "read parammeter error")
		return
	}
	if len(c.RequestBody) == 0 {
		c.SendError(w, web.ERROR_PARAMETER_CODE, "request body empty")
		return
	}
	c.RequestId = utils.UUID()
	c.LogRequestBegin(r)
	if err := c.CheckParameter(); err != nil {
		c.SendError(w, web.ERROR_PARAMETER_CODE, err.Error())
		return
	}
	c.doProcess(w, r)
	c.End = time.Now()
	c.LogRequestEnd(r)
}
func (r *VideoFeedController) CheckParameter() error {
	if err := json.Unmarshal(r.RequestBody, &r.param); err != nil {
		return err
	}

	if len(r.param.Mid) == 0 {
		return errors.New("mid not empty")
	}
	if r.param.Size <= 0 {
		r.param.Size = 10
	}
	if r.param.SceneId == "" {
		r.param.SceneId = "default_scene"
	}

	if r.param.Features == nil {
		r.param.Features = make(map[string]interface{}, 0)
	}

	r.param.Features["mid"] = r.param.Mid
	return nil
}
func (c *VideoFeedController) doProcess(w http.ResponseWriter, r *http.Request) {
	c.makeRecommendContext()
	userRecommendService := service.NewUserRecommendService()
	items := userRecommendService.Recommend(c.context)
	data := make([]*ItemData, 0)
	for _, item := range items {
		if c.param.Debug {
			fmt.Println(item)
		}

		idata := &ItemData{
			ItemId:     string(item.Id),
			ItemType:   item.ItemType,
			RetrieveId: item.RetrieveId,
			Score:      item.Score,
			// AlgoScores: item.GetAlgoScores(),
		}

		if c.param.Debug {
			idata.AlgoScores = item.GetAlgoScores()
		}

		data = append(data, idata)
	}

	exp_id := ""
	if c.context.ExperimentResult != nil {
		exp_id = c.context.ExperimentResult.GetExpId()
	}
	if len(data) < c.param.Size {
		response := RecommendResponse{
			ExperimentId: exp_id,
			Size:         len(data),
			Items:        data,
			Response: web.Response{
				RequestId: c.RequestId,
				Code:      299,
				Message:   "items size not enough",
			},
		}
		io.WriteString(w, response.ToString())
		return
	}

	response := RecommendResponse{
		ExperimentId: exp_id,
		Size:         len(data),
		Items:        data,
		Response: web.Response{
			RequestId: c.RequestId,
			Code:      200,
			Message:   "success",
		},
	}
	io.WriteString(w, response.ToString())
}
func (c *VideoFeedController) makeRecommendContext() {
	c.context = context.NewRecommendContext()
	c.context.Size = c.param.Size
	c.context.Debug = c.param.Debug
	c.context.Param = &c.param
	c.context.RecommendId = c.RequestId
	c.context.Config = recconf.Config

	abcontext := model.ExperimentContext{
		Uid:          c.param.Mid,
		RequestId:    c.RequestId,
		FilterParams: map[string]interface{}{},
	}

	if abtest.GetExperimentClient() != nil {
		c.context.ExperimentResult = abtest.GetExperimentClient().MatchExperiment(c.param.SceneId, &abcontext)
		log.Info(c.context.ExperimentResult.Info())
	}
}
func (c *VideoFeedController) ProxyResponseFunc(w http.ResponseWriter, requestBody, responseBody []byte) bool {
	return true
}

func (c *VideoFeedController) ProxyErrorResponseFunc(w http.ResponseWriter, requestBody, responseBody []byte) bool {
	param := VideoFeedRecommendParam{}
	if err := json.Unmarshal(requestBody, &param); err != nil {
		return true
	}
	redis, err := redisdb.GetRedis("redis-cache")
	if err != nil {
		return true
	}

	// 这里 2 是 retry  次数
	itemids := proxy.GetBaselineData(param.Mid, redis, "global_hotness_", param.Size, 2)

	data := make([]*ItemData, 0)
	for _, id := range itemids {
		idata := &ItemData{
			ItemId:     id,
			RetrieveId: "global_hotness",
			Score:      0,
		}

		data = append(data, idata)
	}
	// 反序列化数据，构造 items 列表
	if len(data) < c.param.Size {
		response := RecommendResponse{
			ExperimentId: "",
			Size:         len(data),
			Items:        data,
			Response: web.Response{
				RequestId: c.RequestId,
				Code:      299,
				Message:   "items size not enough",
			},
		}
		io.WriteString(w, response.ToString())
		return false
	}

	response := RecommendResponse{
		ExperimentId: "",
		Size:         len(data),
		Items:        data,
		Response: web.Response{
			RequestId: c.RequestId,
			Code:      200,
			Message:   "success",
		},
	}
	io.WriteString(w, response.ToString())
	return false
}
