package core

import (
	"fmt"

	"github.com/juju/errors"
	"github.com/ngaut/log"
	"github.com/pingcap/schrodinger/config"
	"github.com/pingcap/schrodinger/pkg/test_client"
	"github.com/pingcap/schrodinger/pkg/types"
)

// CreateTestTemplate creates test template
func (mgr *Manager) CreateTestTemplate(c *types.TestTemplate) error {
	c.Status = types.TestTemplateNew
	if err := mgr.storage.CreateTestTemplate(c); err != nil {
		log.Errorf("store case template %+v error %v", c, err)
		return errors.Errorf("store case template error %v", err)
	}

	if err := mgr.setTestTemplate(c); err != nil {
		log.Errorf("build case error %v", err)
		return errors.Errorf("build case error %v", err)
	}

	return nil
}

// UpdateTestTemplate updates test template
func (mgr *Manager) UpdateTestTemplate(c *types.TestTemplate) error {
	c.Status = types.TestTemplateNew
	if err := mgr.storage.UpdateTestTemplate(c); err != nil {
		log.Errorf("store case template %+v error %v", c, err)
		return errors.Errorf("store case template error %v", err)
	}

	if err := mgr.setTestTemplate(c); err != nil {
		log.Errorf("build case error %v", err)
		return errors.Errorf("build case error %v", err)
	}

	return nil
}

// GetTestTemplateByName returns the specified case template by name
func (mgr *Manager) GetTestTemplateByName(name string) ([]*types.TestTemplate, error) {
	ct, err := mgr.storage.GetTestTemplateByName(name)
	if err != nil {
		return nil, errors.Trace(err)
	}

	if len(ct) == 0 {
		return nil, errors.NotFoundf("case template %s", name)
	}
	return ct, nil
}

// GetTestTemplateByID returns the specified case template by id
func (mgr *Manager) GetTestTemplateByID(ID int64) (*types.TestTemplate, error) {
	ct, err := mgr.storage.GetTestTemplate(ID)
	if err != nil {
		return nil, errors.Trace(err)
	}

	return ct, nil
}

// GetTestTemplateByNameAndLatest returns the specified case template by name and latest
func (mgr *Manager) GetTestTemplateByNameAndLatest(name string) (*types.TestTemplate, error) {
	ct, err := mgr.storage.GetTestTemplateByNameAndLatest(name)
	if err != nil {
		return nil, errors.Trace(err)
	}
	return ct, nil
}

// GetTestTemplateByNameAndPR returns the specified case template by name and PR id
func (mgr *Manager) GetTestTemplateByNameAndPR(name string, pr string) (*types.TestTemplate, error) {
	ct, err := mgr.storage.GetTestTemplateByNameAndPR(name, pr)
	if err != nil {
		return nil, errors.Trace(err)
	}
	return ct, nil
}

// DeleteTestTemplate deletes test template
func (mgr *Manager) DeleteTestTemplate(name string, id int64) {
	mgr.storage.DeleteTestTemplate(name, id)
}

// ListTestTemplate lists all case template
func (mgr *Manager) ListTestTemplate() types.TestTemplates {
	return mgr.storage.ListTestTemplate()
}

// GetTestTemplatesByTag returns the specified test templates by tag
func (mgr *Manager) GetTestTemplatesByTag(tag string) types.TestTemplates {
	return mgr.storage.GetTestTemplatesByTag(tag)
}

func (mgr *Manager) setTestTemplate(c *types.TestTemplate) error {
	defer func() {
		if err := mgr.storage.UpdateTestTemplate(c); err != nil {
			log.Errorf("case template stored failed (%s/%d): %s", c.Name, c.ID, err.Error())
		}
	}()

	if len(c.Source.URL) == 0 {
		c.Status = types.TestTemplateError
		c.ErrorInfo = "source url is required"
		return errors.New(c.ErrorInfo)
	}

	c.Status = types.TestTemplateDone
	return nil
}

