package models

import (
	"code.gitea.io/gitea/modules/timeutil"
	"errors"
	"strconv"
	"strings"
	"xorm.io/builder"
)

type ResourceQueue struct {
	ID              int64 `xorm:"pk autoincr"`
	QueueCode       string
	Cluster         string `xorm:"notnull"`
	AiCenterCode    string
	AiCenterName    string
	ComputeResource string
	AccCardType     string
	CardsTotalNum   int
	IsAutomaticSync bool
	Remark          string
	DeletedTime     timeutil.TimeStamp `xorm:"deleted"`
	CreatedTime     timeutil.TimeStamp `xorm:"created"`
	CreatedBy       int64
	UpdatedTime     timeutil.TimeStamp `xorm:"updated"`
	UpdatedBy       int64
}

func (r ResourceQueue) ConvertToRes() *ResourceQueueRes {
	return &ResourceQueueRes{
		ID:              r.ID,
		QueueCode:       r.QueueCode,
		Cluster:         r.Cluster,
		AiCenterCode:    r.AiCenterCode,
		AiCenterName:    r.AiCenterName,
		ComputeResource: r.ComputeResource,
		AccCardType:     r.AccCardType,
		CardsTotalNum:   r.CardsTotalNum,
		UpdatedTime:     r.UpdatedTime,
		Remark:          r.Remark,
	}
}

type ResourceQueueReq struct {
	QueueCode       string
	Cluster         string `binding:"Required"`
	AiCenterCode    string
	ComputeResource string `binding:"Required"`
	AccCardType     string `binding:"Required"`
	CardsTotalNum   int
	CreatorId       int64
	IsAutomaticSync bool
	Remark          string
}

func (r ResourceQueueReq) ToDTO() ResourceQueue {
	q := ResourceQueue{
		QueueCode:       r.QueueCode,
		Cluster:         r.Cluster,
		AiCenterCode:    r.AiCenterCode,
		ComputeResource: strings.ToUpper(r.ComputeResource),
		AccCardType:     strings.ToUpper(r.AccCardType),
		CardsTotalNum:   r.CardsTotalNum,
		IsAutomaticSync: r.IsAutomaticSync,
		Remark:          r.Remark,
		CreatedBy:       r.CreatorId,
		UpdatedBy:       r.CreatorId,
	}
	if r.Cluster == OpenICluster {
		if r.AiCenterCode == AICenterOfCloudBrainOne {
			q.AiCenterName = "云脑一"
		} else if r.AiCenterCode == AICenterOfCloudBrainTwo {
			q.AiCenterName = "云脑二"
		} else if r.AiCenterCode == AICenterOfChengdu {
			q.AiCenterName = "启智成都智算"
		}
	}
	return q
}

type SearchResourceQueueOptions struct {
	ListOptions
	Cluster         string
	AiCenterCode    string
	ComputeResource string
	AccCardType     string
}

type ResourceQueueListRes struct {
	TotalSize int64
	List      []*ResourceQueueRes
}

type ResourceQueueCodesRes struct {
	ID           int64
	QueueCode    string
	Cluster      string
	AiCenterCode string
	AiCenterName string
}

func (ResourceQueueCodesRes) TableName() string {
	return "resource_queue"
}

type ResourceAiCenterRes struct {
	AiCenterCode string
	AiCenterName string
}

type GetQueueCodesOptions struct {
	Cluster string
}

func NewResourceQueueListRes(totalSize int64, list []ResourceQueue) *ResourceQueueListRes {
	resList := make([]*ResourceQueueRes, len(list))
	for i, v := range list {
		resList[i] = v.ConvertToRes()
	}
	return &ResourceQueueListRes{
		TotalSize: totalSize,
		List:      resList,
	}
}

type ResourceQueueRes struct {
	ID              int64
	QueueCode       string
	Cluster         string
	AiCenterCode    string
	AiCenterName    string
	ComputeResource string
	AccCardType     string
	CardsTotalNum   int
	UpdatedTime     timeutil.TimeStamp
	Remark          string
}

func InsertResourceQueue(queue ResourceQueue) (int64, error) {
	return x.Insert(&queue)
}

func UpdateResourceQueueById(queueId int64, queue ResourceQueue) (int64, error) {
	return x.ID(queueId).Update(&queue)
}
func UpdateResourceCardsTotalNum(queueId int64, queue ResourceQueue) (int64, error) {
	return x.ID(queueId).Cols("cards_total_num", "remark").Update(&queue)
}

func SearchResourceQueue(opts SearchResourceQueueOptions) (int64, []ResourceQueue, error) {
	var cond = builder.NewCond()
	if opts.Page <= 0 {
		opts.Page = 1
	}
	if opts.Cluster != "" {
		cond = cond.And(builder.Eq{"cluster": opts.Cluster})
	}
	if opts.AiCenterCode != "" {
		cond = cond.And(builder.Eq{"ai_center_code": opts.AiCenterCode})
	}
	if opts.ComputeResource != "" {
		cond = cond.And(builder.Eq{"compute_resource": opts.ComputeResource})
	}
	if opts.AccCardType != "" {
		cond = cond.And(builder.Eq{"acc_card_type": opts.AccCardType})
	}
	n, err := x.Where(cond).Unscoped().Count(&ResourceQueue{})
	if err != nil {
		return 0, nil, err
	}

	r := make([]ResourceQueue, 0)
	err = x.Where(cond).Desc("id").Limit(opts.PageSize, (opts.Page-1)*opts.PageSize).Unscoped().Find(&r)
	if err != nil {
		return 0, nil, err
	}
	return n, r, nil
}

