func getCurrentAndNextLabel(prefix string, labels []github.Label, needsLgtm int) (string, string) {
	currentLabel := ""
	nextLabel := ""
	for _, label := range labels {
		if strings.Contains(label.Name, prefix) {
			currentLabel = label.Name
			currentLgtmNumber, _ := strconv.Atoi(strings.Trim(label.Name, prefix))
			if currentLgtmNumber < needsLgtm {
				nextLabel = fmt.Sprintf("%s%d", prefix, currentLgtmNumber+1)
			}
		}
	}
	if currentLabel == "" {
		nextLabel = fmt.Sprintf("%s%d", prefix, 1)
	}
	return currentLabel, nextLabel
}
getCurrentAndNextLabel returns pull request current label and next required label.
package lgtm
import (
	"bytes"
	"fmt"
	"regexp"
	"strconv"
	"strings"
	"text/template"
	"time"
	"github.com/sirupsen/logrus"
	"github.com/ti-community-infra/tichi/internal/pkg/ownersclient"
	"k8s.io/apimachinery/pkg/util/sets"
	"k8s.io/test-infra/prow/config"
	"k8s.io/test-infra/prow/github"
	"k8s.io/test-infra/prow/pluginhelp"
	"k8s.io/test-infra/prow/pluginhelp/externalplugins"
	"k8s.io/test-infra/prow/plugins"
	tiexternalplugins "github.com/ti-community-infra/tichi/internal/pkg/externalplugins"
)
const (
	PluginName = "ti-community-lgtm"
	ReviewNotificationName = "Review Notification"
	ReviewNotificationIdentifier = "Review Notification Identifier"
)
var (
	notificationRegex = regexp.MustCompile("<!--" + ReviewNotificationIdentifier + "-->$")
	reviewersRegex = regexp.MustCompile(`(?i)- [@]*([a-z0-9](?:-?[a-z0-9]){0,38})`)
)
func HelpProvider(_ *tiexternalplugins.ConfigAgent) externalplugins.ExternalPluginHelpProvider {
	return func(enabledRepos []config.OrgRepo) (*pluginhelp.PluginHelp, error) {
		yamlSnippet, err := plugins.CommentMap.GenYaml(&tiexternalplugins.Configuration{
			TiCommunityLgtm: []tiexternalplugins.TiCommunityLgtm{
				{
					Repos:              []string{"ti-community-infra/test-dev"},
					PullOwnersEndpoint: "https://prow-dev.tidb.io/ti-community-owners",
				},
			},
		})
		if err != nil {
			logrus.WithError(err).Warnf("cannot generate comments for %s plugin", PluginName)
		}
		pluginHelp := &pluginhelp.PluginHelp{
			Description: "The ti-community-lgtm plugin manages the 'status/LGT{number}' (Looks Good To Me) label.",
			Snippet:     yamlSnippet,
			Events:      []string{tiexternalplugins.PullRequestReviewEvent, tiexternalplugins.PullRequestEvent},
		}
		pluginHelp.AddCommand(pluginhelp.Command{
			Usage:       "Triggered by GitHub review action: 'Approve' or 'Request Changes'.",
			Description: "Add or remove the 'status/LGT{number}' label.",
			Featured:    true,
			WhoCanUse:   "Reviewers of this pull request.",
			Examples: []string{
				"<a href=\"https://help.github.com/articles/about-pull-request-reviews/\">'Approve' or 'Request Changes'</a>"},
		})
		return pluginHelp, nil
	}
}
type githubClient interface {
	AddLabel(owner, repo string, number int, label string) error
	RemoveLabel(owner, repo string, number int, label string) error
	GetIssueLabels(org, repo string, number int) ([]github.Label, error)
	CreateComment(owner, repo string, number int, comment string) error
	EditComment(org, repo string, id int, comment string) error
	ListIssueComments(org, repo string, number int) ([]github.IssueComment, error)
	DeleteComment(org, repo string, ID int) error
	BotUserChecker() (func(candidate string) bool, error)
}
type reviewCtx struct {
	author, issueAuthor, body, htmlURL string
	repo                               github.Repo
	number                             int
}
func HandlePullReviewEvent(gc githubClient, pullReviewEvent *github.ReviewEvent,
	cfg *tiexternalplugins.Configuration, ol ownersclient.OwnersLoader, log *logrus.Entry) error {
	rc := reviewCtx{
		author:      pullReviewEvent.Review.User.Login,
		issueAuthor: pullReviewEvent.PullRequest.User.Login,
		repo:        pullReviewEvent.Repo,
		number:      pullReviewEvent.PullRequest.Number,
		body:        pullReviewEvent.Review.Body,
		htmlURL:     pullReviewEvent.Review.HTMLURL,
	}
	if pullReviewEvent.Action != github.ReviewActionSubmitted {
		return nil
	}
	reviewState := github.ReviewState(strings.ToUpper(string(pullReviewEvent.Review.State)))
	wantLGTM := false
	if reviewState == github.ReviewStateApproved {
		wantLGTM = true
	} else if reviewState == github.ReviewStateChangesRequested {
		wantLGTM = false
	} else {
		return nil
	}
	return handle(wantLGTM, cfg, rc, gc, ol, log)
}
func HandlePullRequestEvent(gc githubClient, pe *github.PullRequestEvent,
	config *tiexternalplugins.Configuration, log *logrus.Entry) error {
	if pe.Action != github.PullRequestActionOpened {
		log.Debug("Not a pull request opened action, skipping...")
		return nil
	}
	org := pe.PullRequest.Base.Repo.Owner.Login
	repo := pe.PullRequest.Base.Repo.Name
	number := pe.PullRequest.Number
	tichiURL := fmt.Sprintf(ownersclient.OwnersURLFmt, config.TichiWebURL, org, repo, number)
	reviewMsg, err := getMessage(nil, config.CommandHelpLink, config.PRProcessLink, tichiURL, org, repo)
	if err != nil {
		return err
	}
	return gc.CreateComment(org, repo, number, *reviewMsg)
}
func handle(wantLGTM bool, config *tiexternalplugins.Configuration, rc reviewCtx,
	gc githubClient, ol ownersclient.OwnersLoader, log *logrus.Entry) error {
	funcStart := time.Now()
	defer func() {
		log.WithField("duration", time.Since(funcStart).String()).Debug("Completed handle")
	}()
	author := rc.author
	number := rc.number
	body := rc.body
	htmlURL := rc.htmlURL
	org := rc.repo.Owner.Login
	repo := rc.repo.Name
	fetchErr := func(context string, err error) error {
		return fmt.Errorf("failed to get %s for %s/%s#%d: %v", context, org, repo, number, err)
	}
	opts := config.LgtmFor(rc.repo.Owner.Login, rc.repo.Name)
	tichiURL := fmt.Sprintf(ownersclient.OwnersURLFmt, config.TichiWebURL, org, repo, number)
	reviewersAndNeedsLGTM, err := ol.LoadOwners(opts.PullOwnersEndpoint, org, repo, number)
	if err != nil {
		return fetchErr("owners info", err)
	}
	reviewers := sets.String{}
	for _, reviewer := range reviewersAndNeedsLGTM.Reviewers {
		reviewers.Insert(reviewer)
	}
	if !reviewers.Has(author) && wantLGTM {
		resp := "Thanks for your review. "
		resp += "The bot only counts approvals from reviewers and higher roles in [list](" + tichiURL + "), "
		resp += "but you're still welcome to leave your comments."
		log.Infof("Reply approve pull request in comment: \"%s\"", resp)
		return gc.CreateComment(org, repo, number, tiexternalplugins.FormatResponseRaw(body, htmlURL, author, resp))
	}
	if !reviewers.Has(author) && !wantLGTM {
		resp := "Request changes is only allowed for the reviewers in [list](" + tichiURL + ")."
		log.Infof("Reply request changes pull request in comment: \"%s\"", resp)
		return gc.CreateComment(org, repo, number, tiexternalplugins.FormatResponseRaw(body, htmlURL, author, resp))
	}
	labels, err := gc.GetIssueLabels(org, repo, number)
	if err != nil {
		return fetchErr("issue labels", err)
	}
	botUserChecker, err := gc.BotUserChecker()
	if err != nil {
		return fetchErr("bot name", err)
	}
	issueComments, err := gc.ListIssueComments(org, repo, number)
	if err != nil {
		return fetchErr("issue comments", err)
	}
	notifications := filterComments(issueComments, notificationMatcher(botUserChecker))
	latestNotification := getLastComment(notifications)
	cleanupRedundantNotifications := func() {
		if len(notifications) != 0 {
			for _, notification := range notifications[:len(notifications)-1] {
				notif := notification
				if err := gc.DeleteComment(org, repo, notif.ID); err != nil {
					log.WithError(err).Errorf("Failed to delete comment from %s/%s#%d, ID: %d.", org, repo, number, notif.ID)
				}
			}
		}
	}
	currentLabel, nextLabel := getCurrentAndNextLabel(tiexternalplugins.LgtmLabelPrefix, labels,
		reviewersAndNeedsLGTM.NeedsLgtm)
	if currentLabel != "" && !wantLGTM {
		newMsg, err := getMessage(nil, config.CommandHelpLink, config.PRProcessLink, tichiURL, org, repo)
		if err != nil {
			return err
		}
		if latestNotification == nil {
			err := gc.CreateComment(org, repo, number, *newMsg)
			if err != nil {
				return err
			}
		} else {
			err := gc.EditComment(org, repo, latestNotification.ID, *newMsg)
			if err != nil {
				return err
			}
		}
		log.Info("Removing LGTM label.")
		if err := gc.RemoveLabel(org, repo, number, currentLabel); err != nil {
			return err
		}
		cleanupRedundantNotifications()
	} else if nextLabel != "" && wantLGTM {
		reviewedReviewers := getReviewersFromNotification(latestNotification)
		if reviewedReviewers.Has(author) {
			log.Infof("Ignore %s's multiple reviews.", author)
			return nil
		}
		reviewedReviewers.Insert(author)
		newMsg, err := getMessage(reviewedReviewers.List(), config.CommandHelpLink, config.PRProcessLink, tichiURL, org, repo)
		if err != nil {
			return err
		}
		if latestNotification == nil {
			err := gc.CreateComment(org, repo, number, *newMsg)
			if err != nil {
				return err
			}
		} else {
			err := gc.EditComment(org, repo, latestNotification.ID, *newMsg)
			if err != nil {
				return err
			}
		}
		log.Info("Adding LGTM label.")
		if currentLabel != "" {
			if err := gc.RemoveLabel(org, repo, number, currentLabel); err != nil {
				return err
			}
		}
		if err := gc.AddLabel(org, repo, number, nextLabel); err != nil {
			return err
		}
		cleanupRedundantNotifications()
	}
	return nil
}