package web

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

	"gitlab.alibaba-inc.com/pai_biz_arch/pairec/context"
	plog "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"
	"gitlab.alibaba-inc.com/pai_biz_arch/pairec/utils"
)

type CallBackParam struct {
	SceneId     string                   `json:"scene_id"`
	RequestId   string                   `json:"request_id"`
	Uid         string                   `json:"uid"`
	Features    map[string]interface{}   `json:"features"`
	ItemList    []map[string]interface{} `json:"item_list"`
	RequestInfo map[string]interface{}   `json:"request_info"`
	Debug       bool                     `json:"debug"`
}

func (r *CallBackParam) GetParameter(name string) interface{} {
	if name == "uid" {
		return r.Uid
	} else if name == "scene" {
		return r.SceneId + "_callback"
	} else if name == "request_id" {
		return r.RequestId
	}

	return nil
}

type CallBackResponse struct {
	Response
}

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

type CallBackController struct {
	Controller
	param   CallBackParam
	context *context.RecommendContext
}

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

func (r *CallBackController) CheckParameter() error {
	if err := json.Unmarshal(r.RequestBody, &r.param); err != nil {
		return err
	}

	if len(r.param.Uid) == 0 {
		return errors.New("uid not empty")
	}

	if len(r.param.RequestId) == 0 {
		return errors.New("request_id not empty")
	}

	if len(r.param.SceneId) == 0 {
		return errors.New("scene_id not empty")
	}

	if len(r.param.ItemList) == 0 {
		return errors.New("recommend item list not empty")
	}
	return nil
}

func (c *CallBackController) doProcess(w http.ResponseWriter, r *http.Request) {
	c.makeCallBackContext()

	userId := module.UID(c.param.Uid)
	user := module.NewUserWithContext(userId, c.context)
	var items []*module.Item
	for _, info := range c.param.ItemList {
		itemId := fmt.Sprintf("%v", info["item_id"])
		item := module.NewItem(itemId)
		items = append(items, item)
	}

	// load characteristics
	callBackService := service.NewCallBackService()
	callBackService.User = user
	callBackService.Items = items
	callBackService.LoadLog(c.context)

	// sorting data
	for key, val := range c.param.Features {
		if _, ok := callBackService.User.Properties[key]; ok {
			callBackService.User.Properties[key] = val
		} else {
			callBackService.User.AddProperty(key, val)
		}
	}

	// the data fell into Kafka
	log := make(map[string]interface{})
	log["request_id"] = c.param.RequestId
	//log["source"] = c.context.GetParameter("scene").(string)
	log["scene"] = c.param.SceneId
	log["request_time"] = time.Now().Unix()
	log["user_features"] = callBackService.User.MakeUserFeatures()
	log["user_id"] = string(callBackService.User.Id)
	for k, v := range c.param.RequestInfo {
		log[k] = v
	}
	var messages []string
	i := 0
	for _, item := range callBackService.Items {
		log["item_id"] = string(item.Id)
		log["item_features"] = item.GetFeatures()
		msg, _ := json.Marshal(log)
		if c.param.Debug {
			info := fmt.Sprintf("requestId=%s\tmsg=%s", c.RequestId, string(msg))
			plog.Debug(info)
		}

		messages = append(messages, string(msg))
		i++
		if i%4 == 0 {
			if err := callBackService.RecordLogList(c.context, messages); err != nil {
				c.dealResponse(w, r, ERROR_PARAMETER_CODE, err.Error())
				return
			}
			messages = messages[:0]
		}
	}
	if len(messages) > 0 {
		if err := callBackService.RecordLogList(c.context, messages); err != nil {
			c.dealResponse(w, r, ERROR_PARAMETER_CODE, err.Error())
			return
		}
		messages = messages[:0]
	}
	c.dealResponse(w, r, SUCCESS_CODE, "success")
}

func (c *CallBackController) dealResponse(w http.ResponseWriter, r *http.Request, code int, msg string) {
	response := CallBackResponse{
		Response: Response{
			RequestId: c.RequestId,
			Code:      code,
			Message:   msg,
		},
	}

	io.WriteString(w, response.ToString())
}

func (c *CallBackController) makeCallBackContext() {
	c.context = context.NewRecommendContext()
	c.context.Param = &c.param
	c.context.Config = recconf.Config
	c.context.RecommendId = c.RequestId
}
