package box

import (
	"context"
	"sync"

	"github.com/juju/errors"
	"github.com/ngaut/log"
	"github.com/pingcap/schrodinger/manager/controller/experiment"
	"github.com/pingcap/schrodinger/pkg/storage"
	"github.com/pingcap/schrodinger/pkg/trigger"
	"github.com/pingcap/schrodinger/pkg/types"
)

// Controller controls missions and tasks
type Controller struct {
	sync.RWMutex
	wg sync.WaitGroup

	ctx           context.Context
	shutdown      context.CancelFunc
	storage       *storage.Storage
	experimentCtl *experiment.Controller
	trigger       *trigger.Trigger
}

// NewController creates controller
func NewController(
	s *storage.Storage,
	experimentCtl *experiment.Controller,
) *Controller {
	ctx, shutdown := context.WithCancel(context.Background())
	ctrl := &Controller{
		ctx:           ctx,
		shutdown:      shutdown,
		storage:       s,
		experimentCtl: experimentCtl,
	}

	ctrl.trigger = trigger.New(ctx, s)
	return ctrl
}

func (ctrl *Controller) close() {
	log.Infof("stop box controller")
	ctrl.shutdown()
	ctrl.wg.Wait()
}

// SetBox sets box
func (ctrl *Controller) SetBox(box *types.Box) error {
	ctrl.Lock()
	defer ctrl.Unlock()
	log.Infof("set box %s(%d)", box.Name, box.ID)

	// first delete rules for trigger box for create or update
	jobID := box.ID
	err := ctrl.trigger.DelJob(jobID)
	if err != nil {
		log.Infof("clean job error %v, never mind", err)
	}

	// Stopped box should not trigger
	if !box.IsOnline() {
		return nil
	}

	rules := parserRules(box.Rules, box)

	job := &trigger.Job{
		ID: box.ID,
		Fn: func() {
			err := triggerBox(ctrl, box.ID)
			if err != nil {
				log.Errorf("trigger box %d error %v", box.ID, err)
			}
			return
		},
		Rules: rules,
	}
	return errors.Trace(ctrl.trigger.AddJob(job))
}

// StopBox stops box
// stop did not delete box in controller. just delete trigger rule for box
func (ctrl *Controller) StopBox(boxID int64) error {
	ctrl.Lock()
	defer ctrl.Unlock()

	log.Infof("stop box %d", boxID)
	box, exist := ctrl.storage.GetBox(boxID)
	if !exist {
		log.Warningf("[box %d] not exist", boxID)
		return errors.NotFoundf("box %d", boxID)
	}
	if !box.IsOnline() {
		log.Debugf("[box %d] is not online, did not need stop", boxID)
		return nil
	}

	return errors.Trace(ctrl.trigger.DelJob(boxID))
}

// Trigger trigger box immediately
func (ctrl *Controller) Trigger(boxID int64) error {
	log.Infof("trigger box %d", boxID)
	return errors.Trace(triggerBox(ctrl, boxID))
}
