// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package integration_test

import (
	"log"
	"testing"
	"time"

	gitea "gitea.dev/sdk"

	"github.com/stretchr/testify/assert"
)

func TestPull(t *testing.T) {
	log.Println("== TestPull ==")
	c := newTestClient()
	user, _, err := c.Users.GetMyUserInfo(t.Context())
	assert.NoError(t, err)

	repoName := "repo_pull_test"
	forkOrg := "ForkOrg"
	if !preparePullTest(t, c, repoName, forkOrg) {
		return
	}

	// ListRepoPullRequests list PRs of one repository
	pulls, _, err := c.PullRequests.ListRepoPullRequests(t.Context(), user.UserName, repoName, gitea.ListPullRequestsOptions{})
	assert.NoError(t, err)
	assert.Len(t, pulls, 0)

	pullUpdateFile, _, err := c.PullRequests.CreatePullRequest(t.Context(), user.UserName, repoName, gitea.CreatePullRequestOption{
		Base:  "main",
		Head:  forkOrg + ":overwrite_licence",
		Title: "overwrite a file",
	})
	assert.NoError(t, err)
	assert.NotNil(t, pullUpdateFile)

	pullNewFile, _, err := c.PullRequests.CreatePullRequest(t.Context(), user.UserName, repoName, gitea.CreatePullRequestOption{
		Base:  "main",
		Head:  forkOrg + ":new_file",
		Title: "create a file",
	})
	assert.NoError(t, err)
	assert.NotNil(t, pullNewFile)

	pullConflict, _, err := c.PullRequests.CreatePullRequest(t.Context(), user.UserName, repoName, gitea.CreatePullRequestOption{
		Base:  "main",
		Head:  forkOrg + ":will_conflict",
		Title: "this pull will conflict",
	})
	assert.NoError(t, err)
	assert.NotNil(t, pullConflict)

	pulls, _, err = c.PullRequests.ListRepoPullRequests(t.Context(), user.UserName, repoName, gitea.ListPullRequestsOptions{})
	assert.NoError(t, err)
	assert.Len(t, pulls, 3)

	diff, _, err := c.PullRequests.GetPullRequestDiff(t.Context(), user.UserName, repoName, pullUpdateFile.Index, gitea.PullRequestDiffOptions{
		Binary: true,
	})
	assert.NoError(t, err)
	assert.True(t, len(diff) > 1100 && len(diff) < 1300)
	patch, _, err := c.PullRequests.GetPullRequestPatch(t.Context(), user.UserName, repoName, pullUpdateFile.Index)
	assert.NoError(t, err)
	assert.True(t, len(patch) > len(diff))

	commits, _, err := c.PullRequests.ListPullRequestCommits(t.Context(), user.UserName, repoName, pullUpdateFile.Index, gitea.ListPullRequestCommitsOptions{})
	assert.NoError(t, err)
	if assert.Len(t, commits, 1) && assert.Len(t, commits[0].Files, 1) {
		assert.EqualValues(t, "LICENSE", commits[0].Files[0].Filename)
	}

	files, _, err := c.PullRequests.ListPullRequestFiles(t.Context(), user.UserName, repoName, pullUpdateFile.Index, gitea.ListPullRequestFilesOptions{})
	assert.NoError(t, err)
	assert.Len(t, files, 1)
	file := files[0]
	assert.EqualValues(t, "LICENSE", file.Filename)
	assert.EqualValues(t, "changed", file.Status)
	assert.EqualValues(t, 3, file.Additions)
	assert.EqualValues(t, 18, file.Deletions)
	assert.EqualValues(t, 21, file.Changes)

	// test Update pull
	var pr *gitea.PullRequest
	for deadline := time.Now().Add(5 * time.Second); ; {
		pr, _, err = c.PullRequests.GetPullRequest(t.Context(), user.UserName, repoName, pullUpdateFile.Index)
		if err == nil && pr != nil && pr.Mergeable {
			break
		}
		if time.Now().After(deadline) {
			break
		}
		time.Sleep(100 * time.Millisecond)
	}
	assert.NoError(t, err)
	if !assert.NotNil(t, pr) {
		return
	}
	assert.False(t, pr.HasMerged)
	assert.True(t, pr.Mergeable)
	pullUpdateFile = pr
	merged, _, err := c.PullRequests.MergePullRequest(t.Context(), user.UserName, repoName, pullUpdateFile.Index, gitea.MergePullRequestOption{
		Style:   gitea.MergeStyleSquash,
		Title:   pullUpdateFile.Title,
		Message: "squash: " + pullUpdateFile.Title,
	})
	assert.NoError(t, err)
	assert.True(t, merged)
	merged, _, err = c.PullRequests.IsPullRequestMerged(t.Context(), user.UserName, repoName, pullUpdateFile.Index)
	assert.NoError(t, err)
	assert.True(t, merged)
	pr, _, err = c.PullRequests.GetPullRequest(t.Context(), user.UserName, repoName, pullUpdateFile.Index)
	assert.NoError(t, err)
	assert.EqualValues(t, pullUpdateFile.Head.Name, pr.Head.Name)
	assert.EqualValues(t, pullUpdateFile.Base.Name, pr.Base.Name)
	assert.NotEqual(t, pullUpdateFile.Base.Sha, pr.Base.Sha)
	assert.Len(t, *pr.MergedCommitID, 40)
	assert.True(t, pr.HasMerged)

	// test conflict pull
	pr, _, err = c.PullRequests.GetPullRequest(t.Context(), user.UserName, repoName, pullConflict.Index)
	assert.NoError(t, err)
	assert.False(t, pr.HasMerged)
	assert.False(t, pr.Mergeable)
	merged, _, err = c.PullRequests.MergePullRequest(t.Context(), user.UserName, repoName, pullConflict.Index, gitea.MergePullRequestOption{
		Style:   gitea.MergeStyleMerge,
		Title:   "pullConflict",
		Message: "pullConflict Msg",
	})
	assert.NoError(t, err)
	assert.False(t, merged)
	merged, _, err = c.PullRequests.IsPullRequestMerged(t.Context(), user.UserName, repoName, pullConflict.Index)
	assert.NoError(t, err)
	assert.False(t, merged)
	pr, _, err = c.PullRequests.GetPullRequest(t.Context(), user.UserName, repoName, pullConflict.Index)
	assert.NoError(t, err)
	assert.Nil(t, pr.MergedCommitID)
	assert.False(t, pr.HasMerged)

	state := gitea.StateClosed
	pr, _, err = c.PullRequests.EditPullRequest(t.Context(), user.UserName, repoName, pullConflict.Index, gitea.EditPullRequestOption{
		Title: "confl",
		State: &state,
	})
	assert.NoError(t, err)
	assert.EqualValues(t, state, pr.State)

	pulls, _, err = c.PullRequests.ListRepoPullRequests(t.Context(), user.UserName, repoName, gitea.ListPullRequestsOptions{
		State: gitea.StateClosed,
		Sort:  "leastupdate",
	})
	assert.NoError(t, err)
	assert.Len(t, pulls, 2)
}

