package storage

import (
	"database/sql"
	"math"
	"sort"
	"strings"

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

// Client is the storage interface for manager
type Client interface {
	// test template
	createTestTemplate(ct *types.TestTemplate) error
	updateTestTemplate(ct *types.TestTemplate) error
	listTestTemplate(limit, offset int64) ([]*types.TestTemplate, error)
	listTestTemplateByName(name string) ([]*types.TestTemplate, error)
	findTestTemplate(id int64) (*types.TestTemplate, error)
	findTestTemplateByNameAndLatest(name string) (*types.TestTemplate, error)
	findTestTemplateByNameAndPR(name, pr string) (*types.TestTemplate, error)
	findTestTemplateByTag(tag string, limit, offset int64) ([]*types.TestTemplate, error)
	deleteTestTemplate(id int64) error

	// test template tag
	createTag(t *types.Tag) error
	updateTag(t *types.Tag) error
	listTags() ([]*types.Tag, error)

	// test result
	createTestResult(cr *types.TestResult) error
	updateTestResult(cr *types.TestResult) error
	findTestResult(id int64) (*types.TestResult, error)
	findTestResultByBoxAndName(box, name string, limit, offset int64) ([]*types.TestResult, error)
	findTestResultByExperimentAndName(experimentID int64, name string) (*types.TestResult, error)
	findTestResultByExperiment(experimentID int64, limit, offset int64) ([]*types.TestResult, error)
	listTestResult(limit, offset int64) ([]*types.TestResult, error)

	// Used for case client
	createTestProcess(cp *types.TestProcess) error
	updateTestProcess(cp *types.TestProcess) error
	listTestProcess(limit, offset int64) ([]*types.TestProcess, error)
	findTestProcess(id int64) (*types.TestProcess, error)
	findTestProcessByExperiment(experimentID int64, limit, offset int64) ([]*types.TestProcess, error)
	findTestProcessByExperiments(experimentIDs []int64) ([]*types.TestProcess, error)

	// cat process
	createCatProcess(cp *types.CatProcess) error
	updateCatProcess(cp *types.CatProcess) error
	listCatProcess(conditions *CatProcessConditions) ([]*types.CatProcess, int64, error)
	findCatProcess(id int64) (*types.CatProcess, error)
	findCatProcessByName(name string) (*types.CatProcess, error)
	findCatProcessByExperiment(name string) ([]*types.CatProcess, error)
	findCatProcessByExperiments(names []string) ([]*types.CatProcess, error)

	// experiment process
	createExperimentProcess(experiment *types.ExperimentProcess) error
	updateExperimentProcess(experiment *types.ExperimentProcess) error
	listExperimentProcess(conditions *ExperimentProcessConditions) ([]*types.ExperimentProcess, int64, error)
	findExperimentProcess(id int64) (*types.ExperimentProcess, error)
	deleteExperimentProcess(id int64) error

	// box
	createBox(cp *types.Box) error
	updateBox(cp *types.Box) error
	listBox(conditions *BoxConditions) ([]*types.Box, int64, error)
	findBoxByID(ID int64) (*types.Box, error)
	findBoxByName(name string) (*types.Box, error)
	findBoxByTag(tag string) ([]*types.Box, error)

	// failpoint
	setFailpoint(fail *types.Failpoint) error
	listFailpoint() ([]*types.Failpoint, error)

	// endpoint
	createEndpoint(*types.Endpoint) error
	updateEndpoint(*types.Endpoint) error
	deleteEndpoint(id int64) error
	listEndpoint() ([]*types.Endpoint, error)
	findEndpointByID(ID int64) (*types.Endpoint, error)

	// github trigger info
	setGithubTriggerInfo(*types.GithubTriggerInfo) error
	listGithubTriggerInfo() ([]*types.GithubTriggerInfo, error)
	findGithubTriggerInfoByJobID(jobID int64) (*types.GithubTriggerInfo, error)

	// immediately trigger info
	setImmediatelyTriggerInfo(*types.ImmediateTriggerInfo) error
	listImmediatelyTriggerInfo() ([]*types.ImmediateTriggerInfo, error)
	findImmediatelyTriggerInfoByJobID(jobID int64) (*types.ImmediateTriggerInfo, error)

	// notifier
	listUnAlertedExperiments() ([]*types.ExperimentProcess, error)
	updateNotifyTime(*types.ExperimentProcess) error

	setManagerSettings(*types.ManagerSettings) error
	listManagerSettings() (*types.ManagerSettings, error)

	createTiDBClusterBackup(*types.TiDBClusterBackup) error
	updateTiDBClusterBackup(*types.TiDBClusterBackup) error
	getTiDBClusterBackupByID(ID int64) (*types.TiDBClusterBackup, error)
	getTiDBClusterBackupByCatIDAndName(catID int64, name string) (*types.TiDBClusterBackup, error)
	getTiDBClusterBackupByCatID(int64) ([]*types.TiDBClusterBackup, error)

	createTiDBClusterConfigTemplate(*types.TiDBClusterConfigTemplate) error
	updateTiDBClusterConfigTemplate(*types.TiDBClusterConfigTemplate) error
	listTiDBClusterConfigTemplates() (types.TiDBClusterConfigTemplates, error)
	getTiDBClusterConfigTemplateByID(ID int64) (*types.TiDBClusterConfigTemplate, error)
	getTiDBClusterConfigTemplateByName(name string) (*types.TiDBClusterConfigTemplate, error)
	getTiDBClusterConfigTemplatesByTag(tag string) (types.TiDBClusterConfigTemplates, error)
	deleteTiDBClusterConfigTemplate(id int64) error

	createObject(*types.Object) error
	updateObject(*types.Object) error
	listObject() ([]*types.Object, error)
	findObjectByID(ID int64) (*types.Object, error)

	createAPPProcess(*types.APPProcess) error
	updateAPPProcess(*types.APPProcess) error
	listAPPProcess() ([]*types.APPProcess, error)
	getAPPProcessByID(ID int64) (*types.APPProcess, error)
	getAPPProcessByExperimentID(expID int64) ([]*types.APPProcess, error)
	getAPPProcessByNamespaceAndName(namespaceName, name string) (*types.APPProcess, error)

	createBuildTask(task *types.BuildTask) error
	updateBuildTask(task *types.BuildTask) error
	findBuildTaskByID(id int64) (*types.BuildTask, error)
	listBuildTask(conditions BuildTaskConditions) ([]*types.BuildTask, error)

	// Close closes storage
	Close() error
}

// Config is storage config for manager
type Config struct {
	Driver       string `toml:"driver"`
	Datasource   string `toml:"datasource"`
	MaxIdleConns int    `toml:"max_idle_conns"`
}

func (c *Config) isValid() bool {
	if util.IsEmpty(c.Driver) || util.IsEmpty(c.Datasource) {
		return false
	}

	return true
}

// Storage storages data
type Storage struct {
	client Client
	// for mysql client
	db *sql.DB
}

// NewStorage creates storage from Config
func NewStorage(cfg Config) (*Storage, error) {
	if !cfg.isValid() {
		return nil, errors.New("storage config is invalid")
	}

	return NewMysqlStorage(cfg)
}

// CreateTestTemplate implements storage interface
func (s *Storage) CreateTestTemplate(ct *types.TestTemplate) error {
	return errors.Trace(s.client.createTestTemplate(ct))
}

// UpdateTestTemplate implements storage interface
func (s *Storage) UpdateTestTemplate(ct *types.TestTemplate) error {
	return errors.Trace(s.client.updateTestTemplate(ct))
}

// GetTestTemplate implements storage interface
func (s *Storage) GetTestTemplate(id int64) (*types.TestTemplate, error) {
	ct, err := s.client.findTestTemplate(id)
	if err != nil {
		return nil, errors.Trace(err)
	}
	return ct, nil
}

// GetTestTemplateByName implements storage interface
func (s *Storage) GetTestTemplateByName(name string) ([]*types.TestTemplate, error) {
	ct, err := s.client.listTestTemplateByName(name)
	if err != nil {
		return nil, err
	}
	return ct, nil
}

// GetTestTemplateByNameAndLatest implements storage interface
func (s *Storage) GetTestTemplateByNameAndLatest(name string) (*types.TestTemplate, error) {
	ct, err := s.client.findTestTemplateByNameAndLatest(name)
	if err != nil {
		return nil, errors.Trace(err)
	}
	return ct, nil
}

// GetTestTemplateByNameAndPR implements storage interface
func (s *Storage) GetTestTemplateByNameAndPR(name, pr string) (*types.TestTemplate, error) {
	ct, err := s.client.findTestTemplateByNameAndPR(name, pr)
	if err != nil {
		return nil, err
	}
	return ct, nil
}

// DeleteTestTemplate deletes test template
func (s *Storage) DeleteTestTemplate(name string, ID int64) {
	if err := s.client.deleteTestTemplate(ID); err != nil {
		log.Errorf("fail to delete testTemplate %s/%d, error: %v", name, ID, err)
	}
}

// GetTestTemplatesByTag implements storage interface
func (s *Storage) GetTestTemplatesByTag(tag string) types.TestTemplates {
	cts, err := s.client.findTestTemplateByTag(tag, math.MaxInt64, 0)
	if err != nil {
		log.Errorf("fail to find testTemplate by tag %s", tag)
		return nil
	}

	return types.TestTemplates(cts)
}

// ListTestTemplate implements storage interface
func (s *Storage) ListTestTemplate() types.TestTemplates {
	cts, err := s.client.listTestTemplate(math.MaxInt64, 0)
	if err != nil {
		log.Error("fail to list testTemplate, error: %v", err)
		return nil
	}

	return types.TestTemplates(cts)
}

// CreateTag implements storage interface
func (s *Storage) CreateTag(t *types.Tag) error {
	return nil
}

// UpdateTag implements storage interface
func (s *Storage) UpdateTag(t *types.Tag) error {
	return nil
}

// GetTag implements storage interface
func (s *Storage) GetTag(id int64) (*types.Tag, bool) {
	return nil, false
}

// GetTagByNameAndKind implements storage interface
func (s *Storage) GetTagByNameAndKind(name string, kind types.TagKind) (*types.Tag, bool) {
	return nil, false
}

// DeleteTag implements storage interface
func (s *Storage) DeleteTag(id int64) error {
	return nil
}

// GetTagsByKind implements storage interface
func (s *Storage) GetTagsByKind(kind types.TagKind) (types.Tags, error) {
	return nil, nil
}

// ListTags implements storage interface
func (s *Storage) ListTags() types.Tags {
	return nil
}

// CreateBox implements storage interface
func (s *Storage) CreateBox(box *types.Box) error {
	err := s.client.createBox(box)
	if err != nil {
		return errors.Trace(err)
	}

	return nil
}

// UpdateBox implements storage interface
func (s *Storage) UpdateBox(box *types.Box) error {
	err := s.client.updateBox(box)
	if err != nil {
		return errors.Trace(err)
	}

	return nil
}

// GetBox implements storage interface
func (s *Storage) GetBox(id int64) (*types.Box, bool) {
	box, err := s.client.findBoxByID(id)
	if err != nil {
		log.Errorf("getting box by id %d fails: %s", id, errors.ErrorStack(err))
		return nil, false
	}
	return box, true
}

// DeleteBox implements storage interface
func (s *Storage) DeleteBox(id int64) error {
	box, err := s.client.findBoxByID(id)
	if err != nil {
		return errors.Trace(err)
	}
	box.State = types.BoxStateDeleted

	return s.client.updateBox(box)
}

// StopBox implements storage interface
func (s *Storage) StopBox(id int64) error {
	box, err := s.client.findBoxByID(id)
	if err != nil {
		return errors.Trace(err)
	}
	box.State = types.BoxStateOffline

	return s.client.updateBox(box)
}

// GetBoxByName implements storage interface
func (s *Storage) GetBoxByName(name string) (*types.Box, bool) {
	box, err := s.client.findBoxByName(name)
	if err != nil {
		log.Errorf("getting box by name `%s` fails: %s", name, errors.ErrorStack(err))
		return nil, false
	}
	return box, true
}

// GetBoxesByTag implements storage interface
func (s *Storage) GetBoxesByTag(tag string) types.Boxes {
	boxes, err := s.client.findBoxByTag(tag)
	if err != nil {
		log.Error("getting boxes by tag %s fails:", tag, err)
	}

	var sortedBox types.Boxes = boxes
	sort.Sort(sortedBox)
	return sortedBox
}

// ListBox implements storage interface
func (s *Storage) ListBox(conditions *BoxConditions) (types.Boxes, int64) {
	boxes, total, err := s.client.listBox(conditions)
	if err != nil {
		log.Error("listing boxes fails:", err)
	}

	var sortedBox types.Boxes = boxes
	sort.Sort(sortedBox)
	return sortedBox, total
}

// GetBoxExperimentCount gets experiment count in Box
func (s *Storage) GetBoxExperimentCount(id int64) (int64, error) {
	box, err := s.client.findBoxByID(id)
	if err != nil {
		return 0, errors.Trace(err)
	}
	return box.ExperimentCount, nil
}

// IncBoxExperimentCount increases experiments count in box and return incrased id
func (s *Storage) IncBoxExperimentCount(id int64) (int64, error) {
	box, err := s.client.findBoxByID(id)
	if err != nil {
		return 0, errors.Trace(err)
	}
	box.IncExprimentCount()

	err = s.client.updateBox(box)
	if err != nil {
		return 0, errors.Trace(err)
	}
	return box.ExperimentCount, nil
}

// CreateExperimentProcess implements storage interface
func (s *Storage) CreateExperimentProcess(ep *types.ExperimentProcess) error {
	return errors.Trace(s.client.createExperimentProcess(ep))
}

// UpdateExperimentProcess implements storage interface
func (s *Storage) UpdateExperimentProcess(ep *types.ExperimentProcess) error {
	return errors.Trace(s.client.updateExperimentProcess(ep))
}

// OmitExperimentProcess implements storage interface
func (s *Storage) OmitExperimentProcess(id int64) error {
	_, err := s.client.findExperimentProcess(id)
	if err != nil {
		log.Warningf("can't find experiment id %d in storage, error :%v", id, err)
		return nil
	}
	return errors.Trace(s.client.deleteExperimentProcess(id))
}

// GetExperimentProcess implements storage interface
func (s *Storage) GetExperimentProcess(id int64) (*types.ExperimentProcess, bool) {
	ep, err := s.client.findExperimentProcess(id)
	if err != nil {
		log.Errorf("fail to get experiment %d, error: %v", id, err)
		return nil, false
	}
	return ep, true
}

// ListExperimentProcess implements storage interface
func (s *Storage) ListExperimentProcess(conditions *ExperimentProcessConditions) (types.ExperimentProcesses, int64) {
	eps, total, err := s.client.listExperimentProcess(conditions)
	if err != nil {
		log.Errorf("fail to list experiment, error: %v", err)
		return nil, 0
	}
	return types.ExperimentProcesses(eps), total
}

// CreateCatProcess implements storage interface
func (s *Storage) CreateCatProcess(cat *types.CatProcess) error {
	return errors.Trace(s.client.createCatProcess(cat))
}

// UpdateCatProcess implements storage interface
func (s *Storage) UpdateCatProcess(cat *types.CatProcess) error {
	return errors.Trace(s.client.updateCatProcess(cat))
}

// GetCatProcess implements storage interface
func (s *Storage) GetCatProcess(id int64) (*types.CatProcess, bool) {
	cp, err := s.client.findCatProcess(id)
	if err == nil {
		return cp, true
	}
	log.Error(err)
	return nil, false
}

// GetCatProcessByName implements storage interface
func (s *Storage) GetCatProcessByName(name string) (*types.CatProcess, bool) {
	if cp, err := s.client.findCatProcessByName(name); err == nil {
		return cp, true
	}
	return nil, false
}

// ListCatProcess implements storage interface
func (s *Storage) ListCatProcess(conditions *CatProcessConditions) (types.CatProcesses, int64) {
	cps, total, err := s.client.listCatProcess(conditions)
	if err != nil {
		log.Errorf("fail to list catProcess, error: %v", err)
		return nil, 0
	}
	return types.CatProcesses(cps), total
}

// CreateTestProcess implements storage interface
func (s *Storage) CreateTestProcess(tp *types.TestProcess) error {
	return errors.Trace(s.client.createTestProcess(tp))
}

// UpdateTestProcess implements storage interface
func (s *Storage) UpdateTestProcess(tp *types.TestProcess) error {
	return errors.Trace(s.client.updateTestProcess(tp))
}

// GetTestProcess implements storage interface
func (s *Storage) GetTestProcess(id int64) (*types.TestProcess, bool) {
	tp, err := s.client.findTestProcess(id)
	if err != nil {
		log.Errorf("fail to get testProcess %d, error: %v", id, err)
		return nil, false
	}

	return tp, true
}

// ListTestProcess implements storage interface
func (s *Storage) ListTestProcess() types.TestProcesses {
	tps, err := s.client.listTestProcess(math.MaxInt64, 0)
	if err != nil {
		log.Errorf("fail to list testProcess, error: %v", err)
		return nil
	}

	return types.TestProcesses(tps)
}

// CreateEndpoint creates endpoint
func (s *Storage) CreateEndpoint(endpoint *types.Endpoint) error {
	if err := endpoint.Verify(); err != nil {
		return errors.Trace(err)
	}

	ept, found := s.detectDuplicateEndpoint(endpoint.Name, endpoint.Type)
	if found {
		return errors.Errorf("duplicate endpoint %d", ept.ID)
	}

	if err := s.client.createEndpoint(endpoint); err != nil {
		return errors.Trace(err)
	}
	return nil
}

// SetTestResult implements storage interface
// if tr.id equals to zero, will add new test reuslt or update the old test result
func (s *Storage) SetTestResult(tr *types.TestResult) error {
	box, err := s.client.findBoxByName(tr.Box)
	if err != nil {
		return errors.Trace(err)
	}

	tr.Box = box.Name

	trs, err := s.client.findTestResultByBoxAndName(tr.Box, tr.TestName, math.MaxInt64, 0)
	if err != nil && trs == nil {
		return errors.Trace(err)
	}

	if tr.ID == 0 {
		// add new test reuslt add compare it with the latest test result with the same box
		if trs != nil && len(trs) > 0 {
			tr.LastResultID = trs[0].ID
			tr.CompareTestResult(trs[0])
		}
		err = s.client.createTestResult(tr)
	} else {
		// update test reuslt
		lastTR, err := s.client.findTestResult(tr.LastResultID)
		if err != nil && lastTR != nil {
			tr.CompareTestResult(lastTR)
		}
		err = s.client.updateTestResult(tr)
	}

	if err != nil {
		return errors.Trace(err)
	}
	return nil
}

// GetTestResult implements storage interface
func (s *Storage) GetTestResult(id int64) (*types.TestResult, bool) {
	tr, err := s.client.findTestResult(id)
	if err != nil {
		log.Errorf("fail to get testResult %d, error: %v", id, err)
		return nil, false
	}
	return tr, true
}

// GetTestResultByExperimentWithTestName implements storage interface
func (s *Storage) GetTestResultByExperimentWithTestName(experimentID int64, name string) (*types.TestResult, bool) {
	tr, err := s.client.findTestResultByExperimentAndName(experimentID, name)
	if err != nil {
		log.Errorf("fail to get testResult experimentID %d name %s, error: %v", experimentID, name, err)
		return nil, false
	}
	return tr, true
}

// ListTestResultsByExperiment implements storage interface
func (s *Storage) ListTestResultsByExperiment(experimentID int64) types.TestResults {
	trs, err := s.client.findTestResultByExperiment(experimentID, math.MaxInt64, 0)
	if err != nil {
		log.Errorf("fail to testResult experimentID %d, error: %v", experimentID, err)
		return nil
	}
	return types.TestResults(trs)
}

func (s *Storage) detectDuplicateEndpoint(name string, typ string) (*types.Endpoint, bool) {
	endpoints, err := s.client.listEndpoint()
	if err != nil {
		log.Errorf("list endpoint fails: %s", errors.ErrorStack(err))
		return nil, false
	}
	for _, endpoint := range endpoints {
		if endpoint.Name == name && endpoint.Type == typ {
			return endpoint, true
		}
	}
	return nil, false
}

// UpdateEndpoint creates endpoint
func (s *Storage) UpdateEndpoint(endpoint *types.Endpoint) error {
	if err := endpoint.Verify(); err != nil {
		return errors.Trace(err)
	}

	if err := s.client.updateEndpoint(endpoint); err != nil {
		return errors.Trace(err)
	}
	return nil
}

// GetEndpoint returns endpoint by given id
func (s *Storage) GetEndpoint(id int64) (*types.Endpoint, bool) {
	endpoint, err := s.client.findEndpointByID(id)
	if err != nil {
		log.Errorf("listing endpoint by `%d` fails: %s", id, errors.ErrorStack(err))
		return nil, false
	}
	return endpoint, true
}

// DeleteEndpoint returns endpoint by given id
func (s *Storage) DeleteEndpoint(id int64) error {
	err := s.client.deleteEndpoint(id)
	if err != nil {
		return errors.Trace(err)
	}
	return nil
}

// ListEndpoint lists all endpoints
func (s *Storage) ListEndpoint() []*types.Endpoint {
	endpoints, err := s.client.listEndpoint()
	if err != nil {
		log.Error("listing endpoints fails:", err)
		return nil
	}
	return endpoints
}

// ListFailpoints implements storage interface
// TODO: refine failpoints
func (s *Storage) ListFailpoints() []*types.Failpoint {
	return nil
}

// SetFailpoint implements storage interface
func (s *Storage) SetFailpoint(fail *types.Failpoint) error {
	return nil
}

// GetFailpoint implements storage interface
func (s *Storage) GetFailpoint(name string) (*types.Failpoint, bool) {
	return nil, false
}

// SetGithubTriggerInfo sets git trigger info
func (s *Storage) SetGithubTriggerInfo(info *types.GithubTriggerInfo) error {
	err := s.client.setGithubTriggerInfo(info)

	if err != nil {
		return errors.Trace(err)
	}
	return nil
}

// GetGithubTriggerInfo gets github trigger info
func (s *Storage) GetGithubTriggerInfo(jobID int64) (*types.GithubTriggerInfo, bool) {
	info, err := s.client.findGithubTriggerInfoByJobID(jobID)
	if err != nil {
		log.Errorf("list githubTriggerInfoB `%d` fails: %s", jobID, errors.ErrorStack(err))
		return nil, false
	}
	return info, true
}

// ListGithubTriggerInfo implements storage interface
func (s *Storage) ListGithubTriggerInfo() []*types.GithubTriggerInfo {
	infos, err := s.client.listGithubTriggerInfo()
	if err != nil {
		log.Errorf("list githubTriggerInfo fails: %s", errors.ErrorStack(err))
		return nil
	}
	return infos
}

// SetImmediatelyTriggerInfo implements storage interface
func (s *Storage) SetImmediatelyTriggerInfo(info *types.ImmediateTriggerInfo) error {
	err := s.client.setImmediatelyTriggerInfo(info)

	if err != nil {
		return errors.Trace(err)
	}
	return nil
}

// GetImmediatelyTriggerInfo implements storage interface
func (s *Storage) GetImmediatelyTriggerInfo(jobID int64) (*types.ImmediateTriggerInfo, bool) {
	info, err := s.client.findImmediatelyTriggerInfoByJobID(jobID)
	if err != nil {
		log.Errorf("list immediatelyTriggerInfoB `%d` fails: %s", jobID, errors.ErrorStack(err))
		return nil, false
	}
	return info, true
}

// ListImmediatelyTriggerInfo implements storage interface
func (s *Storage) ListImmediatelyTriggerInfo() []*types.ImmediateTriggerInfo {
	infos, err := s.client.listImmediatelyTriggerInfo()
	if err != nil {
		log.Errorf("list immediatelyTriggerInfo fails: %s", errors.ErrorStack(err))
		return nil
	}
	return infos
}

// ListUnAlertedExperiments implements storage interface
func (s *Storage) ListUnAlertedExperiments() []*types.ExperimentProcess {
	eps, err := s.client.listUnAlertedExperiments()
	if err != nil {
		log.Errorf("list unAlertedExperiments fails: %s", errors.ErrorStack(err))
		return nil
	}
	return eps
}

// UpdateNotifyTime updates last_notify_time in table
func (s *Storage) UpdateNotifyTime(ep *types.ExperimentProcess) {
	err := s.client.updateNotifyTime(ep)
	if err != nil {
		log.Errorf("update notifyTime fails: %s", errors.ErrorStack(err))
	}
}

// CreateObject implements storage interface
func (s *Storage) CreateObject(object *types.Object) error {
	err := s.client.createObject(object)
	if err != nil {
		return errors.Trace(err)
	}
	return nil
}

// UpdateObject implements storage interface
func (s *Storage) UpdateObject(object *types.Object) error {
	err := s.client.updateObject(object)
	if err != nil {
		return errors.Trace(err)
	}
	return nil
}

// GetObject implements storage interface
func (s *Storage) GetObject(id int64) (*types.Object, bool) {
	object, err := s.client.findObjectByID(id)
	if err != nil {
		log.Errorf("list object by `%d` fails: %s", id, errors.ErrorStack(err))
		return nil, false
	}
	return object, true
}

// ListObject implements storage interface
func (s *Storage) ListObject() []*types.Object {
	objects, err := s.client.listObject()
	if err != nil {
		log.Errorf("list objects fails: %s", errors.ErrorStack(err))
	}
	return objects
}

// CreateAPPProcess implements storage interface
func (s *Storage) CreateAPPProcess(ap *types.APPProcess) error {
	err := s.client.createAPPProcess(ap)
	if err != nil {
		return errors.Trace(err)
	}
	return nil
}

// UpdateAPPProcess implements storage interface
func (s *Storage) UpdateAPPProcess(ap *types.APPProcess) error {
	err := s.client.updateAPPProcess(ap)
	if err != nil {
		return errors.Trace(err)
	}
	return nil
}

// GetAPPProcessByID implements storage interface
func (s *Storage) GetAPPProcessByID(id int64) (*types.APPProcess, bool) {
	app, err := s.client.getAPPProcessByID(id)
	if err != nil {
		return nil, false
	}
	return app, true
}

// GetAPPProcessByExperimentID implements storage interface
func (s *Storage) GetAPPProcessByExperimentID(id int64) (types.APPProcesses, bool) {
	apps, err := s.client.getAPPProcessByExperimentID(id)
	if err != nil {
		return nil, false
	}
	return apps, true
}

// GetAPPProcessByNamespaceAndName implements storage interface
func (s *Storage) GetAPPProcessByNamespaceAndName(namespace, name string) (*types.APPProcess, bool) {
	app, err := s.client.getAPPProcessByNamespaceAndName(namespace, name)
	if err != nil {
		return nil, false
	}
	return app, true
}

// CreateTiDBClusterBackup implements storage interface
func (s *Storage) CreateTiDBClusterBackup(backup *types.TiDBClusterBackup) error {
	return errors.Trace(s.client.createTiDBClusterBackup(backup))
}

// UpdateTiDBClusterBackup implements storage interface
func (s *Storage) UpdateTiDBClusterBackup(backup *types.TiDBClusterBackup) error {
	return errors.Trace(s.client.updateTiDBClusterBackup(backup))
}

// GetTiDBClusterBackup implements storage interface
func (s *Storage) GetTiDBClusterBackup(id int64) (*types.TiDBClusterBackup, bool) {
	backup, err := s.client.getTiDBClusterBackupByID(id)
	if err != nil {
		return nil, false
	}
	return backup, true
}

// GetTiDBClusterBackupByCatIDAndName implements storage interface
func (s *Storage) GetTiDBClusterBackupByCatIDAndName(catID int64, name string) (*types.TiDBClusterBackup, bool) {
	backup, err := s.client.getTiDBClusterBackupByCatIDAndName(catID, name)
	if err != nil {
		return nil, false
	}
	return backup, true
}

// GetTiDBClusterBackupsByCatID implements storage interface
func (s *Storage) GetTiDBClusterBackupsByCatID(catID int64) []*types.TiDBClusterBackup {
	backups, err := s.client.getTiDBClusterBackupByCatID(catID)
	if err != nil {
		return []*types.TiDBClusterBackup{}
	}

	sort.SliceStable(backups, func(i, j int) bool { return backups[i].Ctime < backups[j].Ctime })
	return backups
}

// SetManagerSettings sets manager settings
func (s *Storage) SetManagerSettings(settings *types.ManagerSettings) error {
	err := s.client.setManagerSettings(settings)
	return errors.Trace(err)
}

// GetManagerSettings gets manager settings
func (s *Storage) GetManagerSettings() (*types.ManagerSettings, bool) {
	settings, err := s.client.listManagerSettings()
	if err != nil {
		return nil, false
	}
	return settings, true
}

// CreateTiDBClusterConfigTemplate implements storage interface
func (s *Storage) CreateTiDBClusterConfigTemplate(tpl *types.TiDBClusterConfigTemplate) error {
	if err := s.client.createTiDBClusterConfigTemplate(tpl); err != nil {
		return errors.Trace(err)
	}
	return nil
}

// UpdateTiDBClusterConfigTemplate implements storage interface
func (s *Storage) UpdateTiDBClusterConfigTemplate(tpl *types.TiDBClusterConfigTemplate) error {
	if err := s.client.updateTiDBClusterConfigTemplate(tpl); err != nil {
		return errors.Trace(err)
	}
	return nil
}

// GetTiDBClusterConfigTemplate implements storage interface
func (s *Storage) GetTiDBClusterConfigTemplate(id int64) (*types.TiDBClusterConfigTemplate, bool) {
	template, err := s.client.getTiDBClusterConfigTemplateByID(id)
	if err != nil {
		errors.Errorf("getting tidb cluster config template fails: %s", errors.ErrorStack(err))
		return nil, false
	}

	return template, true
}

// GetTiDBClusterConfigTemplateByName implements storage interface
func (s *Storage) GetTiDBClusterConfigTemplateByName(name string) (*types.TiDBClusterConfigTemplate, bool) {
	tmpl, err := s.client.getTiDBClusterConfigTemplateByName(name)
	if err != nil {
		return nil, false
	}

	return tmpl, true
}

// DeleteTiDBClusterConfigTemplate implements storage interface
func (s *Storage) DeleteTiDBClusterConfigTemplate(id int64) error {
	_, err := s.client.getTiDBClusterConfigTemplateByID(id)
	if err != nil {
		if strings.Contains(err.Error(), "no rows in result set") {
			return errors.NotFoundf("config template %d", id)
		}
		return errors.Trace(err)
	}

	if err := s.client.deleteTiDBClusterConfigTemplate(id); err != nil {
		return errors.Trace(err)
	}

	return nil
}

// GetTiDBClusterConfigTemplateByTag implements storage interface
func (s *Storage) GetTiDBClusterConfigTemplateByTag(tag string) types.TiDBClusterConfigTemplates {
	tpls, err := s.client.getTiDBClusterConfigTemplatesByTag(tag)
	if err != nil {
		return nil
	}

	sort.Sort(tpls)
	return tpls
}

// ListTiDBClusterConfigTemplates implements storage interface
func (s *Storage) ListTiDBClusterConfigTemplates() types.TiDBClusterConfigTemplates {
	tpls, err := s.client.listTiDBClusterConfigTemplates()
	if err != nil {
		return nil
	}

	sort.Sort(tpls)
	return tpls
}

// CreateBuildTask implements storage interface
func (s *Storage) CreateBuildTask(task *types.BuildTask) error {
	return errors.Trace(s.client.createBuildTask(task))
}

// UpdateBuildTask implements storage interface
func (s *Storage) UpdateBuildTask(task *types.BuildTask) error {
	return errors.Trace(s.client.updateBuildTask(task))
}

// GetBuildTaskByID implements storage interface
func (s *Storage) GetBuildTaskByID(id int64) (*types.BuildTask, bool) {
	task, err := s.client.findBuildTaskByID(id)
	if err != nil {
		log.Warnf("fail to get build task %d, error: %v", id, err)
		return nil, false
	}

	return task, true
}

// ListBuildTask implements storage interface
func (s *Storage) ListBuildTask(condition BuildTaskConditions) ([]*types.BuildTask, error) {
	return s.client.listBuildTask(condition)
}

// Close implements storage interface
func (s *Storage) Close() error {
	return s.client.Close()
}
