package main

import (
	"errors"
	"os"
	"os/exec"
	"path"
	"strings"
	"testing"

	"go.uber.org/mock/gomock"
	"src.opensuse.org/autogits/common"
	"src.opensuse.org/autogits/common/gitea-generated/models"
	mock_common "src.opensuse.org/autogits/common/mock"
)

func TestPR(t *testing.T) {
	baseConfig := common.AutogitConfig{
		Reviewers:      []string{"+super1", "*super2", "m1", "-m2"},
		Branch:         "branch",
		Organization:   "foo",
		GitProjectName: "barPrj",
	}

	type prdata struct {
		pr           *models.PullRequest
		pr_err       error
		reviews      []*models.PullReview
		review_error error
	}

	t.Run("TestPR", func(t *testing.T) {
		tests := []struct {
			name      string
			data      []prdata
			api_error string

			resLen        int
			reviewed      bool
			consistentSet bool
			prjGitPRIndex int

			reviewSetFetcher func(*mock_common.MockGiteaPRFetcher) (*PRSet, error)
		}{
			{
				name: "Error fetching PullRequest",
				data: []prdata{
					{pr: &models.PullRequest{Body: "", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "test"}}}}, pr_err: errors.New("Missing PR")},
				},
			},

			{
				name: "Error fetching PullRequest in PrjGit",
				data: []prdata{
					{pr: &models.PullRequest{Body: "PR: foo/barPrj#22", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "test"}}}}},
					{pr: &models.PullRequest{Body: "", Index: 22, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}}, pr_err: errors.New("Missing PR")},
				},
			},

			{
				name: "Error fetching is missing links, fixable",
				data: []prdata{
					{pr: &models.PullRequest{Body: "", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "test"}}}}},
				},
				resLen:        1,
				prjGitPRIndex: -1,
			},
			{
				name: "Review set is not consistent, but fixable",
				data: []prdata{
					{pr: &models.PullRequest{Body: "PR: foo/barPrj#22", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "test"}}}}},
					{pr: &models.PullRequest{Body: "", Index: 22, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}}},
				},
				resLen:        2,
				prjGitPRIndex: 1,
			},

			{
				name: "Review set is consistent: 1pkg",
				data: []prdata{
					{pr: &models.PullRequest{Body: "PR: foo/barPrj#22", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "test"}}}}},
					{pr: &models.PullRequest{Body: "PR: test/repo#42", Index: 22, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}}},
				},
				resLen:        2,
				prjGitPRIndex: 1,
				consistentSet: true,
			},
			{
				name: "Review set is consistent: 2pkg",
				data: []prdata{
					{pr: &models.PullRequest{Body: "PR: foo/barPrj#22", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "test"}}}}},
					{pr: &models.PullRequest{Body: "PR: test/repo#42\nPR: test/repo2#41", Index: 22, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}}},
					{pr: &models.PullRequest{Body: "PR: foo/barPrj#22", Index: 41, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo2", Owner: &models.User{UserName: "test"}}}}},
				},
				resLen:        3,
				prjGitPRIndex: 1,
				consistentSet: true,
			},
			{
				name: "Review set of prjgit PR is consistent",
				data: []prdata{
					{
						pr: &models.PullRequest{Body: "", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}},
						reviews: []*models.PullReview{
							{Body: "LGTM", User: &models.User{UserName: "super1"}, State: common.ReviewStateApproved},
							{Body: "LGTM", User: &models.User{UserName: "super2"}, State: common.ReviewStateApproved},
							{Body: "LGTM", User: &models.User{UserName: common.Bot_BuildReview}, State: common.ReviewStateApproved},
						},
					},
				},
				resLen:        1,
				prjGitPRIndex: 0,
				consistentSet: true,
				reviewed:      true,
				reviewSetFetcher: func(mock *mock_common.MockGiteaPRFetcher) (*PRSet, error) {
					return FetchPRSet(mock, "foo", "barPrj", 42, &baseConfig)
				},
			},
			{
				name: "Review set is inconsistent: 2pkg",
				data: []prdata{
					{pr: &models.PullRequest{Body: "PR: foo/barPrj#22", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "test"}}}}},
					{pr: &models.PullRequest{Body: "PR: test/repo2#41", Index: 20, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}}},
					{pr: &models.PullRequest{Body: "PR: test/repo#42\nPR: test/repo2#41", Index: 22, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}}},
					{pr: &models.PullRequest{Body: "PR: foo/barPrj#20", Index: 41, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo2", Owner: &models.User{UserName: "test"}}}}},
				},
				resLen:        4,
				prjGitPRIndex: -1,
			},
			{
				name: "Review set is inconsistent: not pointing at PrjGit",
				data: []prdata{
					{pr: &models.PullRequest{Body: "PR: test/repo2#41", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "test"}}}}},
					{pr: &models.PullRequest{Body: "PR: test/repo#42\nPR: test/repo2#41", Index: 22, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}}},
					{pr: &models.PullRequest{Body: "PR: test/repo#42", Index: 41, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo2", Owner: &models.User{UserName: "test"}}}}},
				},
				resLen:        2,
				prjGitPRIndex: -1,
			},
		}

		for _, test := range tests {
			t.Run(test.name, func(t *testing.T) {
				ctl := gomock.NewController(t)
				pr_mock := mock_common.NewMockGiteaPRFetcher(ctl)
				review_mock := mock_common.NewMockGiteaReviewFetcher(ctl)

				var test_err error
				for _, data := range test.data {
					pr_mock.EXPECT().GetPullRequest(data.pr.Base.Repo.Owner.UserName, data.pr.Base.Repo.Name, data.pr.Index).Return(data.pr, data.pr_err).AnyTimes()
					if data.pr_err != nil {
						test_err = data.pr_err
					}
					review_mock.EXPECT().GetPullRequestReviews(data.pr.Base.Repo.Owner.UserName, data.pr.Base.Repo.Name, data.pr.Index).Return(data.reviews, data.review_error).AnyTimes()
				}

				var res *PRSet
				var err error

				if test.reviewSetFetcher != nil {
					res, err = test.reviewSetFetcher(pr_mock)
				} else {
					res, err = FetchPRSet(pr_mock, "test", "repo", 42, &baseConfig)
				}

				if err == nil {
					if test_err != nil {
						t.Fatal("Expected", test_err, "but got", err)
					}
				} else {
					if res != nil {
						t.Fatal("error but got ReviewSet?")
					}

					if test.api_error != "" {
						if err.Error() != test.api_error {
							t.Fatal("expected", test.api_error, "but got", err)
						}
					} else if test_err != err {
						t.Fatal("expected", test_err, "but got", err)
					}
					return
				}

				if test.resLen != len(res.prs) {
					t.Error("expected result len", test.resLen, "but got", len(res.prs))
				}

				PrjGitPR, err := res.GetPrjGitPR()
				if test.prjGitPRIndex < 0 {
					if err == nil {
						t.Error("expected error, but nothing")
					}
				}
				pr_found := false
				if test.prjGitPRIndex >= 0 {
					for i := range test.data {
						if PrjGitPR == test.data[i].pr && i == test.prjGitPRIndex {
							t.Log("found at index", i)
							pr_found = true
						}
					}
					if !pr_found {
						t.Error("Cannot find expected PrjGit location in PR set", PrjGitPR)
					}
				} else {
					if PrjGitPR != nil {
						t.Log("Expected prjgit not found, but found?", PrjGitPR)
					}
				}

				if res.IsConsistent() != test.consistentSet {
					t.Error("IsConsistent() returned unexpected:", test.consistentSet)
				}

				if isReviewed := res.IsReviewed(review_mock); isReviewed != test.reviewed {
					t.Error("expected reviewed to be NOT", isReviewed)
				}
			})
		}
	})
}

