package core

import (
	"github.com/juju/errors"
	"github.com/ngaut/log"
	"github.com/pingcap/schrodinger/manager/template"
	"github.com/pingcap/schrodinger/pkg/storage"
	"github.com/pingcap/schrodinger/pkg/types"
	"github.com/pingcap/schrodinger/util"
)

// StartExperiment starts experiment
func (mgr *Manager) StartExperiment(experimentID int64) error {
	experiment, exist := mgr.storage.GetExperimentProcess(experimentID)
	if !exist {
		return errors.NotFoundf("experiment %d", experimentID)
	}

	return errors.Trace(mgr.experimentCtrl.StartExperiment(experiment))
}

// CancelExperiment cancels experiments
func (mgr *Manager) CancelExperiment(experimentID int64) error {
	experiment, exist := mgr.storage.GetExperimentProcess(experimentID)
	if !exist {
		return errors.NotFoundf("experiment %d", experimentID)
	}

	return errors.Trace(mgr.experimentCtrl.CancelExperiment(experiment.BoxID))
}

// DestroyExperiment destroys experiment
func (mgr *Manager) DestroyExperiment(experimentID int64) error {
	experiment, exist := mgr.storage.GetExperimentProcess(experimentID)
	if !exist {
		return errors.NotFoundf("experiment %d", experimentID)
	}

	if experiment.IsEnd() {
		return errors.NotSupportedf("experiment %d aready end", experimentID)
	}

	return errors.Trace(mgr.experimentCtrl.DestroyExperiment(experimentID))
}

// AnnotateExperiment annotates expirement
func (mgr *Manager) AnnotateExperiment(experimentID int64, annotation string, author string) error {
	return errors.Trace(mgr.experimentCtrl.AnnotateExperiment(experimentID, annotation, author))
}

// GetExperimentReport gets experiment report
func (mgr *Manager) GetExperimentReport(experimentID int64, tmplName string, tmpl string) (string, error) {
	experiment, exist := mgr.storage.GetExperimentProcess(experimentID)
	if !exist {
		return "", errors.NotFoundf("experiment %d", experimentID)
	}
	report, err := template.GenerateReport(tmplName, tmpl, experiment.Report)
	if err != nil {
		return "", errors.Trace(err)
	}
	return report, nil
}

// RedoExperiment redo experiment
func (mgr *Manager) RedoExperiment(experimentID int64) error {
	mgr.Lock()
	defer mgr.Unlock()

	experiment, exist := mgr.storage.GetExperimentProcess(experimentID)
	if !exist {
		return errors.NotFoundf("experiment %d", experimentID)
	}

	newExperiment := experiment.CreateRedoProcess()

	index, err := mgr.storage.GetBoxExperimentCount(newExperiment.BoxID)
	if err != nil {
		return errors.Trace(err)
	}

	newExperiment.BoxIndex = index + 1
	newExperiment.Name = util.GenExperimentName(experiment.BoxName, newExperiment.BoxIndex)
	newExperiment.Namespace = util.GenExperimentNamespace(newExperiment.Name)

	err = mgr.storage.CreateExperimentProcess(newExperiment)
	if err != nil {
		return errors.Errorf("create experiment %s error %v", experiment.Name, err)
	}

	err = mgr.experimentCtrl.DestroyExperiment(experimentID)
	if err != nil {
		log.Errorf("stop experiment %s error %v,skip stop", experiment.Name, err)
	}

	return errors.Trace(mgr.experimentCtrl.StartExperiment(newExperiment))
}

// ReplaceCat replaces cat
func (mgr *Manager) ReplaceCat(experimentID int64, cat *types.Cat) error {
	return errors.Trace(mgr.experimentCtrl.ReplaceCat(experimentID, cat))
}

// RemoveTest removes test
func (mgr *Manager) RemoveTest(experimentID int64, testName string) error {
	return errors.Trace(mgr.experimentCtrl.RemoveTest(experimentID, testName))
}

// StopTest stops test
func (mgr *Manager) StopTest(experimentID int64, testName string) error {
	return errors.Trace(mgr.experimentCtrl.StopTest(experimentID, testName))
}

// StartTest starts test
func (mgr *Manager) StartTest(experimentID int64, testName string) error {
	return errors.Trace(mgr.experimentCtrl.StartTest(experimentID, testName))
}

// ListExperimentProcess lists experiments
func (mgr *Manager) ListExperimentProcess(conditions *storage.ExperimentProcessConditions) (types.ExperimentProcesses, int64) {
	return mgr.storage.ListExperimentProcess(conditions)
}

// GetExperimentProcess gets experiments
func (mgr *Manager) GetExperimentProcess(experimentID int64) (*types.ExperimentProcess, bool) {
	return mgr.storage.GetExperimentProcess(experimentID)
}

// GetExperimentDetail gets experiment detail infomation
func (mgr *Manager) GetExperimentDetail(experimentID int64) (*types.ExperimentReport, bool) {
	experimentProcess, exist := mgr.storage.GetExperimentProcess(experimentID)
	if !exist {
		return nil, false
	}
	return experimentProcess.Report, true
}

// ListRunningExperiments list running experiments
func (mgr *Manager) ListRunningExperiments() []*types.ExperimentProcess {
	return mgr.experimentCtrl.ListRunningExperiments()
}
