package test

import (
	"context"
	"sync"

	"github.com/juju/errors"
	"github.com/ngaut/log"
	"github.com/pingcap/schrodinger/manager/operator/proc/k8s"
	"github.com/pingcap/schrodinger/manager/test"
	"github.com/pingcap/schrodinger/pkg/storage"
	"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

	tests    map[int64]*test.Test
	operator test.Operator
}

// NewController creates controller
func NewController(s *storage.Storage, operator test.Operator) *Controller {
	ctx, shutdown := context.WithCancel(context.Background())
	ctrl := &Controller{
		ctx:      ctx,
		shutdown: shutdown,
		storage:  s,
		tests:    make(map[int64]*test.Test),
		operator: operator,
	}
	return ctrl
}

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

// StartTest starts test
func (ctrl *Controller) StartTest(testProcess *types.TestProcess) (*test.Test, error) {
	ctrl.Lock()

	t := test.NewTest(testProcess, ctrl.storage, ctrl.operator)

	ctrl.tests[testProcess.ID] = t

	ctrl.Unlock()

	isStarted, err := ctrl.operator.CheckProcIsStarted(t.Proc)
	if err != nil {
		return t, errors.Trace(err)
	}

	if isStarted {
		t.SetState(types.TestStateRunning)
		proc, err := ctrl.operator.GetProcByName(t.Namespace(), t.Name())
		if err != nil {
			return t, errors.Trace(err)
		}
		t.Proc = mergeProc(t.Proc, proc)

		t.SyncStatus(ctrl.ctx)
		return t, nil
	}

	return t, errors.Trace(t.Start(ctrl.ctx))
}

// NewTest just return an new instance of test
func (ctrl *Controller) NewTest(process *types.TestProcess) *test.Test {
	return test.NewTest(process, ctrl.storage, ctrl.operator)
}

// StopTest stops test
func (ctrl *Controller) StopTest(testID int64) error {
	ctrl.RLock()

	t, exist := ctrl.tests[testID]
	if !exist {
		ctrl.RUnlock()
		return errors.NotFoundf("test %d", testID)
	}

	ctrl.RUnlock()
	return errors.Trace(t.Stop(ctrl.ctx))
}

func mergeProc(p1, p2 *k8s.Proc) *k8s.Proc {
	if p2.Pod != nil {
		p1.Pod = p2.Pod
	}

	p1.State = p2.State

	return p1
}
