package trigger

import (
	"context"
	"fmt"
	"sync"
	"time"

	"github.com/google/go-github/github"
	"github.com/juju/errors"
	"github.com/ngaut/log"
	"github.com/pingcap/schrodinger/config"
	"github.com/pingcap/schrodinger/pkg/storage"
	"github.com/pingcap/schrodinger/pkg/types"
)

const (
	// pingCapOrg is pingcap org
	pingCapOrg = "pingcap"
)

type jobPair struct {
	fn    func()
	value string
}

// GithubTrigger is github trigger
type GithubTrigger struct {
	ctx context.Context
	wg  sync.WaitGroup

	storage *storage.Storage

	githubClient *github.Client
	jobPairs     map[int64]*jobPair
}

// NewGithubTrigger creates github trigger
func NewGithubTrigger(ctx context.Context, s *storage.Storage) Interface {
	trigger := &GithubTrigger{
		ctx:      ctx,
		storage:  s,
		jobPairs: make(map[int64]*jobPair),
	}

	trigger.githubClient = github.NewClient(nil)
	go trigger.run()
	return trigger
}

// AddJob adds github job
func (g *GithubTrigger) AddJob(ruleValue string, fn func(), jobID int64) error {
	githubJob := &jobPair{
		fn:    fn,
		value: ruleValue,
	}

	g.jobPairs[jobID] = githubJob
	return nil
}

// DelJob deletes job
func (g *GithubTrigger) DelJob(jobID int64) error {
	delete(g.jobPairs, jobID)
	return nil
}

func (g *GithubTrigger) run() {
	log.Info("github trigger start !")
	g.wg.Add(1)
	defer func() {
		log.Info("github trigger stop !")
		g.wg.Done()
	}()

	ticker := time.NewTicker(config.GetGlobalConfig().Util.GithubMonitorInterval.Duration)
	defer ticker.Stop()
	for {
		select {
		case <-g.ctx.Done():
			return
		case <-ticker.C:
			g.monitor()
		}
	}
}

func (g *GithubTrigger) monitor() {
	for jobID, jobPair := range g.jobPairs {
		log.Infof("monitor job %d", jobID)
		repo, hash, err := g.getHash(jobPair.value)
		if err != nil {
			log.Errorf("get cluster repo info failed %v", err)
			continue
		}
		trigger := false
		trigerInfo, exist := g.storage.GetGithubTriggerInfo(jobID)
		if !exist {
			log.Infof("should trigger")
			trigger = true
		} else {
			if trigerInfo.Hash != hash {
				trigger = true
			}
		}

		if trigger {
			g.trigger(repo, jobID, jobPair.fn, hash)
		}
	}
}

func (g *GithubTrigger) trigger(repo string, jobID int64, fn func(), hash string) {
	triggerInfo := &types.GithubTriggerInfo{
		JobID: jobID,
		Repo:  repo,
		Hash:  hash,
	}
	g.storage.SetGithubTriggerInfo(triggerInfo)
	fn()
}

func (g *GithubTrigger) getHash(branchStr string) (string, string, error) {
	repo, branch, err := getBranchFromValue(branchStr)
	if err != nil {
		return "", "", errors.Trace(err)
	}

	hash, err := g.getRepoHash(repo, branch)

	return repo, hash, errors.Trace(err)
}

func (g *GithubTrigger) getRepoHash(repo, branch string) (string, error) {
	ref, _, err := g.githubClient.Git.GetRef(g.ctx, pingCapOrg, repo, fmt.Sprintf("heads/%s", branch))
	if err != nil {
		return "", errors.Trace(err)
	}
	return ref.GetObject().GetSHA(), nil
}
