// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package integration

import (
	"fmt"
	"net/url"
	"strings"
	"testing"
	"time"

	auth_model "gitea.dev/models/auth"
	issues_model "gitea.dev/models/issues"
	repo_model "gitea.dev/models/repo"
	"gitea.dev/models/unittest"
	user_model "gitea.dev/models/user"
	"gitea.dev/modules/git/gitcmd"
	"gitea.dev/modules/setting"
	api "gitea.dev/modules/structs"

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

func TestManualMergeAutodetect(t *testing.T) {
	onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
		// user2 is the repo owner
		// user1 is the pusher/merger
		user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
		user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})

		// Create a repo owned by user2
		repoName := "manual-merge-autodetect"
		defaultBranch := setting.Repository.DefaultBranch
		user2Ctx := NewAPITestContext(t, user2.Name, repoName, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
		doAPICreateRepository(user2Ctx, false)(t)

		// Enable autodetect manual merge
		doAPIEditRepository(user2Ctx, &api.EditRepoOption{
			HasPullRequests:       new(true),
			AllowManualMerge:      new(true),
			AutodetectManualMerge: new(true),
		})(t)

		repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: repoName})
		user1Ctx := NewAPITestContext(t, user1.Name, repoName, auth_model.AccessTokenScopeWriteRepository)

		// multiple PRs should be able to be closed together if a push contains their branch commits.
		branchNames := []string{"fix-1", "fix-2"}
		apiPulls := make([]api.PullRequest, len(branchNames))
		for i, branchName := range branchNames {
			_, err := createFileInBranch(user2, repo,
				createFileInBranchOptions{OldBranch: defaultBranch, NewBranch: branchName},
				map[string]string{"test-file-" + branchName: "dummy"},
			)
			require.NoError(t, err)
			apiPulls[i], err = doAPICreatePullRequest(user1Ctx, user2.Name, repoName, defaultBranch, branchName)(t)
			require.NoError(t, err)
		}

		// user1 clones, then merges every branch sequentially, then pushes once.
		// The first merge fast-forwards; the rest produce real merge commits,
		// which generates multiple commits for "git rev-list --ancestry-path --merges ...".
		dstPath := t.TempDir()
		u, _ := url.Parse(giteaURL.String())
		u.Path = fmt.Sprintf("%s/%s.git", user2.Name, repoName)
		u.User = url.UserPassword(user1.Name, userPassword)

		doGitClone(dstPath, u)(t)

		// Capture each branch's expected merge commit hash from the local clone,
		// so we can assert that Gitea recorded the correct merge commit per PR
		// (and not just "some merge commit" — see the regression where every PR
		// was attributed to the last merge in the push).
		expectedMergeCommits := make([]string, len(branchNames))
		for i, branchName := range branchNames {
			doGitMerge(dstPath, "--no-ff", "-m", "merge "+branchName, "origin/"+branchName)(t)
			head, _, cmdErr := gitcmd.NewCommand("rev-parse", "HEAD").WithDir(dstPath).RunStdString(t.Context())
			require.NoError(t, cmdErr)
			expectedMergeCommits[i] = strings.TrimSpace(head)
		}
		doGitPushTestRepository(dstPath, "origin", defaultBranch)(t)

		// Every PR should be detected as manually merged by the background task, not just the last one.
		require.Eventually(t, func() bool {
			for _, apiPull := range apiPulls {
				pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: apiPull.ID})
				if !pr.HasMerged {
					return false
				}
			}
			return true
		}, 10*time.Second, 100*time.Millisecond)

		for i, apiPull := range apiPulls {
			pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: apiPull.ID})
			assert.Truef(t, pr.HasMerged, "PR %d (%s) should be marked as merged", i+1, branchNames[i])
			assert.Equalf(t, issues_model.PullRequestStatusManuallyMerged, pr.Status, "PR %d (%s) should have ManuallyMerged status", i+1, branchNames[i])
			assert.Equalf(t, user1.ID, pr.MergerID, "PR %d (%s) merger should be the pusher", i+1, branchNames[i])
			assert.Equalf(t, expectedMergeCommits[i], pr.MergedCommitID, "PR %d (%s) should be attributed to its own merge commit, not another PR's", i+1, branchNames[i])
		}
	})
}
