package pull

import (
	"context"
	"fmt"
	"strconv"

	"code.gitea.io/gitea/models/db"
	issues_model "code.gitea.io/gitea/models/issues"
	webhook_model "code.gitea.io/gitea/models/webhook"
	"code.gitea.io/gitea/modules/git"
	"code.gitea.io/gitea/modules/graceful"
	"code.gitea.io/gitea/modules/log"
	"code.gitea.io/gitea/modules/queue"
	"code.gitea.io/gitea/modules/setting"
	"code.gitea.io/gitea/services/gitdiff"
	hat_issues_model "code.gitlink.org.cn/Gitlink/gitea_hat.git/models/issues"
	hat_pull_model "code.gitlink.org.cn/Gitlink/gitea_hat.git/models/pull"
	hat_webhook_model "code.gitlink.org.cn/Gitlink/gitea_hat.git/models/webhook"
)

var hatPrPatchCheckerQueue queue.UniqueQueue

func AddToTaskQueue(pr *issues_model.PullRequest, action string) {
	if action == "opened" || action == "synchronized" {
		err := hatPrPatchCheckerQueue.PushFunc(strconv.FormatInt(pr.ID, 10), func() error {
			return nil
		})
		if err != nil && err != queue.ErrAlreadyInQueue {
			log.Error("Error adding prID %d to the test pull requests queue: %v", pr.ID, err)
		}
	} else {
		log.Error("action type is not found rule.")
	}

}

// InitializePullRequests checks and tests untested patches of pull requests.
func InitializePullRequests(ctx context.Context) {
	prs, err := hat_issues_model.GetPullRequestIDsByIssueUpdatedUnix()
	if err != nil {
		log.Error("Find Checking PRs: %v", err)
		return
	}
	for _, prID := range prs {
		select {
		case <-ctx.Done():
			return
		default:
			if err := hatPrPatchCheckerQueue.PushFunc(strconv.FormatInt(prID, 10), func() error {
				log.Trace("Adding PR ID: %d to the pull requests patch hat checking queue", prID)
				return nil
			}); err != nil {
				log.Error("Error adding prID: %s to the pull requests patch hat checking queue %v", prID, err)
			}
		}
	}
}

func InitializeCheckHook(ctx context.Context) {
	w := webhook_model.Webhook{
		RepoID:          0,
		OrgID:           0,
		IsSystemWebhook: true,
		URL:             fmt.Sprintf("%sapi/hat/create_pr_version", setting.AppURL),
		HTTPMethod:      "POST",
	}
	_, err := hat_webhook_model.GetWebhook(&hat_webhook_model.Webhook{
		Webhook: w,
	})
	if err != nil {
		log.Error("Find System Hook: %v", err)
		if webhook_model.IsErrWebhookNotExist(err) {
			w.HookEvent = &webhook_model.HookEvent{
				ChooseEvents: true,
				HookEvents: webhook_model.HookEvents{
					PullRequestSync: true,
					PullRequest:     true,
				},
				BranchFilter: "*",
			}
			w.ContentType = webhook_model.ContentTypeJSON
			w.IsActive = true
			w.Type = "gitea"

			err = w.UpdateEvent()
			if err != nil {
				log.Error("Update System Hook Event: %v", err)
			}
			err = webhook_model.CreateWebhook(ctx, &w)
			if err != nil {
				log.Error("Create System Hook: %v", err)
			}
		}
	}

	return
}

func handle(data ...queue.Data) []queue.Data {
	for _, datum := range data {
		id, _ := strconv.ParseInt(datum.(string), 10, 64)
		pr, err := issues_model.GetPullRequestByID(db.DefaultContext, id)
		if err != nil {
			log.Error("GetPullRequestByID[%s]: %v", datum, err)
			continue
		} else if pr.HasMerged {
			continue
		}

		err = pr.LoadIssue()
		if err != nil {
			log.Error("pullrequest load issue error: %v", err)
			continue
		}

		err = pr.LoadBaseRepo()
		if err != nil {
			log.Error("pullrequest load base repo error: %v", err)
			continue
		}

		err = pr.LoadHeadRepo()
		if err != nil {
			log.Error("pullrequest load head repo error: %v", err)
			continue
		}

		baseGitRepo, err := git.OpenRepository(db.DefaultContext, pr.BaseRepo.RepoPath())
		if err != nil {
			log.Error("git.OpenRepository err:%v", err)
			continue
		}
		defer baseGitRepo.Close()

		commit, err := baseGitRepo.GetCommit(pr.GetGitRefName())
		if err != nil {
			log.Error("GetCommit err:%v", err)
			continue
		}

		hpr := &hat_issues_model.PullRequest{PullRequest: *pr}
		err = hpr.LoadLatestPullRequestVersion(db.DefaultContext)
		if err != nil && !issues_model.IsErrPullRequestNotExist(err) {
			log.Error("pullrequest load version error: %v", err)
			continue
		}
		if hpr.LatestPullRequestVersion == nil {
			betweenCommitsCount, err := baseGitRepo.CommitsCountBetween(pr.MergeBase, commit.ID.String())
			if err != nil {
				log.Error("CommitsBeforeUntil err: %v", err)
				continue
			}
			diffs, err := gitdiff.GetDiff(baseGitRepo, &gitdiff.DiffOptions{
				BeforeCommitID: pr.MergeBase,
				AfterCommitID:  commit.ID.String(),
			})
			if err != nil {
				log.Error("GetDiff err: %v", err)
				continue
			}
			err = hat_pull_model.NewPullRequestVersion(db.DefaultContext, pr.HeadRepo, pr, diffs.TotalAddition, int(betweenCommitsCount), diffs.TotalDeletion, diffs.NumFiles, commit.ID.String(), pr.MergeBase, pr.MergeBase)
			if err != nil {
				log.Error("models.NewPullRequestVersion err: %v", err)
				continue
			}
		} else {
			if commit.ID.String() == hpr.LatestPullRequestVersion.HeadCommitID {
				log.Error("This pull version is saved.")
				continue
			}
			betweenCommitsCount, err := baseGitRepo.CommitsCountBetween(hpr.LatestPullRequestVersion.HeadCommitID, commit.ID.String())
			if err != nil {
				log.Error("CommitsBeforeUntil err: %v", err)
				continue
			}
			diffs, err := gitdiff.GetDiff(baseGitRepo, &gitdiff.DiffOptions{
				BeforeCommitID: hpr.LatestPullRequestVersion.HeadCommitID,
				AfterCommitID:  commit.ID.String(),
			})
			if err != nil {
				log.Error("GetDiff err: %v", err)
				continue
			}
			err = hat_pull_model.NewPullRequestVersion(db.DefaultContext, pr.HeadRepo, pr, diffs.TotalAddition, int(betweenCommitsCount), diffs.TotalDeletion, diffs.NumFiles, commit.ID.String(), pr.MergeBase, hpr.LatestPullRequestVersion.HeadCommitID)
			if err != nil {
				log.Error("models.NewPullRequestVersion err: %v", err)
				continue
			}
		}
	}
	return nil
}

func Init() error {
	hatPrPatchCheckerQueue = queue.CreateUniqueQueue("hat_pr_patch_checker", handle, "")
	if hatPrPatchCheckerQueue == nil {
		return fmt.Errorf("Unable to create hat_pr_patch_checker Queue")
	}

	go graceful.GetManager().RunWithShutdownFns(hatPrPatchCheckerQueue.Run)
	go graceful.GetManager().RunWithShutdownContext(InitializePullRequests)
	go graceful.GetManager().RunWithShutdownContext(InitializeCheckHook)

	return nil
}
