package repo

import (
	"net/http"
	"strings"

	asymkey_model "code.gitea.io/gitea/models/asymkey"
	git_model "code.gitea.io/gitea/models/git"
	issues_model "code.gitea.io/gitea/models/issues"
	repo_model "code.gitea.io/gitea/models/repo"
	user_model "code.gitea.io/gitea/models/user"
	"code.gitea.io/gitea/services/gitdiff"

	"code.gitea.io/gitea/modules/context"
	"code.gitea.io/gitea/modules/git"
	"code.gitea.io/gitea/modules/log"
	"code.gitea.io/gitea/modules/setting"
	"code.gitlink.org.cn/Gitlink/gitea_hat.git/modules/convert"
	hat_git "code.gitlink.org.cn/Gitlink/gitea_hat.git/modules/git"
)

func GetPullRequest(ctx *context.APIContext) {
	pr, err := issues_model.GetPullRequestByIndex(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
	if err != nil {
		if issues_model.IsErrPullRequestNotExist(err) {
			ctx.NotFound()
		} else {
			ctx.Error(http.StatusInternalServerError, "GetPullRequestByIndex", err)
		}
		return
	}

	if err = pr.LoadBaseRepoCtx(ctx); err != nil {
		ctx.Error(http.StatusInternalServerError, "LoadBaseRepo", err)
		return
	}
	if err = pr.LoadHeadRepoCtx(ctx); err != nil {
		ctx.Error(http.StatusInternalServerError, "LoadHeadRepo", err)
		return
	}
	apiResult, err := convert.ToAPIPullRequest(ctx, ctx.Repo.GitRepo, pr, ctx.Doer)
	if err != nil {
		ctx.Error(http.StatusInternalServerError, "ToAPIPullRequest", err)
		return
	}
	ctx.JSON(http.StatusOK, apiResult)
}

type PullRequestCommit struct {
	git_model.SignCommitWithStatuses
	Sha string
}

func GetPullCommits(ctx *context.APIContext) {
	pull, err := issues_model.GetPullRequestByIndex(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
	if err != nil {
		if issues_model.IsErrPullRequestNotExist(err) {
			ctx.NotFound()
		} else {
			ctx.Error(http.StatusInternalServerError, "GetPullRequestByIndex", err)
		}
		return
	}

	issue := pull.Issue
	if issue == nil {
		ctx.NotFound("issue is not present.")
		return
	}
	gitRepo := ctx.Repo.GitRepo

	var commits []*git.Commit
	var prInfo *git.CompareInfo

	if pull.HasMerged {
		var baseCommit string
		if pull.MergeBase == "" {
			var commitSHA, parentCommit string
			commitSHA, err := gitRepo.GetRefCommitID(pull.GetGitRefName())
			if err != nil {
				commitSHA, err := gitRepo.ReadPatchCommit(pull.Index)
				if err == nil {
					if err := gitRepo.SetReference(pull.GetGitRefName(), commitSHA); err != nil {
						log.Error("Could not write head file", err)
					}
				} else {
					log.Trace("No history file available for PR %d", pull.Index)
				}
			}
			if commitSHA != "" {
				parentCommit, _, err = git.NewCommand(ctx, "rev-list", "-1", "--skip=1").AddDynamicArguments(commitSHA).RunStdString(&git.RunOpts{Dir: gitRepo.Path})
				if err == nil {
					parentCommit = strings.TrimSpace(parentCommit)
				}
				if err != nil || parentCommit == "" {
					log.Info("No known parent commit for PR %d, error: %v", pull.Index, err)
					parentCommit = commitSHA
				}
			}
			baseCommit = parentCommit
		} else {
			baseCommit = pull.MergeBase
		}
		prInfo, err = gitRepo.GetCompareInfo(gitRepo.Path, baseCommit, pull.GetGitRefName(), false, false)

	} else {
		prInfo, err = gitRepo.GetCompareInfo(gitRepo.Path, pull.MergeBase, pull.GetGitRefName(), false, false)
	}

	if err != nil {
		ctx.Error(http.StatusInternalServerError, "GetCompareInfo", err)
		return
	}

	ctx.Data["Username"] = ctx.Repo.Owner.Name
	ctx.Data["Reponame"] = ctx.Repo.Repository.Name
	commits = prInfo.Commits
	userCommits := user_model.ValidateCommitsWithEmails(commits)
	signCommits := asymkey_model.ParseCommitsWithSignature(userCommits, ctx.Repo.Repository.GetTrustModel(),
		func(user *user_model.User) (bool, error) {
			return repo_model.IsOwnerMemberCollaborator(ctx.Repo.Repository, user.ID)
		})
	signStatusCommits := git_model.ParseCommitsWithStatus(signCommits, ctx.Repo.Repository)

	result := make([]PullRequestCommit, 0)
	for _, commit := range signStatusCommits {
		result = append(result, PullRequestCommit{
			SignCommitWithStatuses: *commit,
			Sha:                    commit.ID.String(),
		})
	}

	ctx.JSON(http.StatusOK, result)
}

func GetPullFiles(ctx *context.APIContext) {
	pull, err := issues_model.GetPullRequestByIndex(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
	if err != nil {
		if issues_model.IsErrPullRequestNotExist(err) {
			ctx.NotFound()
		} else {
			ctx.Error(http.StatusInternalServerError, "GetPullRequestByIndex", err)
		}
		return
	}

	issue := pull.Issue
	if issue == nil {
		ctx.NotFound("issue is not present.")
		return
	}
	gitRepo := ctx.Repo.GitRepo

	var prInfo *git.CompareInfo

	if pull.HasMerged {
		var baseCommit string
		if pull.MergeBase == "" {
			var commitSHA, parentCommit string
			commitSHA, err := gitRepo.GetRefCommitID(pull.GetGitRefName())
			if err != nil {
				commitSHA, err := gitRepo.ReadPatchCommit(pull.Index)
				if err == nil {
					if err := gitRepo.SetReference(pull.GetGitRefName(), commitSHA); err != nil {
						log.Error("Could not write head file", err)
					}
				} else {
					log.Trace("No history file available for PR %d", pull.Index)
				}
			}
			if commitSHA != "" {
				parentCommit, _, err = git.NewCommand(ctx, "rev-list", "-1", "--skip=1").AddDynamicArguments(commitSHA).RunStdString(&git.RunOpts{Dir: gitRepo.Path})
				if err == nil {
					parentCommit = strings.TrimSpace(parentCommit)
				}
				if err != nil || parentCommit == "" {
					log.Info("No known parent commit for PR %d, error: %v", pull.Index, err)
					parentCommit = commitSHA
				}
			}
			baseCommit = parentCommit
		} else {
			baseCommit = pull.MergeBase
		}
		prInfo, err = gitRepo.GetCompareInfo(gitRepo.Path, baseCommit, pull.GetGitRefName(), false, false)

	} else {
		prInfo, err = gitRepo.GetCompareInfo(gitRepo.Path, pull.MergeBase, pull.GetGitRefName(), false, false)
	}

	if err != nil {
		ctx.Error(http.StatusInternalServerError, "GetCompareInfo", err)
		return
	}

	headCommitID, err := gitRepo.GetRefCommitID(pull.GetGitRefName())
	if err != nil {
		ctx.Error(http.StatusInternalServerError, "GetRefCommitID", err)
		return
	}

	startCommitID := prInfo.MergeBase
	endCommitID := headCommitID

	ctx.Data["WhitespaceBehavior"] = ""
	diff := &gitdiff.Diff{Files: make([]*gitdiff.DiffFile, 0)}
	if ctx.Params("not-need-files") == "true" || ctx.Params("only-file-name") == "true" {
		// shortstatArgs := []string{startCommitID + "..." + endCommitID}
		shortStatArgs := []git.CmdArg{git.CmdArgCheck(startCommitID + "..." + endCommitID)}
		if len(startCommitID) == 0 || startCommitID == git.EmptySHA {
			shortStatArgs = []git.CmdArg{git.EmptyTreeSHA, git.CmdArgCheck(endCommitID)}
		}
		diff.NumFiles, diff.TotalAddition, diff.TotalDeletion, err = git.GetDiffShortStat(ctx, gitRepo.Path, shortStatArgs...)
		if err != nil && strings.Contains(err.Error(), "no merge base") {
			// git >= 2.28 now returns an error if base and head have become unrelated.
			// previously it would return the results of git diff --shortstat base head so let's try that...
			shortStatArgs = []git.CmdArg{git.CmdArgCheck(startCommitID), git.CmdArgCheck(endCommitID)}
			diff.NumFiles, diff.TotalAddition, diff.TotalDeletion, _ = git.GetDiffShortStat(ctx, gitRepo.Path, shortStatArgs...)
		}
		if ctx.Params("only-file-name") == "true" {
			var diffString string
			var err error
			if len(startCommitID) == 0 || startCommitID == git.EmptySHA {
				diffString, err = hat_git.GetDiffFileOnlyName(ctx.Repo.GitRepo, git.EmptyTreeSHA, headCommitID)
			} else {
				diffString, err = hat_git.GetDiffFileOnlyName(ctx.Repo.GitRepo, startCommitID, headCommitID)
			}
			if err == nil {
				for _, fileName := range strings.Split(diffString, "\n") {
					if fileName != "" {
						diff.Files = append(diff.Files, &gitdiff.DiffFile{
							Name: fileName,
						})
					}
				}
			}
		}
	} else {
		diff, err = gitdiff.GetDiff(gitRepo, &gitdiff.DiffOptions{
			BeforeCommitID:     startCommitID,
			AfterCommitID:      endCommitID,
			SkipTo:             ctx.FormString("skip-to"),
			MaxLines:           setting.Git.MaxGitDiffLines,
			MaxLineCharacters:  setting.Git.MaxGitDiffLineCharacters,
			MaxFiles:           setting.Git.MaxGitDiffFiles,
			WhitespaceBehavior: gitdiff.GetWhitespaceFlag(ctx.Data["WhitespaceBehavior"].(string)),
		})
		if err != nil {
			ctx.ServerError("GetDiffRangeWithWhitespaceBehavior", err)
			return
		}

		if err = diff.LoadComments(ctx, issue, ctx.Doer); err != nil {
			ctx.ServerError("LoadComments", err)
			return
		}
	}

	fileDiff := struct {
		*gitdiff.Diff
		LatestSha string
	}{
		Diff:      diff,
		LatestSha: endCommitID,
	}

	ctx.JSON(http.StatusOK, fileDiff)
}
