package utils

import (
	"context"
	"encoding/json"
	"fmt"
	"github.com/jinzhu/gorm"
	"github.com/pingcap/log"
	"github.com/pingcap/tep/db/entity"
	"go.uber.org/zap"
	"k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/apiserver/pkg/endpoints/request"
	"reflect"
	"strings"
	"time"
)

func Pagination(page int, itemsPerPage int, items interface{}) interface{} {
	t := reflect.TypeOf(items)
	if t.Kind() != reflect.Slice {
		panic("slice wanted")
	}
	startIndex := GetStartIndex(page, GetSize(itemsPerPage))
	v := reflect.ValueOf(items)
	if startIndex >= v.Len() {
		//invalid pagination parameter, return an empty result set
		return reflect.MakeSlice(t, 0, 0).Interface()
	}

	endIndex := startIndex + GetSize(itemsPerPage)
	if endIndex > v.Len() {
		endIndex = v.Len()
	}
	reflectSlice := reflect.MakeSlice(t, 0, endIndex-startIndex)
	for i := startIndex; i < endIndex; i++ {
		reflectSlice = reflect.Append(reflectSlice, v.Index(i))
	}
	return reflectSlice.Interface()
}

func GetStartIndex(page int, itemsPerPage int) int {
	if page <= 0 {
		page = 1
	}
	return (page - 1) * GetSize(itemsPerPage)
}

func GetSize(itemsPerPage int) int {
	if itemsPerPage <= 0 {
		itemsPerPage = 10
	}
	return itemsPerPage
}

type SortBy struct {
	Field string
	Desc  bool
}

func ParseSortByFiled(sortBy string) []SortBy {
	var result = make([]SortBy, 0)

	if sortBy == "" {
		return result
	}

	for _, sort := range strings.Split(sortBy, ",") {
		s := strings.Fields(sort)
		if len(s) != 2 {
			log.Warn("invalid sortBy: ", zap.String("sortBy", sort))
			return []SortBy{}
		}
		result = append(result, SortBy{Field: s[0], Desc: "desc" == s[1]})
	}
	return result
}

func GetSingleOrderByFieldName(sortBy string) string {
	result := ParseSortByFiled(sortBy)
	if len(result) >= 1 {
		return result[0].Field
	}
	return ""
}

func GetSingleOrderByDirection(sortBy string) bool {
	result := ParseSortByFiled(sortBy)
	if len(result) >= 1 {
		return result[0].Desc
	}
	return true
}

func GetGORMSortByExpression(sortBy string, desc bool) string {
	direction := "desc"
	if !desc {
		direction = "asc"
	}
	return fmt.Sprintf(" %s %s", sortBy, direction)
}

type Event struct {
	UserID         string
	Payload        interface{}
	OperationType  entity.OperationType
	ManagementType entity.ManagementType
	CostTime       int64
	Result         string
}

func AddEventAndLogError(db *gorm.DB, event *Event) {
	if err := AddEvent(db, event); err != nil {
		log.Warn("add operation log failed.", zap.String("value", fmt.Sprintf("%v", event)))
	}
}

func AddEvent(db *gorm.DB, event *Event) error {
	content, ok := event.Payload.(string)
	if !ok {
		jsonContent, err := json.Marshal(event.Payload)
		if err != nil {
			log.Error("failed to marshal request body", zap.Error(err))
			return errors.NewBadRequest("failed to marshal request body")
		}
		content = string(jsonContent)
	}
	// record operate event
	operateEvent := entity.OperateEvent{
		ManagementType: string(event.ManagementType),
		User:           event.UserID,
		OperationType:  string(event.OperationType),
		OperationTime:  time.Now().Unix(),
		Cost:           event.CostTime,
		Result:         event.Result,
	}
	if len(content) != 0 {
		operateEvent.Content = string(content)
	}
	if len(event.Result) == 0 {
		operateEvent.Result = "OK"
	}

	err := db.Create(&operateEvent).Error
	if err != nil {
		log.Error("ad operate event failed", zap.Error(err))
		return errors.NewInternalError(err)
	}
	return nil
}

func MakeNilSlice2EmptySlice(slice interface{}) interface{} {
	t := reflect.TypeOf(slice)
	if t.Kind() != reflect.Slice {
		panic("slice wanted")
	}
	if reflect.ValueOf(slice).IsNil() {
		return reflect.MakeSlice(t, 0, 0).Interface()
	}
	return slice
}

func GetAPIUsername(ctx context.Context) string {
	operator, found := request.UserFrom(ctx)
	if !found {
		return ""
		panic("username is not found")
	}
	return operator.GetName()
}