func TestPRMerge(t *testing.T) {
	cwd, _ := os.Getwd()
	cmd := exec.Command("/usr/bin/bash", path.Join(cwd, "test_repo_setup.sh"))
	cmd.Dir = t.TempDir()
	if out, err := cmd.CombinedOutput(); err != nil {
		t.Fatal(string(out))
	}

	common.ExtraGitParams = []string{
		"GIT_CONFIG_COUNT=1",
		"GIT_CONFIG_KEY_0=protocol.file.allow",
		"GIT_CONFIG_VALUE_0=always",

		"GIT_AUTHOR_NAME=testname",
		"GIT_AUTHOR_EMAIL=test@suse.com",
		"GIT_AUTHOR_DATE='2005-04-07T22:13:13'",
		"GIT_COMMITTER_NAME=testname",
		"GIT_COMMITTER_EMAIL=test@suse.com",
		"GIT_COMMITTER_DATE='2005-04-07T22:13:13'",
	}

	config := &common.AutogitConfig{
		Organization: "org",
		GitProjectName: "prj",
	}

	tests := []struct {
		name string
		pr *models.PullRequest
		mergeError string 
	}{
		{
			name: "Merge base not merged in main",

			pr: &models.PullRequest{
				Base: &models.PRBranchInfo{
					Sha: "e8b0de43d757c96a9d2c7101f4bff404e322f53a1fa4041fb85d646110c38ad4", // "base_add_b1"
					Repo: &models.Repository{
						Name: "prj",
						Owner: &models.User{
							UserName: "org",
						},
						SSHURL: path.Join(cmd.Dir, "prjgit"),
					},
				},
				Head: &models.PRBranchInfo {
					Sha: "88584433de1c917c1d773f62b82381848d882491940b5e9b427a540aa9057d9a", // "base_add_b2"
				},
			},
			mergeError: "Aborting merge",
		},
		{
			name: "Merge conflict in modules",

			pr: &models.PullRequest{
				Base: &models.PRBranchInfo{
					Sha: "4fbd1026b2d7462ebe9229a49100c11f1ad6555520a21ba515122d8bc41328a8",
					Repo: &models.Repository{
						Name: "prj",
						Owner: &models.User{
							UserName: "org",
						},
						SSHURL: path.Join(cmd.Dir, "prjgit"),
					},
				},
				Head: &models.PRBranchInfo {
					Sha: "88584433de1c917c1d773f62b82381848d882491940b5e9b427a540aa9057d9a", // "base_add_b2"
				},
			},
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			ctl := gomock.NewController(t)
			mock := mock_common.NewMockGiteaPRFetcher(ctl)

			mock.EXPECT().GetPullRequest("org", "prj", int64(1)).Return(test.pr, nil)

			set, err := FetchPRSet(mock, "org", "prj", 1, config)
			if err != nil {
				t.Fatal(err)
			}

			if err = set.Merge(); err != nil && (test.mergeError == "" || (len(test.mergeError) > 0 && !strings.Contains(err.Error(), test.mergeError))) {
				t.Fatal(err)
			}
		})
	}
}