func preparePullTest(t *testing.T, c *gitea.Client, repoName, forkOrg string) bool {
	owner := mustCurrentUsername(t, c)

	_, _ = c.Repositories.DeleteRepo(t.Context(), forkOrg, repoName)
	_, _ = c.Repositories.DeleteRepo(t.Context(), owner, repoName)
	_, _ = c.Organizations.DeleteOrg(t.Context(), forkOrg)

	origRepo, err := createTestRepo(t, repoName, c)
	if !assert.NoError(t, err) {
		return false
	}
	allowSquash := true
	_, _, err = c.Repositories.EditRepo(t.Context(), origRepo.Owner.UserName, origRepo.Name, gitea.EditRepoOption{
		AllowSquash: &allowSquash,
	})
	if !assert.NoError(t, err) {
		return false
	}

	org, _, err := c.Organizations.CreateOrg(t.Context(), gitea.CreateOrgOption{Name: forkOrg})
	assert.NoError(t, err)
	forkRepo, _, err := c.Repositories.CreateFork(t.Context(), origRepo.Owner.UserName, origRepo.Name, gitea.CreateForkOption{Organization: &org.Name})
	assert.NoError(t, err)
	assert.NotNil(t, forkRepo)

	mainLicense, _, err := c.Repositories.GetContents(t.Context(), forkRepo.Owner.UserName, forkRepo.Name, "main", "LICENSE")
	if !assert.NoError(t, err) || !assert.NotNil(t, mainLicense) {
		return false
	}

	updatedFile, _, err := c.Repositories.UpdateFile(t.Context(), forkRepo.Owner.UserName, forkRepo.Name, "LICENSE", gitea.UpdateFileOptions{
		FileOptions: gitea.FileOptions{
			Message:       "Overwrite",
			BranchName:    "main",
			NewBranchName: "overwrite_licence",
		},
		SHA:     mainLicense.SHA,
		Content: "Tk9USElORyBJUyBIRVJFIEFOWU1PUkUKSUYgWU9VIExJS0UgVE8gRklORCBTT01FVEhJTkcKV0FJVCBGT1IgVEhFIEZVVFVSRQo=",
	})
	if !assert.NoError(t, err) || !assert.NotNil(t, updatedFile) {
		return false
	}

	newFile, _, err := c.Repositories.CreateFile(t.Context(), forkRepo.Owner.UserName, forkRepo.Name, "WOW-file", gitea.CreateFileOptions{
		Content: "QSBuZXcgRmlsZQo=",
		FileOptions: gitea.FileOptions{
			Message:       "creat a new file",
			BranchName:    "main",
			NewBranchName: "new_file",
		},
	})
	if !assert.NoError(t, err) || !assert.NotNil(t, newFile) {
		return false
	}

	conflictFile1, _, err := c.Repositories.CreateFile(t.Context(), origRepo.Owner.UserName, origRepo.Name, "bad-file", gitea.CreateFileOptions{
		Content: "U3RhcnQgQ29uZmxpY3QK",
		FileOptions: gitea.FileOptions{
			Message:    "Start Conflict",
			BranchName: "main",
		},
	})
	if !assert.NoError(t, err) || !assert.NotNil(t, conflictFile1) {
		return false
	}

	conflictFile2, _, err := c.Repositories.CreateFile(t.Context(), forkRepo.Owner.UserName, forkRepo.Name, "bad-file", gitea.CreateFileOptions{
		Content: "V2lsbEhhdmUgQ29uZmxpY3QK",
		FileOptions: gitea.FileOptions{
			Message:       "creat a new file witch will conflict",
			BranchName:    "main",
			NewBranchName: "will_conflict",
		},
	})
	if !assert.NoError(t, err) || !assert.NotNil(t, conflictFile2) {
		return false
	}

	return true
}