// GetTestConfig returns the specified case config by case ID
func (mgr *Manager) GetTestConfig(id int64) (*client.Config, error) {
	cp, ok := mgr.storage.GetTestProcess(id)
	if !ok {
		return nil, errors.NotFoundf("testProcess %d", id)
	}

	experiment, exist := mgr.storage.GetExperimentProcess(cp.ExperimentID)

	if !exist {
		return nil, errors.NotFoundf("experimentProcess %d", cp.ExperimentID)
	}

	exper := &client.ExperimentInfo{
		ID:                     experiment.ID,
		BoxID:                  experiment.BoxID,
		Timeout:                experiment.Timeout,
		Failpoints:             mgr.storage.ListFailpoints(),
		DaemonPort:             config.GetGlobalConfig().Daemon.Port,
		Name:                   experiment.Name,
		Namespace:              experiment.Namespace,
		SlackChannelForError:   experiment.Config.Misc.SlackChannelForError,
		SlackChannelForSuccess: experiment.Config.Misc.SlackChannelForSuccess,
	}

	var tests []*client.TestInfo
	for _, test := range experiment.Tests {
		t := &client.TestInfo{
			ID:    test.ID,
			Name:  test.Name,
			State: test.Status,
		}

		if test.Report != nil {
			t.IP = test.Report.PodIP
			t.HostIP = test.Report.HostIP
		}

		tests = append(tests, t)
	}

	managerConf := &client.ManagerConfig{
		AgentPort:       config.GetGlobalConfig().Cat.AgentPort,
		TidbServicePort: config.GetGlobalConfig().Cat.TidbServicePort,
		TikvServicePort: config.GetGlobalConfig().Cat.TikvServicePort,
		PdServicePort:   config.GetGlobalConfig().Cat.PdServicePort,
		PdDataDir:       config.GetGlobalConfig().Cat.PdDataDir,
		TikvDataDir:     config.GetGlobalConfig().Cat.TikvDataDir,
		DataDir:         config.GetGlobalConfig().Cat.TikvDataDir,
	}

	self := &client.TestInfo{
		ID:    id,
		Name:  cp.Name,
		State: cp.Status,
	}

	if cp.Report != nil {
		self.IP = cp.Report.PodIP
		self.HostIP = cp.Report.HostIP
	}

	conf := &client.Config{
		Manager:    managerConf,
		Self:       self,
		Experiment: exper,
		Tests:      tests,
		LogService: &config.GetGlobalConfig().LogService,
	}

	if experiment.Config.WithoutCat {
		return conf, nil
	}

	catReport := experiment.Cat.Report

	if catReport == nil || catReport.Cluster == nil {
		return &client.Config{
			Manager:    managerConf,
			Self:       self,
			Experiment: exper,
			Tests:      tests,
			Cat: &client.CatInfo{
				Config: experiment.Cat.Config,
			},
		}, nil
	}

	cat := &client.CatInfo{
		ID:             catReport.ID,
		Namespace:      catReport.Namespace,
		PrometheusAddr: fmt.Sprintf("%s:%d", catReport.Cluster.PrometheusService.ClusterIP, catReport.Cluster.PrometheusService.ClusterPort),
		LBService:      fmt.Sprintf("%s:%d", catReport.Cluster.TidbService.ClusterIP, catReport.Cluster.TidbService.ClusterPort),
		PdService:      fmt.Sprintf("%s:%d", catReport.Cluster.PdService.ClusterIP, catReport.Cluster.PdService.ClusterPort),

		PDs:    genPodInfo(catReport.Cluster.PdStatus, client.PD),
		TiDBs:  genPodInfo(catReport.Cluster.TidbStatus, client.TiDB),
		TiKVs:  genPodInfo(catReport.Cluster.TikvStatus, client.TiKV),
		Config: experiment.Cat.Config,
	}

	conf.Cat = cat

	return conf, nil
}

// SetTestResult sets test result
func (mgr *Manager) SetTestResult(tr *types.TestResult) error {
	return errors.Trace(mgr.storage.SetTestResult(tr))
}

// GetTestResult returns the specified test result by id
func (mgr *Manager) GetTestResult(id int64) (*types.TestResult, error) {
	tr, exist := mgr.storage.GetTestResult(id)
	if !exist {
		return nil, errors.NotFoundf("test result %d", id)
	}

	return tr, nil
}

// GetTestResultByExperimentWithTestName returns the specified test result by experiment and test name
func (mgr *Manager) GetTestResultByExperimentWithTestName(experimentID int64, testName string) (*types.TestResult, error) {
	tr, exist := mgr.storage.GetTestResultByExperimentWithTestName(experimentID, testName)
	if !exist {
		return nil, errors.NotFoundf("test result by experiment id %d and test name %s", experimentID, testName)
	}

	return tr, nil
}

// ListTestResultsByExperiment returns all test results by the experiment ID
func (mgr *Manager) ListTestResultsByExperiment(experimentID int64) []*types.TestResult {
	return mgr.storage.ListTestResultsByExperiment(experimentID)
}
