package experiment

import (
	"context"
	"fmt"
	"time"

	"github.com/juju/errors"
	"github.com/ngaut/log"
	"github.com/pingcap/schrodinger/config"
	sjira "github.com/pingcap/schrodinger/pkg/jira"
	"github.com/pingcap/schrodinger/pkg/notify/slack"
	"github.com/pingcap/schrodinger/util"
)

// callbackFunc defines a call function.
type callbackFunc func(experiment *Experiment) error

var callbackFuncs = map[string]callbackFunc{
	"adjustgc": adjustTiDBGC,
	"slack":    sendToSlack,
	"jira":     synToJira,
}

func synToJira(experiment *Experiment) error {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()
	var (
		jiraCli *sjira.Jira
		err     error
	)

	err = util.RetryOnError2(ctx, 5, 2*time.Second, func() (error, bool) {
		jiraConf := config.GetGlobalConfig().Jira
		jiraConf.FieldKeys = CustomField
		jiraCli, err = sjira.CreateJiraClient(&jiraConf)
		if err != nil {
			if util.CheckIsNetworkError(err) {
				return errors.Trace(err), false
			}
			return errors.Trace(err), true
		}
		return nil, true
	})
	if err != nil {
		log.Errorf("can't create jira client, error: %v", err)
		return errors.Trace(err)
	}
	_, err = CreateRecordIssue(ctx, experiment.process, jiraCli)
	if err != nil {
		log.Errorf("create record issue failed, error: %v", err)
		return errors.Trace(err)
	}

	return nil
}

func adjustTiDBGC(experiment *Experiment) error {
	if err := experiment.verify(); err != nil {
		return errors.Trace(err)
	}
	log.Infof("[experiment %s] adjust tidb cluster GC", experiment.name())

	if experiment.process.Report.Cat == nil || experiment.process.Report.Cat.Cluster == nil {
		log.Errorf("[experiment: %s] cluster is nil", experiment.name())
		return errors.Errorf("[experiment: %s] cluster is nil", experiment.name())
	}

	ip := experiment.process.Report.Cat.Cluster.TidbService.ClusterIP

	err := util.RetryOnError(context.Background(), config.GetGlobalConfig().Util.SQLRetryTime, func() error {
		err := experiment.operator.AdjustTidbGc(ip)
		return errors.Trace(err)
	})
	if err != nil {
		log.Errorf("[experiment: %s] adjust tidb cluster gc time failed: %v", experiment.name(), err)
		return errors.Trace(err)
	}

	log.Infof("[experiment %s] adjust tidb cluster GC success", experiment.name())
	return nil
}

func sendToSlack(experiment *Experiment) error {
	if err := experiment.verify(); err != nil {
		return errors.Trace(err)
	}

	channel := experiment.process.Config.Misc.SlackChannelForSuccess

	// if the state of experiment is normal and the channl is empty,
	// we will not send report to slack.
	if !experiment.isStateAbnormal() && util.IsEmpty(channel) {
		log.Warn("experiment is normal and slack channel for success is empty, ignore this report")
		return nil
	}

	if experiment.isStateAbnormal() {
		channel = experiment.process.Config.Misc.SlackChannelForError

		if util.IsEmpty(channel) {
			channel = config.GetGlobalConfig().Notifiers.Slack.Channel
		}

		// if channel is empty and the state of the experiment is abnormal,
		// we will return a error.
		if util.IsEmpty(channel) {
			return errors.New("experiment is abnormal and slack channel is empty")
		}
	}

	log.Infof("[experiment %s] send report to slack", experiment.name())

	detail, err := genReport(experiment)
	if err != nil {
		log.Errorf("[experiment %s] format reports error %v while send report to slack", experiment.name(), err)
		return errors.Trace(err)
	}

	notifier, ok := experiment.notifiers[slack.NotifierName]
	if !ok {
		return errors.New("slack notifier not found")
	}

	notification := slack.Notification{
		Channel:    channel,
		Username:   config.GetGlobalConfig().Notifiers.Slack.Username,
		Title:      fmt.Sprintf("experiment: [%s]", experiment.name()),
		Text:       detail,
		Color:      slack.SuccessColor,
		MarkDownIn: []string{"text"},
	}

	if experiment.isStateAbnormal() {
		notification.Color = slack.ErrorColor
	}

	if err = notifier.Notify(context.Background(), notification); err != nil {
		return errors.Trace(err)
	}

	return nil
}
