package dtask

import (
	"encoding/gob"
	"errors"
	"reflect"
	"time"

	"git.rosy.net.cn/baseapi/utils"
	"git.rosy.net.cn/jx-callback/business/model"
	"git.rosy.net.cn/jx-callback/business/model/dao"
	"git.rosy.net.cn/jx-callback/business/partner"
	"git.rosy.net.cn/jx-callback/globals/refutil"
)

var (
	CurMan *DurableTaskMan
)

type DurableTaskMan struct {
	objCreator func(objHint string) interface{}
	tasks      map[string]*DurableTask
}

type DurableTask struct {
	cmdChan   chan string
	data      *model.DurableTask
	itemCount int
	items     []*model.DurableTaskItem
}

func Init(objCreator func(objHint string) interface{}, interfaceTypes ...interface{}) {
	if CurMan == nil {
		if objCreator == nil {
			objCreator = defObjCreator
		}
		CurMan = &DurableTaskMan{
			tasks:      make(map[string]*DurableTask),
			objCreator: objCreator,
		}
		for _, v := range interfaceTypes {
			gob.Register(v)
		}
	}
}

func (m *DurableTaskMan) LoadPendingTask() (err error) {
	db := dao.GetDB()
	tasks := make([]*model.DurableTask, 0)
	if err = dao.GetEntitiesByKV(db, &tasks, utils.Params2Map(model.FieldStatus, 0), true); err == nil {
		for _, task := range tasks {
			dTask := &DurableTask{
				data: task,
			}
			m.tasks[task.TaskID] = dTask
			return dao.GetEntitiesByKV(db, &dTask.items, utils.Params2Map(model.FieldStatus, 0), true)
		}
	}
	return err
}

func (m *DurableTaskMan) Start() error {
	for _, task := range m.tasks {
		m.StartTask(task.data.TaskID)
	}
	return nil
}

func (m *DurableTaskMan) AddTask(desc, createdBy string) (taskID string, err error) {
	task := &DurableTask{}
	task.data = &model.DurableTask{
		TaskID:      utils.GetUUID(),
		Description: desc,
		CreatedBy:   createdBy,
		Status:      0,
		FinishedAt:  utils.DefaultTimeValue,
	}
	if err = dao.CreateEntity(nil, task.data); err == nil {
		m.tasks[task.data.TaskID] = task
		return task.data.TaskID, nil
	}
	return "", err
}

func (m *DurableTaskMan) AddItem(taskID, objHint string, funcName string, params ...interface{}) (err error) {
	d := m.tasks[taskID]
	d.data.TotalItem++
	item := &model.DurableTaskItem{
		ObjHint:    objHint,
		FuncName:   funcName,
		TaskID:     d.data.TaskID,
		TaskIndex:  d.data.TotalItem,
		FinishedAt: utils.DefaultTimeValue,
	}
	if item.Params, err = refutil.SerializeData(params); err == nil {
		db := dao.GetDB()
		if err = dao.CreateEntity(db, item); err == nil {
			_, err = dao.UpdateEntity(db, d.data, "TotalItem")
			d.items = append(d.items, item)
		}
	}
	d.data.TotalItem--
	return err
}

func (m *DurableTaskMan) StartTask(taskID string) error {
	d := m.tasks[taskID]
	if d.cmdChan == nil {
		d.cmdChan = make(chan string)
		utils.CallFuncAsync(func() {
			failedItemCount := 0
			for _, taskItem := range d.items {
				if taskItem.Status == 0 {
					obj := m.objCreator(taskItem.ObjHint)
					objValue := reflect.ValueOf(obj)
					func2Call := objValue.MethodByName(taskItem.FuncName)
					params := []interface{}{}
					refutil.DeSerializeData(taskItem.Params, &params)
					valueParams := make([]reflect.Value, len(params))

					for k, v := range params {
						valueParams[k] = reflect.ValueOf(v)
					}
					if func2Call.Call(valueParams)[0].IsNil() {
						taskItem.Status = 1
						taskItem.FinishedAt = time.Now()
						taskItem.UpdatedAt = taskItem.FinishedAt
						dao.UpdateEntity(nil, taskItem, "Status", "FinishedAt", "UpdatedAt")
					} else {
						failedItemCount++
					}
				}
			}
			if failedItemCount == 0 {
				d.data.Status = 1
				d.data.FinishedAt = time.Now()
				d.data.UpdatedAt = d.data.FinishedAt
				dao.UpdateEntity(nil, d.data, "Status", "FinishedAt", "UpdatedAt")
			}
		})
		return nil
	}
	return errors.New("任务已经启动")
}

func defObjCreator(objHint string) interface{} {
	return partner.GetPurchasePlatformFromVendorID(0)
}