func GetResourceQueueCodes(opts GetQueueCodesOptions) ([]*ResourceQueueCodesRes, error) {
	cond := builder.NewCond()
	if opts.Cluster != "" {
		cond = cond.And(builder.Eq{"cluster": opts.Cluster})
	}
	cond = cond.And(builder.Or(builder.IsNull{"deleted_time"}, builder.Eq{"deleted_time": 0}))
	r := make([]*ResourceQueueCodesRes, 0)
	err := x.Where(cond).OrderBy("cluster desc,ai_center_code asc").Find(&r)
	if err != nil {
		return nil, err
	}
	return r, nil
}

func GetResourceQueue(r *ResourceQueue) (*ResourceQueue, error) {
	has, err := x.Get(r)
	if err != nil {
		return nil, err
	} else if !has {
		return nil, nil
	}
	return r, nil
}

func ParseComputeResourceFormGrampus(grampusDeviceKind string) string {
	t := strings.Split(grampusDeviceKind, "/")
	if len(t) < 2 {
		return ""
	}
	return strings.ToUpper(t[1])
}

type MemSize struct {
	Sizes []string
	Hex   int
}

var memSize = MemSize{Sizes: []string{"K", "M", "G", "T", "P", "E"}, Hex: 1000}
var iMemSize = MemSize{Sizes: []string{"Ki", "Mi", "Gi", "Ti", "Pi", "Ei"}, Hex: 1024}

func MatchMemSize(memSize MemSize, val string) (int, float32, error) {
	for i, v := range memSize.Sizes {
		if strings.HasSuffix(val, v) {
			s := strings.TrimSuffix(val, v)
			f, err := strconv.ParseFloat(s, 32)
			if err != nil {
				return 0, 0, err
			}
			return i, float32(f), nil
		}
	}
	return -1, 0, nil
}

//TransferMemSize transfer oldValue format from old index to new index
//eg:  memSize.Sizes = []string{"M", "G", "T", "P", "E"}, oldValue = 10 , oldIndex = 1 , newIndex = 0. it means transfer 10G to 10000M
//so it returns 10000
func TransferMemSize(memSize MemSize, oldValue float32, oldIndex int, newIndex int) float32 {
	diff := oldIndex - newIndex
	r := oldValue
	if diff > 0 {
		r = oldValue * float32(diff) * float32(memSize.Hex)
	} else if diff < 0 {
		r = oldValue / float32(-1*diff) / float32(memSize.Hex)
	}
	return r
}

//ParseMemSize find the memSize which matches value's format,and parse the number from value
func ParseMemSize(value string, memSize MemSize, newIndex int) (bool, float32, error) {
	index, r, err := MatchMemSize(memSize, value)
	if err != nil {
		return false, 0, err
	}
	if index < 0 {
		return false, 0, nil
	}
	return true, TransferMemSize(memSize, r, index, newIndex), nil
}

func ParseMemSizeFromGrampus(grampusMemSize string) (float32, error) {
	if grampusMemSize == "" {
		return 0, nil
	}
	memflag, memResult, err := ParseMemSize(grampusMemSize, memSize, 2)
	if err != nil {
		return 0, err
	}
	if memflag {
		return memResult, nil
	}

	iMemFlag, imemResult, err := ParseMemSize(grampusMemSize, iMemSize, 2)
	if err != nil {
		return 0, err
	}
	if iMemFlag {
		return imemResult, nil
	}
	return 0, errors.New("grampus memSize format error")
}

func SyncGrampusQueues(updateList []ResourceQueue, insertList []ResourceQueue, existIds []int64) error {
	sess := x.NewSession()
	var err error
	defer func() {
		if err != nil {
			sess.Rollback()
		}
		sess.Close()
	}()

	//delete queues that no longer exists
	deleteQueueIds := make([]int64, 0)
	queueCond := builder.NewCond()
	queueCond = queueCond.And(builder.NotIn("resource_queue.id", existIds)).And(builder.Eq{"resource_queue.cluster": C2NetCluster})
	if err := sess.Cols("resource_queue.id").Table("resource_queue").
		Where(queueCond).Find(&deleteQueueIds); err != nil {
		return err
	}

	if len(deleteQueueIds) > 0 {
		if _, err = sess.In("id", deleteQueueIds).Update(&ResourceQueue{Remark: "自动同步时被下架"}); err != nil {
			return err
		}
		if _, err = sess.In("id", deleteQueueIds).Delete(&ResourceQueue{}); err != nil {
			return err
		}

		//delete specs and scene that no longer exists
		deleteSpcIds := make([]int64, 0)
		if err := sess.Cols("resource_specification.id").Table("resource_specification").
			In("queue_id", deleteQueueIds).Find(&deleteSpcIds); err != nil {
			return err
		}
		if len(deleteSpcIds) > 0 {
			if _, err = sess.In("id", deleteSpcIds).Update(&ResourceSpecification{Status: SpecOffShelf}); err != nil {
				return err
			}
		}

	}

	//update exists specs
	if len(updateList) > 0 {
		for _, v := range updateList {
			if _, err = sess.ID(v.ID).Update(&v); err != nil {
				return err
			}
		}

	}

	//insert new specs
	if len(insertList) > 0 {
		if _, err = sess.Insert(insertList); err != nil {
			return err
		}
	}

	return sess.Commit()
}

func GetResourceAiCenters() ([]ResourceAiCenterRes, error) {
	r := make([]ResourceAiCenterRes, 0)

	err := x.SQL("SELECT  t.ai_center_code, t.ai_center_name FROM (SELECT DISTINCT ai_center_code, ai_center_name,cluster FROM resource_queue WHERE (deleted_time IS NULL OR deleted_time=0)) t ORDER BY cluster desc,ai_center_code asc").Find(&r)
	if err != nil {
		return nil, err
	}
	return r, nil
}
