package github

import (
	"context"
	"encoding/json"
	"net/http"
	"sync/atomic"
	"testing"
	"time"

	"github.com/github/github-mcp-server/internal/githubv4mock"
	"github.com/github/github-mcp-server/internal/toolsnaps"
	"github.com/github/github-mcp-server/pkg/translations"
	"github.com/google/go-github/v89/github"
	"github.com/google/jsonschema-go/jsonschema"
	"github.com/shurcooL/githubv4"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func Test_GetPullRequest(t *testing.T) {
	// Verify tool definition once
	serverTool := PullRequestRead(translations.NullTranslationHelper)
	tool := serverTool.Tool
	require.NoError(t, toolsnaps.Test(tool.Name, tool))

	assert.Equal(t, "pull_request_read", tool.Name)
	assert.NotEmpty(t, tool.Description)
	schema := tool.InputSchema.(*jsonschema.Schema)
	assert.Contains(t, schema.Properties, "method")
	assert.Contains(t, schema.Properties, "owner")
	assert.Contains(t, schema.Properties, "repo")
	assert.Contains(t, schema.Properties, "pullNumber")
	assert.ElementsMatch(t, schema.Required, []string{"method", "owner", "repo", "pullNumber"})

	// Setup mock PR for success case
	mockPR := &github.PullRequest{
		Number:  github.Ptr(42),
		Title:   github.Ptr("Test PR"),
		State:   github.Ptr("open"),
		HTMLURL: github.Ptr("https://github.com/owner/repo/pull/42"),
		Head: &github.PullRequestBranch{
			SHA: github.Ptr("abcd1234"),
			Ref: github.Ptr("feature-branch"),
		},
		Base: &github.PullRequestBranch{
			Ref: github.Ptr("main"),
		},
		Body: github.Ptr("This is a test PR"),
		User: &github.User{
			Login: github.Ptr("testuser"),
		},
	}

	tests := []struct {
		name           string
		mockedClient   *http.Client
		requestArgs    map[string]any
		expectError    bool
		expectedPR     *github.PullRequest
		expectedErrMsg string
	}{
		{
			name: "successful PR fetch",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				GetReposPullsByOwnerByRepoByPullNumber: mockResponse(t, http.StatusOK, mockPR),
			}),
			requestArgs: map[string]any{
				"method":     "get",
				"owner":      "owner",
				"repo":       "repo",
				"pullNumber": float64(42),
			},
			expectError: false,
			expectedPR:  mockPR,
		},
		{
			name: "PR fetch fails",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				GetReposPullsByOwnerByRepoByPullNumber: func(w http.ResponseWriter, _ *http.Request) {
					w.WriteHeader(http.StatusNotFound)
					_, _ = w.Write([]byte(`{"message": "Not Found"}`))
				},
			}),
			requestArgs: map[string]any{
				"method":     "get",
				"owner":      "owner",
				"repo":       "repo",
				"pullNumber": float64(999),
			},
			expectError:    true,
			expectedErrMsg: "failed to get pull request",
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			// Setup client with mock
			client := mustNewGHClient(t, tc.mockedClient)
			gqlClient := githubv4.NewClient(githubv4mock.NewMockedHTTPClient())
			deps := BaseDeps{
				Client:          client,
				GQLClient:       gqlClient,
				RepoAccessCache: stubRepoAccessCache(nil, 5*time.Minute),
				Flags:           stubFeatureFlags(map[string]bool{"lockdown-mode": false}),
			}
			handler := serverTool.Handler(deps)

			// Create call request
			request := createMCPRequest(tc.requestArgs)

			// Call handler
			result, err := handler(ContextWithDeps(context.Background(), deps), &request)

			// Verify results
			if tc.expectError {
				require.NoError(t, err)
				require.True(t, result.IsError)
				errorContent := getErrorResult(t, result)
				assert.Contains(t, errorContent.Text, tc.expectedErrMsg)
				return
			}

			require.NoError(t, err)
			require.False(t, result.IsError)

			// Parse the result and get the text content if no error
			textContent := getTextResult(t, result)

			// Unmarshal and verify the minimal result
			var returnedPR MinimalPullRequest
			err = json.Unmarshal([]byte(textContent.Text), &returnedPR)
			require.NoError(t, err)
			assert.Equal(t, tc.expectedPR.GetNumber(), returnedPR.Number)
			assert.Equal(t, tc.expectedPR.GetTitle(), returnedPR.Title)
			assert.Equal(t, tc.expectedPR.GetState(), returnedPR.State)
			assert.Equal(t, tc.expectedPR.GetHTMLURL(), returnedPR.HTMLURL)
		})
	}
}

func Test_UpdatePullRequest(t *testing.T) {
	// Verify tool definition once
	serverTool := UpdatePullRequest(translations.NullTranslationHelper)
	tool := serverTool.Tool
	require.NoError(t, toolsnaps.Test(tool.Name, tool))

	assert.Equal(t, "update_pull_request", tool.Name)
	assert.NotEmpty(t, tool.Description)
	schema := tool.InputSchema.(*jsonschema.Schema)
	assert.Contains(t, schema.Properties, "owner")
	assert.Contains(t, schema.Properties, "repo")
	assert.Contains(t, schema.Properties, "pullNumber")
	assert.Contains(t, schema.Properties, "draft")
	assert.Contains(t, schema.Properties, "title")
	assert.Contains(t, schema.Properties, "body")
	assert.Contains(t, schema.Properties, "state")
	assert.Contains(t, schema.Properties, "base")
	assert.Contains(t, schema.Properties, "maintainer_can_modify")
	assert.Contains(t, schema.Properties, "reviewers")
	assert.ElementsMatch(t, schema.Required, []string{"owner", "repo", "pullNumber"})

	// Setup mock PR for success case
	mockUpdatedPR := &github.PullRequest{
		Number:              github.Ptr(42),
		Title:               github.Ptr("Updated Test PR Title"),
		State:               github.Ptr("open"),
		HTMLURL:             github.Ptr("https://github.com/owner/repo/pull/42"),
		Body:                github.Ptr("Updated test PR body."),
		MaintainerCanModify: github.Ptr(false),
		Draft:               github.Ptr(false),
		Base: &github.PullRequestBranch{
			Ref: github.Ptr("develop"),
		},
	}

	mockClosedPR := &github.PullRequest{
		Number: github.Ptr(42),
		Title:  github.Ptr("Test PR"),
		State:  github.Ptr("closed"), // State updated
	}

	// Mock PR for when there are no updates but we still need a response
	mockPRWithReviewers := &github.PullRequest{
		Number: github.Ptr(42),
		Title:  github.Ptr("Test PR"),
		State:  github.Ptr("open"),
		RequestedReviewers: []*github.User{
			{Login: github.Ptr("reviewer1")},
			{Login: github.Ptr("reviewer2")},
		},
	}

	tests := []struct {
		name           string
		mockedClient   *http.Client
		requestArgs    map[string]any
		expectError    bool
		expectedPR     *github.PullRequest
		expectedErrMsg string
	}{
		{
			name: "successful PR update (title, body, base, maintainer_can_modify)",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				PatchReposPullsByOwnerByRepoByPullNumber: expectRequestBody(t, map[string]any{
					"title":                 "Updated Test PR Title",
					"body":                  "Updated test PR body.",
					"base":                  "develop",
					"maintainer_can_modify": false,
				}).andThen(
					mockResponse(t, http.StatusOK, mockUpdatedPR),
				),
				GetReposPullsByOwnerByRepoByPullNumber: mockResponse(t, http.StatusOK, mockUpdatedPR),
			}),
			requestArgs: map[string]any{
				"owner":                 "owner",
				"repo":                  "repo",
				"pullNumber":            float64(42),
				"title":                 "Updated Test PR Title",
				"body":                  "Updated test PR body.",
				"base":                  "develop",
				"maintainer_can_modify": false,
			},
			expectError: false,
			expectedPR:  mockUpdatedPR,
		},
		{
			name: "successful PR update (state)",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				PatchReposPullsByOwnerByRepoByPullNumber: expectRequestBody(t, map[string]any{
					"state": "closed",
				}).andThen(
					mockResponse(t, http.StatusOK, mockClosedPR),
				),
				GetReposPullsByOwnerByRepoByPullNumber: mockResponse(t, http.StatusOK, mockClosedPR),
			}),
			requestArgs: map[string]any{
				"owner":      "owner",
				"repo":       "repo",
				"pullNumber": float64(42),
				"state":      "closed",
			},
			expectError: false,
			expectedPR:  mockClosedPR,
		},
		{
			name: "successful PR update with reviewers",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				PostReposPullsRequestedReviewersByOwnerByRepoByPullNumber: mockResponse(t, http.StatusOK, mockPRWithReviewers),
				GetReposPullsByOwnerByRepoByPullNumber:                    mockResponse(t, http.StatusOK, mockPRWithReviewers),
			}),
			requestArgs: map[string]any{
				"owner":      "owner",
				"repo":       "repo",
				"pullNumber": float64(42),
				"reviewers":  []any{"reviewer1", "reviewer2"},
			},
			expectError: false,
			expectedPR:  mockPRWithReviewers,
		},
		{
			name: "successful PR update with user and team reviewers",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				PostReposPullsRequestedReviewersByOwnerByRepoByPullNumber: expectRequestBody(t, map[string]any{
					"reviewers":      []any{"reviewer1"},
					"team_reviewers": []any{"platform"},
				}).andThen(mockResponse(t, http.StatusOK, mockPRWithReviewers)),
				GetReposPullsByOwnerByRepoByPullNumber: mockResponse(t, http.StatusOK, mockPRWithReviewers),
			}),
			requestArgs: map[string]any{
				"owner":      "owner",
				"repo":       "repo",
				"pullNumber": float64(42),
				"reviewers":  []any{"reviewer1", "owner/platform"},
			},
			expectError: false,
			expectedPR:  mockPRWithReviewers,
		},
		{
			name: "successful PR update (title only)",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				PatchReposPullsByOwnerByRepoByPullNumber: expectRequestBody(t, map[string]any{
					"title": "Updated Test PR Title",
				}).andThen(
					mockResponse(t, http.StatusOK, mockUpdatedPR),
				),
				GetReposPullsByOwnerByRepoByPullNumber: mockResponse(t, http.StatusOK, mockUpdatedPR),
			}),
			requestArgs: map[string]any{
				"owner":      "owner",
				"repo":       "repo",
				"pullNumber": float64(42),
				"title":      "Updated Test PR Title",
			},
			expectError: false,
			expectedPR:  mockUpdatedPR,
		},
		{
			name:         "no update parameters provided",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), // No API call expected
			requestArgs: map[string]any{
				"owner":      "owner",
				"repo":       "repo",
				"pullNumber": float64(42),
				// No update fields
			},
			expectError:    false, // Error is returned in the result, not as Go error
			expectedErrMsg: "No update parameters provided",
		},
		{
			name: "PR update fails (API error)",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				PatchReposPullsByOwnerByRepoByPullNumber: func(w http.ResponseWriter, _ *http.Request) {
					w.WriteHeader(http.StatusUnprocessableEntity)
					_, _ = w.Write([]byte(`{"message": "Validation Failed"}`))
				},
			}),
			requestArgs: map[string]any{
				"owner":      "owner",
				"repo":       "repo",
				"pullNumber": float64(42),
				"title":      "Invalid Title Causing Error",
			},
			expectError:    true,
			expectedErrMsg: "failed to update pull request",
		},
		{
			name: "request reviewers fails",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				PostReposPullsRequestedReviewersByOwnerByRepoByPullNumber: func(w http.ResponseWriter, _ *http.Request) {
					w.WriteHeader(http.StatusUnprocessableEntity)
					_, _ = w.Write([]byte(`{"message": "Invalid reviewers"}`))
				},
			}),
			requestArgs: map[string]any{
				"owner":      "owner",
				"repo":       "repo",
				"pullNumber": float64(42),
				"reviewers":  []any{"invalid-user"},
			},
			expectError:    true,
			expectedErrMsg: "failed to request reviewers",
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			// Setup client with mock
			client := mustNewGHClient(t, tc.mockedClient)
			gqlClient := githubv4.NewClient(nil)
			deps := BaseDeps{
				Client:    client,
				GQLClient: gqlClient,
			}
			handler := serverTool.Handler(deps)

			// Create call request
			request := createMCPRequest(tc.requestArgs)

			// Call handler
			result, err := handler(ContextWithDeps(context.Background(), deps), &request)

			// Verify results
			if tc.expectError || tc.expectedErrMsg != "" {
				require.NoError(t, err)
				require.True(t, result.IsError)
				errorContent := getErrorResult(t, result)
				if tc.expectedErrMsg != "" {
					assert.Contains(t, errorContent.Text, tc.expectedErrMsg)
				}
				return
			}

			require.NoError(t, err)
			require.False(t, result.IsError)

			// Parse the result and get the text content
			textContent := getTextResult(t, result)

			// Unmarshal and verify the minimal result
			var updateResp MinimalResponse
			err = json.Unmarshal([]byte(textContent.Text), &updateResp)
			require.NoError(t, err)
			assert.Equal(t, tc.expectedPR.GetHTMLURL(), updateResp.URL)
		})
	}
}

func Test_UpdatePullRequest_Draft(t *testing.T) {
	// Setup mock PR for success case
	mockUpdatedPR := &github.PullRequest{
		Number:              github.Ptr(42),
		Title:               github.Ptr("Test PR Title"),
		State:               github.Ptr("open"),
		HTMLURL:             github.Ptr("https://github.com/owner/repo/pull/42"),
		Body:                github.Ptr("Test PR body."),
		MaintainerCanModify: github.Ptr(false),
		Draft:               github.Ptr(false), // Updated to ready for review
		Base: &github.PullRequestBranch{
			Ref: github.Ptr("main"),
		},
	}

	tests := []struct {
		name           string
		mockedClient   *http.Client
		requestArgs    map[string]any
		expectError    bool
		expectedPR     *github.PullRequest
		expectedErrMsg string
	}{
		{
			name: "successful draft update to ready for review",
			mockedClient: githubv4mock.NewMockedHTTPClient(
				githubv4mock.NewQueryMatcher(
					struct {
						Repository struct {
							PullRequest struct {
								ID      githubv4.ID
								IsDraft githubv4.Boolean
							} `graphql:"pullRequest(number: $prNum)"`
						} `graphql:"repository(owner: $owner, name: $repo)"`
					}{},
					map[string]any{
						"owner": githubv4.String("owner"),
						"repo":  githubv4.String("repo"),
						"prNum": githubv4.Int(42),
					},
					githubv4mock.DataResponse(map[string]any{
						"repository": map[string]any{
							"pullRequest": map[string]any{
								"id":      "PR_kwDOA0xdyM50BPaO",
								"isDraft": true, // Current state is draft
							},
						},
					}),
				),
				githubv4mock.NewMutationMatcher(
					struct {
						MarkPullRequestReadyForReview struct {
							PullRequest struct {
								ID      githubv4.ID
								IsDraft githubv4.Boolean
							}
						} `graphql:"markPullRequestReadyForReview(input: $input)"`
					}{},
					githubv4.MarkPullRequestReadyForReviewInput{
						PullRequestID: "PR_kwDOA0xdyM50BPaO",
					},
					nil,
					githubv4mock.DataResponse(map[string]any{
						"markPullRequestReadyForReview": map[string]any{
							"pullRequest": map[string]any{
								"id":      "PR_kwDOA0xdyM50BPaO",
								"isDraft": false,
							},
						},
					}),
				),
			),
			requestArgs: map[string]any{
				"owner":      "owner",
				"repo":       "repo",
				"pullNumber": float64(42),
				"draft":      false,
			},
			expectError: false,
			expectedPR:  mockUpdatedPR,
		},
		{
			name: "successful convert pull request to draft",
			mockedClient: githubv4mock.NewMockedHTTPClient(
				githubv4mock.NewQueryMatcher(
					struct {
						Repository struct {
							PullRequest struct {
								ID      githubv4.ID
								IsDraft githubv4.Boolean
							} `graphql:"pullRequest(number: $prNum)"`
						} `graphql:"repository(owner: $owner, name: $repo)"`
					}{},
					map[string]any{
						"owner": githubv4.String("owner"),
						"repo":  githubv4.String("repo"),
						"prNum": githubv4.Int(42),
					},
					githubv4mock.DataResponse(map[string]any{
						"repository": map[string]any{
							"pullRequest": map[string]any{
								"id":      "PR_kwDOA0xdyM50BPaO",
								"isDraft": false, // Current state is draft
							},
						},
					}),
				),
				githubv4mock.NewMutationMatcher(
					struct {
						ConvertPullRequestToDraft struct {
							PullRequest struct {
								ID      githubv4.ID
								IsDraft githubv4.Boolean
							}
						} `graphql:"convertPullRequestToDraft(input: $input)"`
					}{},
					githubv4.ConvertPullRequestToDraftInput{
						PullRequestID: "PR_kwDOA0xdyM50BPaO",
					},
					nil,
					githubv4mock.DataResponse(map[string]any{
						"convertPullRequestToDraft": map[string]any{
							"pullRequest": map[string]any{
								"id":      "PR_kwDOA0xdyM50BPaO",
								"isDraft": true,
							},
						},
					}),
				),
			),
			requestArgs: map[string]any{
				"owner":      "owner",
				"repo":       "repo",
				"pullNumber": float64(42),
				"draft":      true,
			},
			expectError: false,
			expectedPR:  mockUpdatedPR,
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			// For draft-only tests, we need to mock both GraphQL and the final REST GET call
			restClient := mustNewGHClient(t, MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				GetReposPullsByOwnerByRepoByPullNumber: mockResponse(t, http.StatusOK, mockUpdatedPR),
			}))
			gqlClient := githubv4.NewClient(tc.mockedClient)

			serverTool := UpdatePullRequest(translations.NullTranslationHelper)
			deps := BaseDeps{
				Client:    restClient,
				GQLClient: gqlClient,
			}
			handler := serverTool.Handler(deps)

			request := createMCPRequest(tc.requestArgs)

			result, err := handler(ContextWithDeps(context.Background(), deps), &request)

			if tc.expectError || tc.expectedErrMsg != "" {
				require.NoError(t, err)
				require.True(t, result.IsError)
				errorContent := getErrorResult(t, result)
				if tc.expectedErrMsg != "" {
					assert.Contains(t, errorContent.Text, tc.expectedErrMsg)
				}
				return
			}

			require.NoError(t, err)
			require.False(t, result.IsError)

			textContent := getTextResult(t, result)

			// Unmarshal and verify the minimal result
			var updateResp MinimalResponse
			err = json.Unmarshal([]byte(textContent.Text), &updateResp)
			require.NoError(t, err)
			assert.Equal(t, tc.expectedPR.GetHTMLURL(), updateResp.URL)
		})
	}
}

func Test_ListPullRequests(t *testing.T) {
	// Verify tool definition once
	serverTool := ListPullRequests(translations.NullTranslationHelper)
	tool := serverTool.Tool
	// ListPullRequests is the FeatureFlagFieldsParam-enabled variant; it owns
	// the _ff_<flag> snapshot. The canonical list_pull_requests.snap is owned by
	// LegacyListPullRequests (see Test_LegacyListPullRequests_Definition).
	require.NoError(t, toolsnaps.Test(tool.Name+"_ff_"+FeatureFlagFieldsParam, tool))
	require.Equal(t, FeatureFlagFieldsParam, serverTool.FeatureFlagEnable)

	assert.Equal(t, "list_pull_requests", tool.Name)
	assert.NotEmpty(t, tool.Description)
	schema := tool.InputSchema.(*jsonschema.Schema)
	assert.Contains(t, schema.Properties, "owner")
	assert.Contains(t, schema.Properties, "repo")
	assert.Contains(t, schema.Properties, "state")
	assert.Contains(t, schema.Properties, "head")
	assert.Contains(t, schema.Properties, "base")
	assert.Contains(t, schema.Properties, "sort")
	assert.Contains(t, schema.Properties, "direction")
	assert.Contains(t, schema.Properties, "perPage")
	assert.Contains(t, schema.Properties, "page")
	assert.Contains(t, schema.Properties, "fields")
	assert.ElementsMatch(t, schema.Required, []string{"owner", "repo"})

	// Setup mock PRs for success case
	mockPRs := []*github.PullRequest{
		{
			Number:  github.Ptr(42),
			Title:   github.Ptr("First PR"),
			State:   github.Ptr("open"),
			HTMLURL: github.Ptr("https://github.com/owner/repo/pull/42"),
		},
		{
			Number:  github.Ptr(43),
			Title:   github.Ptr("Second PR"),
			State:   github.Ptr("closed"),
			HTMLURL: github.Ptr("https://github.com/owner/repo/pull/43"),
		},
	}

	tests := []struct {
		name           string
		mockedClient   *http.Client
		requestArgs    map[string]any
		expectError    bool
		expectedPRs    []*github.PullRequest
		expectedErrMsg string
	}{
		{
			name: "successful PRs listing",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				GetReposPullsByOwnerByRepo: expectQueryParams(t, map[string]string{
					"state":     "all",
					"sort":      "created",
					"direction": "desc",
					"per_page":  "30",
					"page":      "1",
				}).andThen(
					mockResponse(t, http.StatusOK, mockPRs),
				),
			}),
			requestArgs: map[string]any{
				"owner":     "owner",
				"repo":      "repo",
				"state":     "all",
				"sort":      "created",
				"direction": "desc",
				"perPage":   float64(30),
				"page":      float64(1),
			},
			expectError: false,
			expectedPRs: mockPRs,
		},
		{
			name: "PRs listing fails",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				GetReposPullsByOwnerByRepo: func(w http.ResponseWriter, _ *http.Request) {
					w.WriteHeader(http.StatusBadRequest)
					_, _ = w.Write([]byte(`{"message": "Invalid request"}`))
				},
			}),
			requestArgs: map[string]any{
				"owner": "owner",
				"repo":  "repo",
				"state": "invalid",
			},
			expectError:    true,
			expectedErrMsg: "failed to list pull requests",
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			// Setup client with mock
			client := mustNewGHClient(t, tc.mockedClient)
			serverTool := ListPullRequests(translations.NullTranslationHelper)
			deps := BaseDeps{
				Client: client,
			}
			handler := serverTool.Handler(deps)

			// Create call request
			request := createMCPRequest(tc.requestArgs)

			// Call handler
			result, err := handler(ContextWithDeps(context.Background(), deps), &request)

			// Verify results
			if tc.expectError {
				require.NoError(t, err)
				require.True(t, result.IsError)
				errorContent := getErrorResult(t, result)
				assert.Contains(t, errorContent.Text, tc.expectedErrMsg)
				return
			}

			require.NoError(t, err)
			require.False(t, result.IsError)

			// Parse the result and get the text content if no error
			textContent := getTextResult(t, result)

			// Unmarshal and verify the result
			var returnedPRs []MinimalPullRequest
			err = json.Unmarshal([]byte(textContent.Text), &returnedPRs)
			require.NoError(t, err)
			assert.Len(t, returnedPRs, 2)
			assert.Equal(t, *tc.expectedPRs[0].Number, returnedPRs[0].Number)
			assert.Equal(t, *tc.expectedPRs[0].Title, returnedPRs[0].Title)
			assert.Equal(t, *tc.expectedPRs[0].State, returnedPRs[0].State)
			assert.Equal(t, *tc.expectedPRs[1].Number, returnedPRs[1].Number)
			assert.Equal(t, *tc.expectedPRs[1].Title, returnedPRs[1].Title)
			assert.Equal(t, *tc.expectedPRs[1].State, returnedPRs[1].State)
		})
	}
}

func Test_MergePullRequest(t *testing.T) {
	// Verify tool definition once
	serverTool := MergePullRequest(translations.NullTranslationHelper)
	tool := serverTool.Tool
	require.NoError(t, toolsnaps.Test(tool.Name, tool))

	assert.Equal(t, "merge_pull_request", tool.Name)
	assert.NotEmpty(t, tool.Description)
	schema := tool.InputSchema.(*jsonschema.Schema)
	assert.Contains(t, schema.Properties, "owner")
	assert.Contains(t, schema.Properties, "repo")
	assert.Contains(t, schema.Properties, "pullNumber")
	assert.Contains(t, schema.Properties, "commit_title")
	assert.Contains(t, schema.Properties, "commit_message")
	assert.Contains(t, schema.Properties, "merge_method")
	assert.ElementsMatch(t, schema.Required, []string{"owner", "repo", "pullNumber"})

	// Setup mock merge result for success case
	mockMergeResult := &github.PullRequestMergeResult{
		Merged:  github.Ptr(true),
		Message: github.Ptr("Pull Request successfully merged"),
		SHA:     github.Ptr("abcd1234efgh5678"),
	}

	tests := []struct {
		name                string
		mockedClient        *http.Client
		requestArgs         map[string]any
		expectError         bool
		expectedMergeResult *github.PullRequestMergeResult
		expectedErrMsg      string
	}{
		{
			name: "successful merge",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				PutReposPullsMergeByOwnerByRepoByPullNumber: expectRequestBody(t, map[string]any{
					"commit_title":   "Merge PR #42",
					"commit_message": "Merging awesome feature",
					"merge_method":   "squash",
				}).andThen(
					mockResponse(t, http.StatusOK, mockMergeResult),
				),
			}),
			requestArgs: map[string]any{
				"owner":          "owner",
				"repo":           "repo",
				"pullNumber":     float64(42),
				"commit_title":   "Merge PR #42",
				"commit_message": "Merging awesome feature",
				"merge_method":   "squash",
			},
			expectError:         false,
			expectedMergeResult: mockMergeResult,
		},
		{
			name: "merge fails",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				PutReposPullsMergeByOwnerByRepoByPullNumber: func(w http.ResponseWriter, _ *http.Request) {
					w.WriteHeader(http.StatusMethodNotAllowed)
					_, _ = w.Write([]byte(`{"message": "Pull request cannot be merged"}`))
				},
			}),
			requestArgs: map[string]any{
				"owner":      "owner",
				"repo":       "repo",
				"pullNumber": float64(42),
			},
			expectError:    true,
			expectedErrMsg: "failed to merge pull request",
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			// Setup client with mock
			client := mustNewGHClient(t, tc.mockedClient)
			serverTool := MergePullRequest(translations.NullTranslationHelper)
			deps := BaseDeps{
				Client: client,
			}
			handler := serverTool.Handler(deps)

			// Create call request
			request := createMCPRequest(tc.requestArgs)

			// Call handler
			result, err := handler(ContextWithDeps(context.Background(), deps), &request)

			// Verify results
			if tc.expectError {
				require.NoError(t, err)
				require.True(t, result.IsError)
				errorContent := getErrorResult(t, result)
				assert.Contains(t, errorContent.Text, tc.expectedErrMsg)
				return
			}

			require.NoError(t, err)
			require.False(t, result.IsError)

			// Parse the result and get the text content if no error
			textContent := getTextResult(t, result)

			// Unmarshal and verify the result
			var returnedResult github.PullRequestMergeResult
			err = json.Unmarshal([]byte(textContent.Text), &returnedResult)
			require.NoError(t, err)
			assert.Equal(t, *tc.expectedMergeResult.Merged, *returnedResult.Merged)
			assert.Equal(t, *tc.expectedMergeResult.Message, *returnedResult.Message)
			assert.Equal(t, *tc.expectedMergeResult.SHA, *returnedResult.SHA)
		})
	}
}

func Test_SearchPullRequests(t *testing.T) {
	serverTool := SearchPullRequests(translations.NullTranslationHelper)
	tool := serverTool.Tool
	// SearchPullRequests is the FeatureFlagFieldsParam-enabled variant; it owns
	// the _ff_<flag> snapshot. The canonical search_pull_requests.snap is owned
	// by LegacySearchPullRequests (see Test_LegacySearchPullRequests_Definition).
	require.NoError(t, toolsnaps.Test(tool.Name+"_ff_"+FeatureFlagFieldsParam, tool))
	require.Equal(t, FeatureFlagFieldsParam, serverTool.FeatureFlagEnable)

	assert.Equal(t, "search_pull_requests", tool.Name)
	assert.NotEmpty(t, tool.Description)
	schema := tool.InputSchema.(*jsonschema.Schema)
	assert.Contains(t, schema.Properties, "query")
	assert.Contains(t, schema.Properties, "owner")
	assert.Contains(t, schema.Properties, "repo")
	assert.Contains(t, schema.Properties, "sort")
	assert.Contains(t, schema.Properties, "order")
	assert.Contains(t, schema.Properties, "perPage")
	assert.Contains(t, schema.Properties, "page")
	assert.Contains(t, schema.Properties, "fields")
	assert.ElementsMatch(t, schema.Required, []string{"query"})

	mockSearchResult := &github.IssuesSearchResult{
		Total:             github.Ptr(2),
		IncompleteResults: github.Ptr(false),
		Issues: []*github.Issue{
			{
				Number:   github.Ptr(42),
				Title:    github.Ptr("Test PR 1"),
				Body:     github.Ptr("Updated tests."),
				State:    github.Ptr("open"),
				HTMLURL:  github.Ptr("https://github.com/owner/repo/pull/1"),
				Comments: github.Ptr(5),
				User: &github.User{
					Login: github.Ptr("user1"),
				},
			},
			{
				Number:   github.Ptr(43),
				Title:    github.Ptr("Test PR 2"),
				Body:     github.Ptr("Updated build scripts."),
				State:    github.Ptr("open"),
				HTMLURL:  github.Ptr("https://github.com/owner/repo/pull/2"),
				Comments: github.Ptr(3),
				User: &github.User{
					Login: github.Ptr("user2"),
				},
			},
		},
	}

	tests := []struct {
		name           string
		mockedClient   *http.Client
		requestArgs    map[string]any
		expectError    bool
		expectedResult *github.IssuesSearchResult
		expectedErrMsg string
	}{
		{
			name: "successful pull request search with all parameters",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				GetSearchIssues: expectQueryParams(
					t,
					map[string]string{
						"q":        "is:pr repo:owner/repo is:open",
						"sort":     "created",
						"order":    "desc",
						"page":     "1",
						"per_page": "30",
					},
				).andThen(
					mockResponse(t, http.StatusOK, mockSearchResult),
				),
			}),
			requestArgs: map[string]any{
				"query":   "repo:owner/repo is:open",
				"sort":    "created",
				"order":   "desc",
				"page":    float64(1),
				"perPage": float64(30),
			},
			expectError:    false,
			expectedResult: mockSearchResult,
		},
		{
			name: "pull request search with owner and repo parameters",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				GetSearchIssues: expectQueryParams(
					t,
					map[string]string{
						"q":        "repo:test-owner/test-repo is:pr draft:false",
						"sort":     "updated",
						"order":    "asc",
						"page":     "1",
						"per_page": "30",
					},
				).andThen(
					mockResponse(t, http.StatusOK, mockSearchResult),
				),
			}),
			requestArgs: map[string]any{
				"query": "draft:false",
				"owner": "test-owner",
				"repo":  "test-repo",
				"sort":  "updated",
				"order": "asc",
			},
			expectError:    false,
			expectedResult: mockSearchResult,
		},
		{
			name: "pull request search with only owner parameter (should ignore it)",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				GetSearchIssues: expectQueryParams(
					t,
					map[string]string{
						"q":        "is:pr feature",
						"page":     "1",
						"per_page": "30",
					},
				).andThen(
					mockResponse(t, http.StatusOK, mockSearchResult),
				),
			}),
			requestArgs: map[string]any{
				"query": "feature",
				"owner": "test-owner",
			},
			expectError:    false,
			expectedResult: mockSearchResult,
		},
		{
			name: "pull request search with only repo parameter (should ignore it)",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				GetSearchIssues: expectQueryParams(
					t,
					map[string]string{
						"q":        "is:pr review-required",
						"page":     "1",
						"per_page": "30",
					},
				).andThen(
					mockResponse(t, http.StatusOK, mockSearchResult),
				),
			}),
			requestArgs: map[string]any{
				"query": "review-required",
				"repo":  "test-repo",
			},
			expectError:    false,
			expectedResult: mockSearchResult,
		},
		{
			name: "pull request search with minimal parameters",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				GetSearchIssues: mockResponse(t, http.StatusOK, mockSearchResult),
			}),
			requestArgs: map[string]any{
				"query": "is:pr repo:owner/repo is:open",
			},
			expectError:    false,
			expectedResult: mockSearchResult,
		},
		{
			name: "query with existing is:pr filter - no duplication",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				GetSearchIssues: expectQueryParams(
					t,
					map[string]string{
						"q":        "is:pr repo:github/github-mcp-server is:open draft:false",
						"page":     "1",
						"per_page": "30",
					},
				).andThen(
					mockResponse(t, http.StatusOK, mockSearchResult),
				),
			}),
			requestArgs: map[string]any{
				"query": "is:pr repo:github/github-mcp-server is:open draft:false",
			},
			expectError:    false,
			expectedResult: mockSearchResult,
		},
		{
			name: "query with existing repo: filter and conflicting owner/repo params - uses query filter",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				GetSearchIssues: expectQueryParams(
					t,
					map[string]string{
						"q":        "is:pr repo:github/github-mcp-server author:octocat",
						"page":     "1",
						"per_page": "30",
					},
				).andThen(
					mockResponse(t, http.StatusOK, mockSearchResult),
				),
			}),
			requestArgs: map[string]any{
				"query": "repo:github/github-mcp-server author:octocat",
				"owner": "different-owner",
				"repo":  "different-repo",
			},
			expectError:    false,
			expectedResult: mockSearchResult,
		},
		{
			name: "complex query with existing is:pr filter and OR operators",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				GetSearchIssues: expectQueryParams(
					t,
					map[string]string{
						"q":        "is:pr repo:github/github-mcp-server (label:bug OR label:enhancement OR label:feature)",
						"page":     "1",
						"per_page": "30",
					},
				).andThen(
					mockResponse(t, http.StatusOK, mockSearchResult),
				),
			}),
			requestArgs: map[string]any{
				"query": "is:pr repo:github/github-mcp-server (label:bug OR label:enhancement OR label:feature)",
			},
			expectError:    false,
			expectedResult: mockSearchResult,
		},
		{
			name: "search pull requests fails",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				GetSearchIssues: func(w http.ResponseWriter, _ *http.Request) {
					w.WriteHeader(http.StatusBadRequest)
					_, _ = w.Write([]byte(`{"message": "Validation Failed"}`))
				},
			}),
			requestArgs: map[string]any{
				"query": "invalid:query",
			},
			expectError:    true,
			expectedErrMsg: "failed to search pull requests",
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			// Setup client with mock
			client := mustNewGHClient(t, tc.mockedClient)
			serverTool := SearchPullRequests(translations.NullTranslationHelper)
			deps := BaseDeps{
				Client: client,
			}
			handler := serverTool.Handler(deps)

			// Create call request
			request := createMCPRequest(tc.requestArgs)

			// Call handler
			result, err := handler(ContextWithDeps(context.Background(), deps), &request)

			// Verify results
			if tc.expectError {
				require.NoError(t, err)
				require.NotNil(t, result)
				require.True(t, result.IsError)
				textContent := getErrorResult(t, result)
				assert.Contains(t, textContent.Text, tc.expectedErrMsg)
				return
			}

			require.NoError(t, err)

			// Parse the result and get the text content if no error
			textContent := getTextResult(t, result)

			// Unmarshal and verify the result
			var returnedResult github.IssuesSearchResult
			err = json.Unmarshal([]byte(textContent.Text), &returnedResult)
			require.NoError(t, err)
			assert.Equal(t, *tc.expectedResult.Total, *returnedResult.Total)
			assert.Equal(t, *tc.expectedResult.IncompleteResults, *returnedResult.IncompleteResults)
			assert.Len(t, returnedResult.Issues, len(tc.expectedResult.Issues))
			for i, issue := range returnedResult.Issues {
				assert.Equal(t, *tc.expectedResult.Issues[i].Number, *issue.Number)
				assert.Equal(t, *tc.expectedResult.Issues[i].Title, *issue.Title)
				assert.Equal(t, *tc.expectedResult.Issues[i].State, *issue.State)
				assert.Equal(t, *tc.expectedResult.Issues[i].HTMLURL, *issue.HTMLURL)
				assert.Equal(t, *tc.expectedResult.Issues[i].User.Login, *issue.User.Login)
			}
		})
	}

}

func Test_GetPullRequestFiles(t *testing.T) {
	// Verify tool definition once
	serverTool := PullRequestRead(translations.NullTranslationHelper)
	tool := serverTool.Tool
	require.NoError(t, toolsnaps.Test(tool.Name, tool))

	assert.Equal(t, "pull_request_read", tool.Name)
	assert.NotEmpty(t, tool.Description)
	schema := tool.InputSchema.(*jsonschema.Schema)
	assert.Contains(t, schema.Properties, "method")
	assert.Contains(t, schema.Properties, "owner")
	assert.Contains(t, schema.Properties, "repo")
	assert.Contains(t, schema.Properties, "pullNumber")
	assert.Contains(t, schema.Properties, "page")
	assert.Contains(t, schema.Properties, "perPage")
	assert.ElementsMatch(t, schema.Required, []string{"method", "owner", "repo", "pullNumber"})

	// Setup mock PR files for success case
	mockFiles := []*github.CommitFile{
		{
			Filename:  github.Ptr("file1.go"),
			Status:    github.Ptr("modified"),
			Additions: github.Ptr(10),
			Deletions: github.Ptr(5),
			Changes:   github.Ptr(15),
			Patch:     github.Ptr("@@ -1,5 +1,10 @@"),
		},
		{
			Filename:  github.Ptr("file2.go"),
			Status:    github.Ptr("added"),
			Additions: github.Ptr(20),
			Deletions: github.Ptr(0),
			Changes:   github.Ptr(20),
			Patch:     github.Ptr("@@ -0,0 +1,20 @@"),
		},
	}

	tests := []struct {
		name           string
		mockedClient   *http.Client
		requestArgs    map[string]any
		expectError    bool
		expectedFiles  []*github.CommitFile
		expectedErrMsg string
	}{
		{
			name: "successful files fetch",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				GetReposPullsFilesByOwnerByRepoByPullNumber: expectQueryParams(t, map[string]string{
					"page":     "1",
					"per_page": "30",
				}).andThen(
					mockResponse(t, http.StatusOK, mockFiles),
				),
			}),
			requestArgs: map[string]any{
				"method":     "get_files",
				"owner":      "owner",
				"repo":       "repo",
				"pullNumber": float64(42),
			},
			expectError:   false,
			expectedFiles: mockFiles,
		},
		{
			name: "successful files fetch with pagination",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				GetReposPullsFilesByOwnerByRepoByPullNumber: expectQueryParams(t, map[string]string{
					"page":     "2",
					"per_page": "10",
				}).andThen(
					mockResponse(t, http.StatusOK, mockFiles),
				),
			}),
			requestArgs: map[string]any{
				"method":     "get_files",
				"owner":      "owner",
				"repo":       "repo",
				"pullNumber": float64(42),
				"page":       float64(2),
				"perPage":    float64(10),
			},
			expectError:   false,
			expectedFiles: mockFiles,
		},
		{
			name: "files fetch fails",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				GetReposPullsFilesByOwnerByRepoByPullNumber: expectQueryParams(t, map[string]string{
					"page":     "1",
					"per_page": "30",
				}).andThen(
					http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
						w.WriteHeader(http.StatusNotFound)
						_, _ = w.Write([]byte(`{"message": "Not Found"}`))
					}),
				),
			}),
			requestArgs: map[string]any{
				"method":     "get_files",
				"owner":      "owner",
				"repo":       "repo",
				"pullNumber": float64(999),
			},
			expectError:    true,
			expectedErrMsg: "failed to get pull request files",
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			// Setup client with mock
			client := mustNewGHClient(t, tc.mockedClient)
			serverTool := PullRequestRead(translations.NullTranslationHelper)
			deps := BaseDeps{
				Client:          client,
				RepoAccessCache: stubRepoAccessCache(nil, 5*time.Minute),
				Flags:           stubFeatureFlags(map[string]bool{"lockdown-mode": false}),
			}
			handler := serverTool.Handler(deps)

			// Create call request
			request := createMCPRequest(tc.requestArgs)

			// Call handler
			result, err := handler(ContextWithDeps(context.Background(), deps), &request)

			// Verify results
			if tc.expectError {
				require.NoError(t, err)
				require.True(t, result.IsError)
				errorContent := getErrorResult(t, result)
				assert.Contains(t, errorContent.Text, tc.expectedErrMsg)
				return
			}

			require.NoError(t, err)
			require.False(t, result.IsError)

			// Parse the result and get the text content if no error
			textContent := getTextResult(t, result)

			// Unmarshal and verify the result
			var returnedFiles []MinimalPRFile
			err = json.Unmarshal([]byte(textContent.Text), &returnedFiles)
			require.NoError(t, err)
			assert.Len(t, returnedFiles, len(tc.expectedFiles))
			for i, file := range returnedFiles {
				assert.Equal(t, tc.expectedFiles[i].GetFilename(), file.Filename)
				assert.Equal(t, tc.expectedFiles[i].GetStatus(), file.Status)
				assert.Equal(t, tc.expectedFiles[i].GetAdditions(), file.Additions)
				assert.Equal(t, tc.expectedFiles[i].GetDeletions(), file.Deletions)
			}
		})
	}
}

func Test_GetPullRequestCommits(t *testing.T) {
	// Verify tool definition once
	serverTool := PullRequestRead(translations.NullTranslationHelper)
	tool := serverTool.Tool
	require.NoError(t, toolsnaps.Test(tool.Name, tool))

	assert.Equal(t, "pull_request_read", tool.Name)
	assert.NotEmpty(t, tool.Description)
	schema := tool.InputSchema.(*jsonschema.Schema)
	assert.Contains(t, schema.Properties, "method")
	assert.Contains(t, schema.Properties, "owner")
	assert.Contains(t, schema.Properties, "repo")
	assert.Contains(t, schema.Properties, "pullNumber")
	assert.Contains(t, schema.Properties, "page")
	assert.Contains(t, schema.Properties, "perPage")
	assert.ElementsMatch(t, schema.Required, []string{"method", "owner", "repo", "pullNumber"})

	authorDate := time.Date(2026, 4, 1, 12, 0, 0, 0, time.UTC)
	mockCommits := []*github.RepositoryCommit{
		{
			SHA:     github.Ptr("abc123def456"),
			HTMLURL: github.Ptr("https://github.com/owner/repo/commit/abc123def456"),
			Commit: &github.Commit{
				Message: github.Ptr("feat: add commit listing"),
				Author: &github.CommitAuthor{
					Name:  github.Ptr("Test User"),
					Email: github.Ptr("test@example.com"),
					Date:  &github.Timestamp{Time: authorDate},
				},
				Committer: &github.CommitAuthor{
					Name:  github.Ptr("Merge Bot"),
					Email: github.Ptr("merge@example.com"),
					Date:  &github.Timestamp{Time: authorDate.Add(30 * time.Minute)},
				},
			},
			Author: &github.User{
				Login:     github.Ptr("test-user"),
				ID:        github.Ptr(int64(12345)),
				HTMLURL:   github.Ptr("https://github.com/test-user"),
				AvatarURL: github.Ptr("https://github.com/test-user.png"),
			},
			Committer: &github.User{
				Login:     github.Ptr("merge-bot"),
				ID:        github.Ptr(int64(67890)),
				HTMLURL:   github.Ptr("https://github.com/merge-bot"),
				AvatarURL: github.Ptr("https://github.com/merge-bot.png"),
			},
		},
		{
			SHA:     github.Ptr("def456abc789"),
			HTMLURL: github.Ptr("https://github.com/owner/repo/commit/def456abc789"),
			Commit: &github.Commit{
				Message: github.Ptr("fix: handle pagination"),
			},
		},
	}

	tests := []struct {
		name            string
		mockedClient    *http.Client
		requestArgs     map[string]any
		expectError     bool
		expectedCommits []*github.RepositoryCommit
		expectedErrMsg  string
	}{
		{
			name: "successful commits fetch",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				GetReposPullsCommitsByOwnerByRepoByPullNumber: expectQueryParams(t, map[string]string{
					"page":     "1",
					"per_page": "30",
				}).andThen(
					mockResponse(t, http.StatusOK, mockCommits),
				),
			}),
			requestArgs: map[string]any{
				"method":     "get_commits",
				"owner":      "owner",
				"repo":       "repo",
				"pullNumber": float64(42),
			},
			expectError:     false,
			expectedCommits: mockCommits,
		},
		{
			name: "successful commits fetch with pagination",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				GetReposPullsCommitsByOwnerByRepoByPullNumber: expectQueryParams(t, map[string]string{
					"page":     "2",
					"per_page": "10",
				}).andThen(
					mockResponse(t, http.StatusOK, mockCommits),
				),
			}),
			requestArgs: map[string]any{
				"method":     "get_commits",
				"owner":      "owner",
				"repo":       "repo",
				"pullNumber": float64(42),
				"page":       float64(2),
				"perPage":    float64(10),
			},
			expectError:     false,
			expectedCommits: mockCommits,
		},
		{
			name: "commits fetch fails",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				GetReposPullsCommitsByOwnerByRepoByPullNumber: expectQueryParams(t, map[string]string{
					"page":     "1",
					"per_page": "30",
				}).andThen(
					http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
						w.WriteHeader(http.StatusNotFound)
						_, _ = w.Write([]byte(`{"message": "Not Found"}`))
					}),
				),
			}),
			requestArgs: map[string]any{
				"method":     "get_commits",
				"owner":      "owner",
				"repo":       "repo",
				"pullNumber": float64(999),
			},
			expectError:    true,
			expectedErrMsg: "failed to get pull request commits",
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			client := mustNewGHClient(t, tc.mockedClient)
			serverTool := PullRequestRead(translations.NullTranslationHelper)
			deps := BaseDeps{
				Client:          client,
				RepoAccessCache: stubRepoAccessCache(nil, 5*time.Minute),
				Flags:           stubFeatureFlags(map[string]bool{"lockdown-mode": false}),
			}
			handler := serverTool.Handler(deps)
			request := createMCPRequest(tc.requestArgs)

			result, err := handler(ContextWithDeps(context.Background(), deps), &request)

			if tc.expectError {
				require.NoError(t, err)
				require.True(t, result.IsError)
				errorContent := getErrorResult(t, result)
				assert.Contains(t, errorContent.Text, tc.expectedErrMsg)
				return
			}

			require.NoError(t, err)
			require.False(t, result.IsError)

			textContent := getTextResult(t, result)
			assert.NotContains(t, textContent.Text, `"committer"`)
			assert.NotContains(t, textContent.Text, `"profile_url"`)

			var returnedCommits []MinimalPullRequestCommit
			err = json.Unmarshal([]byte(textContent.Text), &returnedCommits)
			require.NoError(t, err)
			assert.Len(t, returnedCommits, len(tc.expectedCommits))
			for i, commit := range returnedCommits {
				assert.Equal(t, tc.expectedCommits[i].GetSHA(), commit.SHA)
				assert.Equal(t, tc.expectedCommits[i].GetHTMLURL(), commit.HTMLURL)
				assert.Equal(t, tc.expectedCommits[i].GetCommit().GetMessage(), commit.Message)
			}

			assert.Equal(t, authorDate.Format(time.RFC3339), returnedCommits[0].Author.Date)
		})
	}
}

func Test_ConvertToMinimalPullRequestCommitsSkipsNilCommit(t *testing.T) {
	commits := convertToMinimalPullRequestCommits([]*github.RepositoryCommit{nil})

	require.Empty(t, commits)
}

func Test_GetPullRequestStatus(t *testing.T) {
	// Verify tool definition once
	serverTool := PullRequestRead(translations.NullTranslationHelper)
	tool := serverTool.Tool
	require.NoError(t, toolsnaps.Test(tool.Name, tool))

	assert.Equal(t, "pull_request_read", tool.Name)
	assert.NotEmpty(t, tool.Description)
	schema := tool.InputSchema.(*jsonschema.Schema)
	assert.Contains(t, schema.Properties, "method")
	assert.Contains(t, schema.Properties, "owner")
	assert.Contains(t, schema.Properties, "repo")
	assert.Contains(t, schema.Properties, "pullNumber")
	assert.ElementsMatch(t, schema.Required, []string{"method", "owner", "repo", "pullNumber"})

	// Setup mock PR for successful PR fetch
	mockPR := &github.PullRequest{
		Number:  github.Ptr(42),
		Title:   github.Ptr("Test PR"),
		HTMLURL: github.Ptr("https://github.com/owner/repo/pull/42"),
		Head: &github.PullRequestBranch{
			SHA: github.Ptr("abcd1234"),
			Ref: github.Ptr("feature-branch"),
		},
	}

	// Setup mock status for success case
	mockStatus := &github.CombinedStatus{
		State:      github.Ptr("success"),
		TotalCount: github.Ptr(3),
		Statuses: []*github.RepoStatus{
			{
				State:       github.Ptr("success"),
				Context:     github.Ptr("continuous-integration/travis-ci"),
				Description: github.Ptr("Build succeeded"),
				TargetURL:   github.Ptr("https://travis-ci.org/owner/repo/builds/123"),
			},
			{
				State:       github.Ptr("success"),
				Context:     github.Ptr("codecov/patch"),
				Description: github.Ptr("Coverage increased"),
				TargetURL:   github.Ptr("https://codecov.io/gh/owner/repo/pull/42"),
			},
			{
				State:       github.Ptr("success"),
				Context:     github.Ptr("lint/golangci-lint"),
				Description: github.Ptr("No issues found"),
				TargetURL:   github.Ptr("https://golangci.com/r/owner/repo/pull/42"),
			},
		},
	}

	tests := []struct {
		name           string
		mockedClient   *http.Client
		requestArgs    map[string]any
		expectError    bool
		expectedStatus *github.CombinedStatus
		expectedErrMsg string
	}{
		{
			name: "successful status fetch",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				GetReposPullsByOwnerByRepoByPullNumber:  mockResponse(t, http.StatusOK, mockPR),
				GetReposCommitsStatusByOwnerByRepoByRef: mockResponse(t, http.StatusOK, mockStatus),
			}),
			requestArgs: map[string]any{
				"method":     "get_status",
				"owner":      "owner",
				"repo":       "repo",
				"pullNumber": float64(42),
			},
			expectError:    false,
			expectedStatus: mockStatus,
		},
		{
			name: "PR fetch fails",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				GetReposPullsByOwnerByRepoByPullNumber: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
					w.WriteHeader(http.StatusNotFound)
					_, _ = w.Write([]byte(`{"message": "Not Found"}`))
				}),
			}),
			requestArgs: map[string]any{
				"method":     "get_status",
				"owner":      "owner",
				"repo":       "repo",
				"pullNumber": float64(999),
			},
			expectError:    true,
			expectedErrMsg: "failed to get pull request",
		},
		{
			name: "status fetch fails",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				GetReposPullsByOwnerByRepoByPullNumber: mockResponse(t, http.StatusOK, mockPR),
				GetReposCommitsStatusesByOwnerByRepoByRef: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
					w.WriteHeader(http.StatusNotFound)
					_, _ = w.Write([]byte(`{"message": "Not Found"}`))
				}),
			}),
			requestArgs: map[string]any{
				"method":     "get_status",
				"owner":      "owner",
				"repo":       "repo",
				"pullNumber": float64(42),
			},
			expectError:    true,
			expectedErrMsg: "failed to get combined status",
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			// Setup client with mock
			client := mustNewGHClient(t, tc.mockedClient)
			serverTool := PullRequestRead(translations.NullTranslationHelper)
			deps := BaseDeps{
				Client:          client,
				RepoAccessCache: stubRepoAccessCache(nil, 5*time.Minute),
				Flags:           stubFeatureFlags(map[string]bool{"lockdown-mode": false}),
			}
			handler := serverTool.Handler(deps)

			// Create call request
			request := createMCPRequest(tc.requestArgs)

			// Call handler
			result, err := handler(ContextWithDeps(context.Background(), deps), &request)

			// Verify results
			if tc.expectError {
				require.NoError(t, err)
				require.True(t, result.IsError)
				errorContent := getErrorResult(t, result)
				assert.Contains(t, errorContent.Text, tc.expectedErrMsg)
				return
			}

			require.NoError(t, err)
			require.False(t, result.IsError)

			// Parse the result and get the text content if no error
			textContent := getTextResult(t, result)

			// Unmarshal and verify the result
			var returnedStatus github.CombinedStatus
			err = json.Unmarshal([]byte(textContent.Text), &returnedStatus)
			require.NoError(t, err)
			assert.Equal(t, *tc.expectedStatus.State, *returnedStatus.State)
			assert.Equal(t, *tc.expectedStatus.TotalCount, *returnedStatus.TotalCount)
			assert.Len(t, returnedStatus.Statuses, len(tc.expectedStatus.Statuses))
			for i, status := range returnedStatus.Statuses {
				assert.Equal(t, *tc.expectedStatus.Statuses[i].State, *status.State)
				assert.Equal(t, *tc.expectedStatus.Statuses[i].Context, *status.Context)
				assert.Equal(t, *tc.expectedStatus.Statuses[i].Description, *status.Description)
			}
		})
	}
}

func Test_GetPullRequestCheckRuns(t *testing.T) {
	// Verify tool definition once
	serverTool := PullRequestRead(translations.NullTranslationHelper)
	tool := serverTool.Tool
	require.NoError(t, toolsnaps.Test(tool.Name, tool))

	assert.Equal(t, "pull_request_read", tool.Name)
	assert.NotEmpty(t, tool.Description)
	schema := tool.InputSchema.(*jsonschema.Schema)
	assert.Contains(t, schema.Properties, "method")
	assert.Contains(t, schema.Properties, "owner")
	assert.Contains(t, schema.Properties, "repo")
	assert.Contains(t, schema.Properties, "pullNumber")
	assert.ElementsMatch(t, schema.Required, []string{"method", "owner", "repo", "pullNumber"})

	// Setup mock PR for successful PR fetch
	mockPR := &github.PullRequest{
		Number:  github.Ptr(42),
		Title:   github.Ptr("Test PR"),
		HTMLURL: github.Ptr("https://github.com/owner/repo/pull/42"),
		Head: &github.PullRequestBranch{
			SHA: github.Ptr("abcd1234"),
			Ref: github.Ptr("feature-branch"),
		},
	}

	// Setup mock check runs for success case
	mockCheckRuns := &github.ListCheckRunsResults{
		Total: github.Ptr(2),
		CheckRuns: []*github.CheckRun{
			{
				ID:         github.Ptr(int64(1)),
				Name:       github.Ptr("build"),
				Status:     github.Ptr("completed"),
				Conclusion: github.Ptr("success"),
				HTMLURL:    github.Ptr("https://github.com/owner/repo/runs/1"),
			},
			{
				ID:         github.Ptr(int64(2)),
				Name:       github.Ptr("test"),
				Status:     github.Ptr("completed"),
				Conclusion: github.Ptr("success"),
				HTMLURL:    github.Ptr("https://github.com/owner/repo/runs/2"),
			},
		},
	}

	tests := []struct {
		name              string
		mockedClient      *http.Client
		requestArgs       map[string]any
		expectError       bool
		expectedCheckRuns *github.ListCheckRunsResults
		expectedErrMsg    string
	}{
		{
			name: "successful check runs fetch",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				GetReposPullsByOwnerByRepoByPullNumber:     mockResponse(t, http.StatusOK, mockPR),
				GetReposCommitsCheckRunsByOwnerByRepoByRef: mockResponse(t, http.StatusOK, mockCheckRuns),
			}),
			requestArgs: map[string]any{
				"method":     "get_check_runs",
				"owner":      "owner",
				"repo":       "repo",
				"pullNumber": float64(42),
			},
			expectError:       false,
			expectedCheckRuns: mockCheckRuns,
		},
		{
			name: "PR fetch fails",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				GetReposPullsByOwnerByRepoByPullNumber: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
					w.WriteHeader(http.StatusNotFound)
					_, _ = w.Write([]byte(`{"message": "Not Found"}`))
				}),
			}),
			requestArgs: map[string]any{
				"method":     "get_check_runs",
				"owner":      "owner",
				"repo":       "repo",
				"pullNumber": float64(999),
			},
			expectError:    true,
			expectedErrMsg: "failed to get pull request",
		},
		{
			name: "check runs fetch fails",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				GetReposPullsByOwnerByRepoByPullNumber: mockResponse(t, http.StatusOK, mockPR),
				GetReposCommitsCheckRunsByOwnerByRepoByRef: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
					w.WriteHeader(http.StatusNotFound)
					_, _ = w.Write([]byte(`{"message": "Not Found"}`))
				}),
			}),
			requestArgs: map[string]any{
				"method":     "get_check_runs",
				"owner":      "owner",
				"repo":       "repo",
				"pullNumber": float64(42),
			},
			expectError:    true,
			expectedErrMsg: "failed to get check runs",
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			// Setup client with mock
			client := mustNewGHClient(t, tc.mockedClient)
			serverTool := PullRequestRead(translations.NullTranslationHelper)
			deps := BaseDeps{
				Client:          client,
				RepoAccessCache: stubRepoAccessCache(nil, 5*time.Minute),
				Flags:           stubFeatureFlags(map[string]bool{"lockdown-mode": false}),
			}
			handler := serverTool.Handler(deps)

			// Create call request
			request := createMCPRequest(tc.requestArgs)

			// Call handler
			result, err := handler(ContextWithDeps(context.Background(), deps), &request)

			// Verify results
			if tc.expectError {
				require.NoError(t, err)
				require.True(t, result.IsError)
				errorContent := getErrorResult(t, result)
				assert.Contains(t, errorContent.Text, tc.expectedErrMsg)
				return
			}

			require.NoError(t, err)
			require.False(t, result.IsError)

			// Parse the result and get the text content if no error
			textContent := getTextResult(t, result)

			// Unmarshal and verify the result (using minimal type)
			var returnedCheckRuns MinimalCheckRunsResult
			err = json.Unmarshal([]byte(textContent.Text), &returnedCheckRuns)
			require.NoError(t, err)
			assert.Equal(t, *tc.expectedCheckRuns.Total, returnedCheckRuns.TotalCount)
			assert.Len(t, returnedCheckRuns.CheckRuns, len(tc.expectedCheckRuns.CheckRuns))
			for i, checkRun := range returnedCheckRuns.CheckRuns {
				assert.Equal(t, *tc.expectedCheckRuns.CheckRuns[i].Name, checkRun.Name)
				assert.Equal(t, *tc.expectedCheckRuns.CheckRuns[i].Status, checkRun.Status)
				assert.Equal(t, *tc.expectedCheckRuns.CheckRuns[i].Conclusion, checkRun.Conclusion)
			}
		})
	}
}

func Test_UpdatePullRequestBranch(t *testing.T) {
	// Verify tool definition once
	serverTool := UpdatePullRequestBranch(translations.NullTranslationHelper)
	tool := serverTool.Tool
	require.NoError(t, toolsnaps.Test(tool.Name, tool))

	assert.Equal(t, "update_pull_request_branch", tool.Name)
	assert.NotEmpty(t, tool.Description)
	schema := tool.InputSchema.(*jsonschema.Schema)
	assert.Contains(t, schema.Properties, "owner")
	assert.Contains(t, schema.Properties, "repo")
	assert.Contains(t, schema.Properties, "pullNumber")
	assert.Contains(t, schema.Properties, "expectedHeadSha")
	assert.ElementsMatch(t, schema.Required, []string{"owner", "repo", "pullNumber"})

	// Setup mock update result for success case
	mockUpdateResult := &github.PullRequestBranchUpdateResponse{
		Message: github.Ptr("Branch was updated successfully"),
		URL:     github.Ptr("https://api.github.com/repos/owner/repo/pulls/42"),
	}

	tests := []struct {
		name                 string
		mockedClient         *http.Client
		requestArgs          map[string]any
		expectError          bool
		expectedUpdateResult *github.PullRequestBranchUpdateResponse
		expectedErrMsg       string
	}{
		{
			name: "successful branch update",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				PutReposPullsUpdateBranchByOwnerByRepoByPullNumber: expectRequestBody(t, map[string]any{
					"expected_head_sha": "abcd1234",
				}).andThen(
					mockResponse(t, http.StatusAccepted, mockUpdateResult),
				),
			}),
			requestArgs: map[string]any{
				"owner":           "owner",
				"repo":            "repo",
				"pullNumber":      float64(42),
				"expectedHeadSha": "abcd1234",
			},
			expectError:          false,
			expectedUpdateResult: mockUpdateResult,
		},
		{
			name: "branch update without expected SHA",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				PutReposPullsUpdateBranchByOwnerByRepoByPullNumber: expectRequestBody(t, map[string]any{}).andThen(
					mockResponse(t, http.StatusAccepted, mockUpdateResult),
				),
			}),
			requestArgs: map[string]any{
				"owner":      "owner",
				"repo":       "repo",
				"pullNumber": float64(42),
			},
			expectError:          false,
			expectedUpdateResult: mockUpdateResult,
		},
		{
			name: "branch update fails",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				PutReposPullsUpdateBranchByOwnerByRepoByPullNumber: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
					w.WriteHeader(http.StatusConflict)
					_, _ = w.Write([]byte(`{"message": "Merge conflict"}`))
				}),
			}),
			requestArgs: map[string]any{
				"owner":      "owner",
				"repo":       "repo",
				"pullNumber": float64(42),
			},
			expectError:    true,
			expectedErrMsg: "failed to update pull request branch",
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			// Setup client with mock
			client := mustNewGHClient(t, tc.mockedClient)
			serverTool := UpdatePullRequestBranch(translations.NullTranslationHelper)
			deps := BaseDeps{
				Client: client,
			}
			handler := serverTool.Handler(deps)

			// Create call request
			request := createMCPRequest(tc.requestArgs)

			// Call handler
			result, err := handler(ContextWithDeps(context.Background(), deps), &request)

			// Verify results
			if tc.expectError {
				require.NoError(t, err)
				require.True(t, result.IsError)
				errorContent := getErrorResult(t, result)
				assert.Contains(t, errorContent.Text, tc.expectedErrMsg)
				return
			}

			require.NoError(t, err)
			require.False(t, result.IsError)

			// Parse the result and get the text content if no error
			textContent := getTextResult(t, result)

			assert.Contains(t, textContent.Text, "is in progress")
		})
	}
}

func Test_GetPullRequestComments(t *testing.T) {
	// Verify tool definition once
	serverTool := PullRequestRead(translations.NullTranslationHelper)
	tool := serverTool.Tool
	require.NoError(t, toolsnaps.Test(tool.Name, tool))

	assert.Equal(t, "pull_request_read", tool.Name)
	assert.NotEmpty(t, tool.Description)
	schema := tool.InputSchema.(*jsonschema.Schema)
	assert.Contains(t, schema.Properties, "method")
	assert.Contains(t, schema.Properties, "owner")
	assert.Contains(t, schema.Properties, "repo")
	assert.Contains(t, schema.Properties, "pullNumber")
	// `after` is required for cursor-based pagination on get_review_comments
	// to be reachable from MCP clients; without it in the schema, callers
	// cannot advance past the first page (issue #2122).
	assert.Contains(t, schema.Properties, "after")
	assert.Equal(t, "string", schema.Properties["after"].Type)
	assert.ElementsMatch(t, schema.Required, []string{"method", "owner", "repo", "pullNumber"})

	tests := []struct {
		name            string
		gqlHTTPClient   *http.Client
		requestArgs     map[string]any
		expectError     bool
		expectedErrMsg  string
		lockdownEnabled bool
		validateResult  func(t *testing.T, textContent string)
	}{
		{
			name: "successful review threads fetch",
			gqlHTTPClient: githubv4mock.NewMockedHTTPClient(
				githubv4mock.NewQueryMatcher(
					reviewThreadsQuery{},
					map[string]any{
						"owner":             githubv4.String("owner"),
						"repo":              githubv4.String("repo"),
						"prNum":             githubv4.Int(42),
						"first":             githubv4.Int(30),
						"commentsPerThread": githubv4.Int(100),
						"after":             (*githubv4.String)(nil),
					},
					githubv4mock.DataResponse(map[string]any{
						"repository": map[string]any{
							"pullRequest": map[string]any{
								"reviewThreads": map[string]any{
									"nodes": []map[string]any{
										{
											"id":          "RT_kwDOA0xdyM4AX1Yz",
											"isResolved":  false,
											"isOutdated":  false,
											"isCollapsed": false,
											"comments": map[string]any{
												"totalCount": 2,
												"nodes": []map[string]any{
													{
														"id":   "PRRC_kwDOA0xdyM4AX1Y0",
														"body": "This looks good",
														"path": "file1.go",
														"line": 5,
														"author": map[string]any{
															"login": "reviewer1",
														},
														"createdAt": "2024-01-01T12:00:00Z",
														"updatedAt": "2024-01-01T12:00:00Z",
														"url":       "https://github.com/owner/repo/pull/42#discussion_r101",
													},
													{
														"id":   "PRRC_kwDOA0xdyM4AX1Y1",
														"body": "Please fix this",
														"path": "file1.go",
														"line": 10,
														"author": map[string]any{
															"login": "reviewer2",
														},
														"createdAt": "2024-01-01T13:00:00Z",
														"updatedAt": "2024-01-01T13:00:00Z",
														"url":       "https://github.com/owner/repo/pull/42#discussion_r102",
													},
												},
											},
										},
									},
									"pageInfo": map[string]any{
										"hasNextPage":     false,
										"hasPreviousPage": false,
										"startCursor":     "cursor1",
										"endCursor":       "cursor2",
									},
									"totalCount": 1,
								},
							},
						},
					}),
				),
			),
			requestArgs: map[string]any{
				"method":     "get_review_comments",
				"owner":      "owner",
				"repo":       "repo",
				"pullNumber": float64(42),
			},
			expectError: false,
			validateResult: func(t *testing.T, textContent string) {
				var result MinimalReviewThreadsResponse
				err := json.Unmarshal([]byte(textContent), &result)
				require.NoError(t, err)

				// Validate review threads
				assert.Len(t, result.ReviewThreads, 1)

				thread := result.ReviewThreads[0]
				assert.Equal(t, false, thread.IsResolved)
				assert.Equal(t, false, thread.IsOutdated)
				assert.Equal(t, false, thread.IsCollapsed)

				// Validate comments within thread
				assert.Len(t, thread.Comments, 2)

				// Validate first comment
				comment1 := thread.Comments[0]
				assert.Equal(t, "This looks good", comment1.Body)
				assert.Equal(t, "file1.go", comment1.Path)
				assert.Equal(t, "reviewer1", comment1.Author)

				// Validate pagination info
				assert.Equal(t, false, result.PageInfo.HasNextPage)
				assert.Equal(t, false, result.PageInfo.HasPreviousPage)
				assert.Equal(t, "cursor1", result.PageInfo.StartCursor)
				assert.Equal(t, "cursor2", result.PageInfo.EndCursor)

				// Validate total count
				assert.Equal(t, 1, result.TotalCount)
			},
		},
		{
			name: "after cursor is forwarded to GraphQL query",
			gqlHTTPClient: githubv4mock.NewMockedHTTPClient(
				githubv4mock.NewQueryMatcher(
					reviewThreadsQuery{},
					map[string]any{
						"owner":             githubv4.String("owner"),
						"repo":              githubv4.String("repo"),
						"prNum":             githubv4.Int(42),
						"first":             githubv4.Int(30),
						"commentsPerThread": githubv4.Int(100),
						"after":             githubv4.String("cursor-page-2"),
					},
					githubv4mock.DataResponse(map[string]any{
						"repository": map[string]any{
							"pullRequest": map[string]any{
								"reviewThreads": map[string]any{
									"nodes": []map[string]any{},
									"pageInfo": map[string]any{
										"hasNextPage":     false,
										"hasPreviousPage": true,
										"startCursor":     "cursor3",
										"endCursor":       "cursor4",
									},
									"totalCount": 5,
								},
							},
						},
					}),
				),
			),
			requestArgs: map[string]any{
				"method":     "get_review_comments",
				"owner":      "owner",
				"repo":       "repo",
				"pullNumber": float64(42),
				"after":      "cursor-page-2",
			},
			expectError: false,
			validateResult: func(t *testing.T, textContent string) {
				var result MinimalReviewThreadsResponse
				err := json.Unmarshal([]byte(textContent), &result)
				require.NoError(t, err)
				assert.Len(t, result.ReviewThreads, 0)
				assert.Equal(t, true, result.PageInfo.HasPreviousPage)
				assert.Equal(t, "cursor4", result.PageInfo.EndCursor)
			},
		},
		{
			name: "review threads fetch fails",
			gqlHTTPClient: githubv4mock.NewMockedHTTPClient(
				githubv4mock.NewQueryMatcher(
					reviewThreadsQuery{},
					map[string]any{
						"owner":             githubv4.String("owner"),
						"repo":              githubv4.String("repo"),
						"prNum":             githubv4.Int(999),
						"first":             githubv4.Int(30),
						"commentsPerThread": githubv4.Int(100),
						"after":             (*githubv4.String)(nil),
					},
					githubv4mock.ErrorResponse("Could not resolve to a PullRequest with the number of 999."),
				),
			),
			requestArgs: map[string]any{
				"method":     "get_review_comments",
				"owner":      "owner",
				"repo":       "repo",
				"pullNumber": float64(999),
			},
			expectError:    true,
			expectedErrMsg: "failed to get pull request review threads",
		},
		{
			name: "lockdown enabled filters review comments without push access",
			gqlHTTPClient: githubv4mock.NewMockedHTTPClient(
				githubv4mock.NewQueryMatcher(
					reviewThreadsQuery{},
					map[string]any{
						"owner":             githubv4.String("owner"),
						"repo":              githubv4.String("repo"),
						"prNum":             githubv4.Int(42),
						"first":             githubv4.Int(30),
						"commentsPerThread": githubv4.Int(100),
						"after":             (*githubv4.String)(nil),
					},
					githubv4mock.DataResponse(map[string]any{
						"repository": map[string]any{
							"pullRequest": map[string]any{
								"reviewThreads": map[string]any{
									"nodes": []map[string]any{
										{
											"id":          "RT_kwDOA0xdyM4AX1Yz",
											"isResolved":  false,
											"isOutdated":  false,
											"isCollapsed": false,
											"comments": map[string]any{
												"totalCount": 2,
												"nodes": []map[string]any{
													{
														"id":   "PRRC_kwDOA0xdyM4AX1Y0",
														"body": "Maintainer review comment",
														"path": "file1.go",
														"line": 5,
														"author": map[string]any{
															"login": "maintainer",
														},
														"createdAt": "2024-01-01T12:00:00Z",
														"updatedAt": "2024-01-01T12:00:00Z",
														"url":       "https://github.com/owner/repo/pull/42#discussion_r2010",
													},
													{
														"id":   "PRRC_kwDOA0xdyM4AX1Y1",
														"body": "External review comment",
														"path": "file1.go",
														"line": 10,
														"author": map[string]any{
															"login": "testuser",
														},
														"createdAt": "2024-01-01T13:00:00Z",
														"updatedAt": "2024-01-01T13:00:00Z",
														"url":       "https://github.com/owner/repo/pull/42#discussion_r2011",
													},
												},
											},
										},
									},
									"pageInfo": map[string]any{
										"hasNextPage":     false,
										"hasPreviousPage": false,
										"startCursor":     "cursor1",
										"endCursor":       "cursor2",
									},
									"totalCount": 1,
								},
							},
						},
					}),
				),
			),
			requestArgs: map[string]any{
				"method":     "get_review_comments",
				"owner":      "owner",
				"repo":       "repo",
				"pullNumber": float64(42),
			},
			expectError:     false,
			lockdownEnabled: true,
			validateResult: func(t *testing.T, textContent string) {
				var result MinimalReviewThreadsResponse
				err := json.Unmarshal([]byte(textContent), &result)
				require.NoError(t, err)

				// Validate that only maintainer comment is returned
				assert.Len(t, result.ReviewThreads, 1)

				thread := result.ReviewThreads[0]

				// Should only have 1 comment (maintainer) after filtering
				assert.Equal(t, 1, thread.TotalCount)
				assert.Len(t, thread.Comments, 1)

				comment := thread.Comments[0]
				assert.Equal(t, "maintainer", comment.Author)
				assert.Equal(t, "Maintainer review comment", comment.Body)
			},
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			// Setup GraphQL client with mock
			var gqlClient *githubv4.Client
			if tc.gqlHTTPClient != nil {
				gqlClient = githubv4.NewClient(tc.gqlHTTPClient)
			} else {
				gqlClient = githubv4.NewClient(nil)
			}

			// Setup cache for lockdown mode
			var restClient *github.Client
			if tc.lockdownEnabled {
				restClient = mockRESTPermissionServer(t, "read", map[string]string{
					"maintainer":    "write",
					"external-user": "read",
					"testuser":      "read",
				})
			}
			cache := stubRepoAccessCache(restClient, 5*time.Minute)

			flags := stubFeatureFlags(map[string]bool{"lockdown-mode": tc.lockdownEnabled})
			serverTool := PullRequestRead(translations.NullTranslationHelper)
			deps := BaseDeps{
				Client:          mustNewGHClient(t, nil),
				GQLClient:       gqlClient,
				RepoAccessCache: cache,
				Flags:           flags,
			}
			handler := serverTool.Handler(deps)

			// Create call request
			request := createMCPRequest(tc.requestArgs)

			// Call handler
			result, err := handler(ContextWithDeps(context.Background(), deps), &request)

			// Verify results
			if tc.expectError {
				require.NoError(t, err)
				require.True(t, result.IsError)
				errorContent := getErrorResult(t, result)
				assert.Contains(t, errorContent.Text, tc.expectedErrMsg)
				return
			}

			require.NoError(t, err)
			require.False(t, result.IsError)

			// Parse the result and get the text content if no error
			textContent := getTextResult(t, result)

			// Use custom validation if provided
			if tc.validateResult != nil {
				tc.validateResult(t, textContent.Text)
			}
		})
	}
}

func Test_GetPullRequestReviews(t *testing.T) {
	// Verify tool definition once
	serverTool := PullRequestRead(translations.NullTranslationHelper)
	tool := serverTool.Tool
	require.NoError(t, toolsnaps.Test(tool.Name, tool))

	assert.Equal(t, "pull_request_read", tool.Name)
	assert.NotEmpty(t, tool.Description)
	schema := tool.InputSchema.(*jsonschema.Schema)
	assert.Contains(t, schema.Properties, "method")
	assert.Contains(t, schema.Properties, "owner")
	assert.Contains(t, schema.Properties, "repo")
	assert.Contains(t, schema.Properties, "pullNumber")
	assert.ElementsMatch(t, schema.Required, []string{"method", "owner", "repo", "pullNumber"})

	// Setup mock PR reviews for success case
	mockReviews := []*github.PullRequestReview{
		{
			ID:      github.Ptr(int64(201)),
			State:   github.Ptr("APPROVED"),
			Body:    github.Ptr("LGTM"),
			HTMLURL: github.Ptr("https://github.com/owner/repo/pull/42#pullrequestreview-201"),
			User: &github.User{
				Login: github.Ptr("approver"),
			},
			CommitID:    github.Ptr("abcdef123456"),
			SubmittedAt: &github.Timestamp{Time: time.Now().Add(-24 * time.Hour)},
		},
		{
			ID:      github.Ptr(int64(202)),
			State:   github.Ptr("CHANGES_REQUESTED"),
			Body:    github.Ptr("Please address the following issues"),
			HTMLURL: github.Ptr("https://github.com/owner/repo/pull/42#pullrequestreview-202"),
			User: &github.User{
				Login: github.Ptr("reviewer"),
			},
			CommitID:    github.Ptr("abcdef123456"),
			SubmittedAt: &github.Timestamp{Time: time.Now().Add(-12 * time.Hour)},
		},
	}

	tests := []struct {
		name            string
		mockedClient    *http.Client
		gqlHTTPClient   *http.Client
		requestArgs     map[string]any
		expectError     bool
		expectedReviews []*github.PullRequestReview
		expectedErrMsg  string
		lockdownEnabled bool
	}{
		{
			name: "successful reviews fetch",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				GetReposPullsReviewsByOwnerByRepoByPullNumber: mockResponse(t, http.StatusOK, mockReviews),
			}),
			requestArgs: map[string]any{
				"method":     "get_reviews",
				"owner":      "owner",
				"repo":       "repo",
				"pullNumber": float64(42),
			},
			expectError:     false,
			expectedReviews: mockReviews,
		},
		{
			name: "successful reviews fetch with pagination",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				GetReposPullsReviewsByOwnerByRepoByPullNumber: expectQueryParams(t, map[string]string{
					"page":     "2",
					"per_page": "10",
				}).andThen(
					mockResponse(t, http.StatusOK, mockReviews),
				),
			}),
			requestArgs: map[string]any{
				"method":     "get_reviews",
				"owner":      "owner",
				"repo":       "repo",
				"pullNumber": float64(42),
				"page":       float64(2),
				"perPage":    float64(10),
			},
			expectError:     false,
			expectedReviews: mockReviews,
		},
		{
			name: "reviews fetch fails",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				GetReposPullsReviewsByOwnerByRepoByPullNumber: expectQueryParams(t, map[string]string{
					"page":     "1",
					"per_page": "30",
				}).andThen(
					http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
						w.WriteHeader(http.StatusNotFound)
						_, _ = w.Write([]byte(`{"message": "Not Found"}`))
					}),
				),
			}),
			requestArgs: map[string]any{
				"method":     "get_reviews",
				"owner":      "owner",
				"repo":       "repo",
				"pullNumber": float64(999),
			},
			expectError:    true,
			expectedErrMsg: "failed to get pull request reviews",
		},
		{
			name: "lockdown enabled filters reviews without push access",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				GetReposPullsReviewsByOwnerByRepoByPullNumber: mockResponse(t, http.StatusOK, []*github.PullRequestReview{
					{
						ID:    github.Ptr(int64(2030)),
						State: github.Ptr("APPROVED"),
						Body:  github.Ptr("Maintainer review"),
						User:  &github.User{Login: github.Ptr("maintainer")},
					},
					{
						ID:    github.Ptr(int64(2031)),
						State: github.Ptr("COMMENTED"),
						Body:  github.Ptr("External reviewer"),
						User:  &github.User{Login: github.Ptr("testuser")},
					},
				}),
			}),
			requestArgs: map[string]any{
				"method":     "get_reviews",
				"owner":      "owner",
				"repo":       "repo",
				"pullNumber": float64(42),
			},
			expectError: false,
			expectedReviews: []*github.PullRequestReview{
				{
					ID:    github.Ptr(int64(2030)),
					State: github.Ptr("APPROVED"),
					Body:  github.Ptr("Maintainer review"),
					User:  &github.User{Login: github.Ptr("maintainer")},
				},
			},
			lockdownEnabled: true,
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			// Setup client with mock
			client := mustNewGHClient(t, tc.mockedClient)
			var restClient *github.Client
			if tc.lockdownEnabled {
				restClient = mockRESTPermissionServer(t, "read", map[string]string{
					"maintainer": "write",
					"testuser":   "read",
				})
			}
			cache := stubRepoAccessCache(restClient, 5*time.Minute)
			flags := stubFeatureFlags(map[string]bool{"lockdown-mode": tc.lockdownEnabled})
			serverTool := PullRequestRead(translations.NullTranslationHelper)
			deps := BaseDeps{
				Client:          client,
				RepoAccessCache: cache,
				Flags:           flags,
			}
			handler := serverTool.Handler(deps)

			// Create call request
			request := createMCPRequest(tc.requestArgs)

			// Call handler
			result, err := handler(ContextWithDeps(context.Background(), deps), &request)

			// Verify results
			if tc.expectError {
				require.NoError(t, err)
				require.True(t, result.IsError)
				errorContent := getErrorResult(t, result)
				assert.Contains(t, errorContent.Text, tc.expectedErrMsg)
				return
			}

			require.NoError(t, err)
			require.False(t, result.IsError)

			// Parse the result and get the text content if no error
			textContent := getTextResult(t, result)

			// Unmarshal and verify the result
			var returnedReviews []MinimalPullRequestReview
			err = json.Unmarshal([]byte(textContent.Text), &returnedReviews)
			require.NoError(t, err)
			assert.Len(t, returnedReviews, len(tc.expectedReviews))
			for i, review := range returnedReviews {
				assert.Equal(t, tc.expectedReviews[i].GetID(), review.ID)
				assert.Equal(t, tc.expectedReviews[i].GetState(), review.State)
				assert.Equal(t, tc.expectedReviews[i].GetBody(), review.Body)
				require.NotNil(t, tc.expectedReviews[i].User)
				require.NotNil(t, review.User)
				assert.Equal(t, tc.expectedReviews[i].GetUser().GetLogin(), review.User.Login)
				assert.Equal(t, tc.expectedReviews[i].GetHTMLURL(), review.HTMLURL)
			}
		})
	}
}

func Test_CreatePullRequest(t *testing.T) {
	// Verify tool definition once
	serverTool := CreatePullRequest(translations.NullTranslationHelper)
	tool := serverTool.Tool
	require.NoError(t, toolsnaps.Test(tool.Name, tool))

	assert.Equal(t, "create_pull_request", tool.Name)
	assert.NotEmpty(t, tool.Description)
	schema := tool.InputSchema.(*jsonschema.Schema)
	assert.Contains(t, schema.Properties, "owner")
	assert.Contains(t, schema.Properties, "repo")
	assert.Contains(t, schema.Properties, "title")
	assert.Contains(t, schema.Properties, "body")
	assert.Contains(t, schema.Properties, "head")
	assert.Contains(t, schema.Properties, "base")
	assert.Contains(t, schema.Properties, "draft")
	assert.Contains(t, schema.Properties, "maintainer_can_modify")
	assert.ElementsMatch(t, schema.Required, []string{"owner", "repo", "title", "head", "base"})

	// Setup mock PR for success case
	mockPR := &github.PullRequest{
		Number:  github.Ptr(42),
		Title:   github.Ptr("Test PR"),
		State:   github.Ptr("open"),
		HTMLURL: github.Ptr("https://github.com/owner/repo/pull/42"),
		Head: &github.PullRequestBranch{
			SHA: github.Ptr("abcd1234"),
			Ref: github.Ptr("feature-branch"),
		},
		Base: &github.PullRequestBranch{
			SHA: github.Ptr("efgh5678"),
			Ref: github.Ptr("main"),
		},
		Body:                github.Ptr("This is a test PR"),
		Draft:               github.Ptr(false),
		MaintainerCanModify: github.Ptr(true),
		User: &github.User{
			Login: github.Ptr("testuser"),
		},
	}

	tests := []struct {
		name           string
		mockedClient   *http.Client
		requestArgs    map[string]any
		expectError    bool
		expectedPR     *github.PullRequest
		expectedErrMsg string
	}{
		{
			name: "successful PR creation",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				PostReposPullsByOwnerByRepo: expectRequestBody(t, map[string]any{
					"title":                 "Test PR",
					"body":                  "This is a test PR",
					"head":                  "feature-branch",
					"base":                  "main",
					"draft":                 false,
					"maintainer_can_modify": true,
				}).andThen(
					mockResponse(t, http.StatusCreated, mockPR),
				),
			}),
			requestArgs: map[string]any{
				"owner":                 "owner",
				"repo":                  "repo",
				"title":                 "Test PR",
				"body":                  "This is a test PR",
				"head":                  "feature-branch",
				"base":                  "main",
				"draft":                 false,
				"maintainer_can_modify": true,
			},
			expectError: false,
			expectedPR:  mockPR,
		},
		{
			name:         "missing required parameter",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}),
			requestArgs: map[string]any{
				"owner": "owner",
				"repo":  "repo",
				// missing title, head, base
			},
			expectError:    true,
			expectedErrMsg: "missing required parameter: title",
		},
		{
			name: "PR creation fails",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				PostReposPullsByOwnerByRepo: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
					w.WriteHeader(http.StatusUnprocessableEntity)
					_, _ = w.Write([]byte(`{"message":"Validation failed","errors":[{"resource":"PullRequest","code":"invalid"}]}`))
				}),
			}),
			requestArgs: map[string]any{
				"owner": "owner",
				"repo":  "repo",
				"title": "Test PR",
				"head":  "feature-branch",
				"base":  "main",
			},
			expectError:    true,
			expectedErrMsg: "failed to create pull request",
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			// Setup client with mock
			client := mustNewGHClient(t, tc.mockedClient)
			serverTool := CreatePullRequest(translations.NullTranslationHelper)
			deps := BaseDeps{
				Client: client,
			}
			handler := serverTool.Handler(deps)

			// Create call request
			request := createMCPRequest(tc.requestArgs)

			// Call handler
			result, err := handler(ContextWithDeps(context.Background(), deps), &request)

			// Verify results
			if tc.expectError {
				if err != nil {
					assert.Contains(t, err.Error(), tc.expectedErrMsg)
					return
				}

				// If no error returned but in the result
				textContent := getTextResult(t, result)
				assert.Contains(t, textContent.Text, tc.expectedErrMsg)
				return
			}

			require.NoError(t, err)

			// Parse the result and get the text content if no error
			textContent := getTextResult(t, result)

			// Unmarshal and verify the minimal result
			var returnedPR MinimalResponse
			err = json.Unmarshal([]byte(textContent.Text), &returnedPR)
			require.NoError(t, err)
			assert.Equal(t, tc.expectedPR.GetHTMLURL(), returnedPR.URL)
		})
	}
}

// Test_CreatePullRequest_MCPAppsFeature_UIGate verifies the MCP Apps feature UI gate
// behavior: UI clients get a form message, non-UI clients execute directly.
func Test_CreatePullRequest_MCPAppsFeature_UIGate(t *testing.T) {
	t.Parallel()

	mockPR := &github.PullRequest{
		Number:  github.Ptr(42),
		Title:   github.Ptr("Test PR"),
		HTMLURL: github.Ptr("https://github.com/owner/repo/pull/42"),
		Head:    &github.PullRequestBranch{SHA: github.Ptr("abc"), Ref: github.Ptr("feature")},
		Base:    &github.PullRequestBranch{SHA: github.Ptr("def"), Ref: github.Ptr("main")},
		User:    &github.User{Login: github.Ptr("testuser")},
	}

	serverTool := CreatePullRequest(translations.NullTranslationHelper)

	client := mustNewGHClient(t, MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
		PostReposPullsByOwnerByRepo: mockResponse(t, http.StatusCreated, mockPR),
	}))

	deps := BaseDeps{
		Client:         client,
		GQLClient:      githubv4.NewClient(nil),
		featureChecker: featureCheckerFor(MCPAppsFeatureFlag),
	}
	handler := serverTool.Handler(deps)

	t.Run("UI client without _ui_submitted returns form message", func(t *testing.T) {
		request := createMCPRequestWithSession(t, ClientNameVSCodeInsiders, true, map[string]any{
			"owner": "owner",
			"repo":  "repo",
			"title": "Test PR",
			"head":  "feature",
			"base":  "main",
		})
		result, err := handler(ContextWithDeps(context.Background(), deps), &request)
		require.NoError(t, err)

		textContent := getTextResult(t, result)
		assert.Contains(t, textContent.Text, "interactive form has been shown to the user for creating a new pull request")
		assert.True(t, result.IsError, "form-routing stub should be marked IsError so agents don't claim success")
	})

	t.Run("UI client with _ui_submitted executes directly", func(t *testing.T) {
		request := createMCPRequestWithSession(t, ClientNameVSCodeInsiders, true, map[string]any{
			"owner":         "owner",
			"repo":          "repo",
			"title":         "Test PR",
			"head":          "feature",
			"base":          "main",
			"_ui_submitted": true,
		})
		result, err := handler(ContextWithDeps(context.Background(), deps), &request)
		require.NoError(t, err)

		textContent := getTextResult(t, result)
		assert.Contains(t, textContent.Text, "https://github.com/owner/repo/pull/42",
			"tool should return the created PR URL")
	})

	t.Run("non-UI client executes directly without _ui_submitted", func(t *testing.T) {
		request := createMCPRequest(map[string]any{
			"owner": "owner",
			"repo":  "repo",
			"title": "Test PR",
			"head":  "feature",
			"base":  "main",
		})
		result, err := handler(ContextWithDeps(context.Background(), deps), &request)
		require.NoError(t, err)

		textContent := getTextResult(t, result)
		assert.Contains(t, textContent.Text, "https://github.com/owner/repo/pull/42",
			"non-UI client should execute directly")
	})

	t.Run("UI client with non-form param skips form and executes directly", func(t *testing.T) {
		// A parameter the form does not collect must bypass the form rather than
		// be silently dropped.
		request := createMCPRequestWithSession(t, ClientNameVSCodeInsiders, true, map[string]any{
			"owner":         "owner",
			"repo":          "repo",
			"title":         "Test PR",
			"head":          "feature",
			"base":          "main",
			"unknown_param": "value",
		})
		result, err := handler(ContextWithDeps(context.Background(), deps), &request)
		require.NoError(t, err)

		textContent := getTextResult(t, result)
		assert.NotContains(t, textContent.Text, "interactive form has been shown",
			"non-form param should skip UI form")
		assert.Contains(t, textContent.Text, "https://github.com/owner/repo/pull/42",
			"non-form param call should execute directly and return PR URL")
	})
}

// Test_UpdatePullRequest_MCPAppsFeature_UIGate verifies the form-routing
// behavior for update_pull_request: UI clients without _ui_submitted get a
// pending-form stub (marked IsError so agents don't claim success), UI clients
// with _ui_submitted execute directly, non-UI clients execute directly, and
// UI clients carrying non-form params bypass the form.
func Test_UpdatePullRequest_MCPAppsFeature_UIGate(t *testing.T) {
	t.Parallel()

	mockPR := &github.PullRequest{
		Number:  github.Ptr(42),
		Title:   github.Ptr("Updated"),
		HTMLURL: github.Ptr("https://github.com/owner/repo/pull/42"),
		Head:    &github.PullRequestBranch{SHA: github.Ptr("abc"), Ref: github.Ptr("feature")},
		Base:    &github.PullRequestBranch{SHA: github.Ptr("def"), Ref: github.Ptr("main")},
		User:    &github.User{Login: github.Ptr("testuser")},
	}

	serverTool := UpdatePullRequest(translations.NullTranslationHelper)

	client := mustNewGHClient(t, MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
		PatchReposPullsByOwnerByRepoByPullNumber: mockResponse(t, http.StatusOK, mockPR),
		GetReposPullsByOwnerByRepoByPullNumber:   mockResponse(t, http.StatusOK, mockPR),
	}))

	deps := BaseDeps{
		Client:         client,
		GQLClient:      githubv4.NewClient(nil),
		featureChecker: featureCheckerFor(MCPAppsFeatureFlag),
	}
	handler := serverTool.Handler(deps)

	t.Run("UI client without _ui_submitted returns form message", func(t *testing.T) {
		request := createMCPRequestWithSession(t, ClientNameVSCodeInsiders, true, map[string]any{
			"owner":      "owner",
			"repo":       "repo",
			"pullNumber": float64(42),
			"title":      "Updated",
		})
		result, err := handler(ContextWithDeps(context.Background(), deps), &request)
		require.NoError(t, err)

		textContent := getTextResult(t, result)
		assert.Contains(t, textContent.Text, "interactive form has been shown to the user for editing pull request #42")
		assert.True(t, result.IsError, "form-routing stub should be marked IsError so agents don't claim success")
	})

	t.Run("UI client with _ui_submitted executes directly", func(t *testing.T) {
		request := createMCPRequestWithSession(t, ClientNameVSCodeInsiders, true, map[string]any{
			"owner":         "owner",
			"repo":          "repo",
			"pullNumber":    float64(42),
			"title":         "Updated",
			"_ui_submitted": true,
		})
		result, err := handler(ContextWithDeps(context.Background(), deps), &request)
		require.NoError(t, err)

		textContent := getTextResult(t, result)
		assert.False(t, result.IsError, "submitted form should execute successfully: %s", textContent.Text)
		assert.Contains(t, textContent.Text, "https://github.com/owner/repo/pull/42",
			"submitted form should return the updated PR URL")
	})

	t.Run("non-UI client executes directly without _ui_submitted", func(t *testing.T) {
		request := createMCPRequest(map[string]any{
			"owner":      "owner",
			"repo":       "repo",
			"pullNumber": float64(42),
			"title":      "Updated",
		})
		result, err := handler(ContextWithDeps(context.Background(), deps), &request)
		require.NoError(t, err)

		textContent := getTextResult(t, result)
		assert.False(t, result.IsError, "non-UI client should execute directly: %s", textContent.Text)
		assert.Contains(t, textContent.Text, "https://github.com/owner/repo/pull/42",
			"non-UI client should return the updated PR URL")
	})

	t.Run("UI client with non-form param skips form and executes directly", func(t *testing.T) {
		request := createMCPRequestWithSession(t, ClientNameVSCodeInsiders, true, map[string]any{
			"owner":         "owner",
			"repo":          "repo",
			"pullNumber":    float64(42),
			"title":         "Updated",
			"unknown_param": "value",
		})
		result, err := handler(ContextWithDeps(context.Background(), deps), &request)
		require.NoError(t, err)

		textContent := getTextResult(t, result)
		assert.NotContains(t, textContent.Text, "interactive form has been shown",
			"non-form param should skip UI form")
	})
}

func Test_pullRequestWriteHasNonFormParams(t *testing.T) {
	t.Parallel()

	tests := []struct {
		name string
		args map[string]any
		want bool
	}{
		{name: "no params", args: map[string]any{}, want: false},
		{name: "only form params", args: map[string]any{"owner": "o", "repo": "r", "title": "t", "body": "b", "head": "h", "base": "b", "draft": true, "maintainer_can_modify": false, "reviewers": []any{"octocat"}, "_ui_submitted": true}, want: false},
		{name: "unknown param present", args: map[string]any{"title": "t", "unknown_param": "value"}, want: true},
		{name: "nil value is ignored", args: map[string]any{"reviewers": nil}, want: false},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()
			assert.Equal(t, tc.want, hasNonFormParams(tc.args, pullRequestWriteFormParams))
		})
	}
}

// Test_createPullRequestSchemaClassification fails when a schema property is
// added without classifying it as either form-resendable
// (pullRequestWriteFormParams) or known-non-form (knownNonForm below).
// Today every property is form-resendable, so knownNonForm is empty.
func Test_createPullRequestSchemaClassification(t *testing.T) {
	t.Parallel()

	knownNonForm := map[string]struct{}{}

	tool := CreatePullRequest(translations.NullTranslationHelper)
	schema, ok := tool.Tool.InputSchema.(*jsonschema.Schema)
	require.True(t, ok, "InputSchema should be *jsonschema.Schema")

	for prop := range schema.Properties {
		_, isForm := pullRequestWriteFormParams[prop]
		_, isNonForm := knownNonForm[prop]

		assert.Falsef(t, isForm && isNonForm,
			"property %q is classified as both form-resendable and non-form — pick one", prop)
		assert.Truef(t, isForm || isNonForm,
			"property %q in create_pull_request schema is unclassified — add it to pullRequestWriteFormParams "+
				"(pkg/github/pullrequests.go) if the MCP App form can carry it on submit, otherwise add it to "+
				"the knownNonForm allowlist in this test", prop)
	}
}

func TestCreateAndSubmitPullRequestReview(t *testing.T) {
	t.Parallel()

	// Verify tool definition once
	serverTool := PullRequestReviewWrite(translations.NullTranslationHelper)
	tool := serverTool.Tool
	require.NoError(t, toolsnaps.Test(tool.Name, tool))

	assert.Equal(t, "pull_request_review_write", tool.Name)
	assert.NotEmpty(t, tool.Description)
	schema := tool.InputSchema.(*jsonschema.Schema)
	assert.Contains(t, schema.Properties, "method")
	assert.Contains(t, schema.Properties, "owner")
	assert.Contains(t, schema.Properties, "repo")
	assert.Contains(t, schema.Properties, "pullNumber")
	assert.Contains(t, schema.Properties, "body")
	assert.Contains(t, schema.Properties, "event")
	assert.Contains(t, schema.Properties, "commitID")
	assert.ElementsMatch(t, schema.Required, []string{"method", "owner", "repo", "pullNumber"})

	tests := []struct {
		name               string
		mockedClient       *http.Client
		requestArgs        map[string]any
		expectToolError    bool
		expectedToolErrMsg string
	}{
		{
			name: "successful review creation",
			mockedClient: githubv4mock.NewMockedHTTPClient(
				githubv4mock.NewQueryMatcher(
					struct {
						Repository struct {
							PullRequest struct {
								ID githubv4.ID
							} `graphql:"pullRequest(number: $prNum)"`
						} `graphql:"repository(owner: $owner, name: $repo)"`
					}{},
					map[string]any{
						"owner": githubv4.String("owner"),
						"repo":  githubv4.String("repo"),
						"prNum": githubv4.Int(42),
					},
					githubv4mock.DataResponse(
						map[string]any{
							"repository": map[string]any{
								"pullRequest": map[string]any{
									"id": "PR_kwDODKw3uc6WYN1T",
								},
							},
						},
					),
				),
				githubv4mock.NewMutationMatcher(
					struct {
						AddPullRequestReview struct {
							PullRequestReview struct {
								ID githubv4.ID
							}
						} `graphql:"addPullRequestReview(input: $input)"`
					}{},
					githubv4.AddPullRequestReviewInput{
						PullRequestID: githubv4.ID("PR_kwDODKw3uc6WYN1T"),
						Body:          githubv4.NewString("This is a test review"),
						Event:         githubv4mock.Ptr(githubv4.PullRequestReviewEventComment),
						CommitOID:     githubv4.NewGitObjectID("abcd1234"),
					},
					nil,
					githubv4mock.DataResponse(map[string]any{}),
				),
			),
			requestArgs: map[string]any{
				"method":     "create",
				"owner":      "owner",
				"repo":       "repo",
				"pullNumber": float64(42),
				"body":       "This is a test review",
				"event":      "COMMENT",
				"commitID":   "abcd1234",
			},
			expectToolError: false,
		},
		{
			name: "successful review creation with string pullNumber",
			mockedClient: githubv4mock.NewMockedHTTPClient(
				githubv4mock.NewQueryMatcher(
					struct {
						Repository struct {
							PullRequest struct {
								ID githubv4.ID
							} `graphql:"pullRequest(number: $prNum)"`
						} `graphql:"repository(owner: $owner, name: $repo)"`
					}{},
					map[string]any{
						"owner": githubv4.String("owner"),
						"repo":  githubv4.String("repo"),
						"prNum": githubv4.Int(42),
					},
					githubv4mock.DataResponse(
						map[string]any{
							"repository": map[string]any{
								"pullRequest": map[string]any{
									"id": "PR_kwDODKw3uc6WYN1T",
								},
							},
						},
					),
				),
				githubv4mock.NewMutationMatcher(
					struct {
						AddPullRequestReview struct {
							PullRequestReview struct {
								ID githubv4.ID
							}
						} `graphql:"addPullRequestReview(input: $input)"`
					}{},
					githubv4.AddPullRequestReviewInput{
						PullRequestID: githubv4.ID("PR_kwDODKw3uc6WYN1T"),
						Body:          githubv4.NewString("This is a test review"),
						Event:         githubv4mock.Ptr(githubv4.PullRequestReviewEventComment),
						CommitOID:     githubv4.NewGitObjectID("abcd1234"),
					},
					nil,
					githubv4mock.DataResponse(map[string]any{}),
				),
			),
			requestArgs: map[string]any{
				"method":     "create",
				"owner":      "owner",
				"repo":       "repo",
				"pullNumber": "42", // Some MCP clients send numeric values as strings
				"body":       "This is a test review",
				"event":      "COMMENT",
				"commitID":   "abcd1234",
			},
			expectToolError: false,
		},
		{
			name: "failure to get pull request",
			mockedClient: githubv4mock.NewMockedHTTPClient(
				githubv4mock.NewQueryMatcher(
					struct {
						Repository struct {
							PullRequest struct {
								ID githubv4.ID
							} `graphql:"pullRequest(number: $prNum)"`
						} `graphql:"repository(owner: $owner, name: $repo)"`
					}{},
					map[string]any{
						"owner": githubv4.String("owner"),
						"repo":  githubv4.String("repo"),
						"prNum": githubv4.Int(42),
					},
					githubv4mock.ErrorResponse("expected test failure"),
				),
			),
			requestArgs: map[string]any{
				"method":     "create",
				"owner":      "owner",
				"repo":       "repo",
				"pullNumber": float64(42),
				"body":       "This is a test review",
				"event":      "COMMENT",
				"commitID":   "abcd1234",
			},
			expectToolError:    true,
			expectedToolErrMsg: "expected test failure",
		},
		{
			name: "failure to submit review",
			mockedClient: githubv4mock.NewMockedHTTPClient(
				githubv4mock.NewQueryMatcher(
					struct {
						Repository struct {
							PullRequest struct {
								ID githubv4.ID
							} `graphql:"pullRequest(number: $prNum)"`
						} `graphql:"repository(owner: $owner, name: $repo)"`
					}{},
					map[string]any{
						"owner": githubv4.String("owner"),
						"repo":  githubv4.String("repo"),
						"prNum": githubv4.Int(42),
					},
					githubv4mock.DataResponse(
						map[string]any{
							"repository": map[string]any{
								"pullRequest": map[string]any{
									"id": "PR_kwDODKw3uc6WYN1T",
								},
							},
						},
					),
				),
				githubv4mock.NewMutationMatcher(
					struct {
						AddPullRequestReview struct {
							PullRequestReview struct {
								ID githubv4.ID
							}
						} `graphql:"addPullRequestReview(input: $input)"`
					}{},
					githubv4.AddPullRequestReviewInput{
						PullRequestID: githubv4.ID("PR_kwDODKw3uc6WYN1T"),
						Body:          githubv4.NewString("This is a test review"),
						Event:         githubv4mock.Ptr(githubv4.PullRequestReviewEventComment),
						CommitOID:     githubv4.NewGitObjectID("abcd1234"),
					},
					nil,
					githubv4mock.ErrorResponse("expected test failure"),
				),
			),
			requestArgs: map[string]any{
				"method":     "create",
				"owner":      "owner",
				"repo":       "repo",
				"pullNumber": float64(42),
				"body":       "This is a test review",
				"event":      "COMMENT",
				"commitID":   "abcd1234",
			},
			expectToolError:    true,
			expectedToolErrMsg: "expected test failure",
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()

			// Setup client with mock
			client := githubv4.NewClient(tc.mockedClient)
			serverTool := PullRequestReviewWrite(translations.NullTranslationHelper)
			deps := BaseDeps{
				GQLClient: client,
			}
			handler := serverTool.Handler(deps)

			// Create call request
			request := createMCPRequest(tc.requestArgs)

			// Call handler
			result, err := handler(ContextWithDeps(context.Background(), deps), &request)
			require.NoError(t, err)

			textContent := getTextResult(t, result)

			if tc.expectToolError {
				require.True(t, result.IsError)
				assert.Contains(t, textContent.Text, tc.expectedToolErrMsg)
				return
			}

			// Parse the result and get the text content if no error
			require.Equal(t, textContent.Text, "pull request review submitted successfully")
		})
	}
}

func TestCreatePendingPullRequestReview(t *testing.T) {
	t.Parallel()

	// Verify tool definition once
	serverTool := PullRequestReviewWrite(translations.NullTranslationHelper)
	tool := serverTool.Tool
	require.NoError(t, toolsnaps.Test(tool.Name, tool))

	assert.Equal(t, "pull_request_review_write", tool.Name)
	assert.NotEmpty(t, tool.Description)
	schema := tool.InputSchema.(*jsonschema.Schema)
	assert.Contains(t, schema.Properties, "method")
	assert.Contains(t, schema.Properties, "owner")
	assert.Contains(t, schema.Properties, "repo")
	assert.Contains(t, schema.Properties, "pullNumber")
	assert.Contains(t, schema.Properties, "commitID")
	assert.ElementsMatch(t, schema.Required, []string{"method", "owner", "repo", "pullNumber"})

	tests := []struct {
		name               string
		mockedClient       *http.Client
		requestArgs        map[string]any
		expectToolError    bool
		expectedToolErrMsg string
	}{
		{
			name: "successful review creation",
			mockedClient: githubv4mock.NewMockedHTTPClient(
				githubv4mock.NewQueryMatcher(
					struct {
						Repository struct {
							PullRequest struct {
								ID githubv4.ID
							} `graphql:"pullRequest(number: $prNum)"`
						} `graphql:"repository(owner: $owner, name: $repo)"`
					}{},
					map[string]any{
						"owner": githubv4.String("owner"),
						"repo":  githubv4.String("repo"),
						"prNum": githubv4.Int(42),
					},
					githubv4mock.DataResponse(
						map[string]any{
							"repository": map[string]any{
								"pullRequest": map[string]any{
									"id": "PR_kwDODKw3uc6WYN1T",
								},
							},
						},
					),
				),
				githubv4mock.NewMutationMatcher(
					struct {
						AddPullRequestReview struct {
							PullRequestReview struct {
								ID githubv4.ID
							}
						} `graphql:"addPullRequestReview(input: $input)"`
					}{},
					githubv4.AddPullRequestReviewInput{
						PullRequestID: githubv4.ID("PR_kwDODKw3uc6WYN1T"),
						CommitOID:     githubv4.NewGitObjectID("abcd1234"),
					},
					nil,
					githubv4mock.DataResponse(map[string]any{}),
				),
			),
			requestArgs: map[string]any{
				"method":     "create",
				"owner":      "owner",
				"repo":       "repo",
				"pullNumber": float64(42),
				"commitID":   "abcd1234",
			},
			expectToolError: false,
		},
		{
			name: "failure to get pull request",
			mockedClient: githubv4mock.NewMockedHTTPClient(
				githubv4mock.NewQueryMatcher(
					struct {
						Repository struct {
							PullRequest struct {
								ID githubv4.ID
							} `graphql:"pullRequest(number: $prNum)"`
						} `graphql:"repository(owner: $owner, name: $repo)"`
					}{},
					map[string]any{
						"owner": githubv4.String("owner"),
						"repo":  githubv4.String("repo"),
						"prNum": githubv4.Int(42),
					},
					githubv4mock.ErrorResponse("expected test failure"),
				),
			),
			requestArgs: map[string]any{
				"method":     "create",
				"owner":      "owner",
				"repo":       "repo",
				"pullNumber": float64(42),
				"commitID":   "abcd1234",
			},
			expectToolError:    true,
			expectedToolErrMsg: "expected test failure",
		},
		{
			name: "failure to create pending review",
			mockedClient: githubv4mock.NewMockedHTTPClient(
				githubv4mock.NewQueryMatcher(
					struct {
						Repository struct {
							PullRequest struct {
								ID githubv4.ID
							} `graphql:"pullRequest(number: $prNum)"`
						} `graphql:"repository(owner: $owner, name: $repo)"`
					}{},
					map[string]any{
						"owner": githubv4.String("owner"),
						"repo":  githubv4.String("repo"),
						"prNum": githubv4.Int(42),
					},
					githubv4mock.DataResponse(
						map[string]any{
							"repository": map[string]any{
								"pullRequest": map[string]any{
									"id": "PR_kwDODKw3uc6WYN1T",
								},
							},
						},
					),
				),
				githubv4mock.NewMutationMatcher(
					struct {
						AddPullRequestReview struct {
							PullRequestReview struct {
								ID githubv4.ID
							}
						} `graphql:"addPullRequestReview(input: $input)"`
					}{},
					githubv4.AddPullRequestReviewInput{
						PullRequestID: githubv4.ID("PR_kwDODKw3uc6WYN1T"),
						CommitOID:     githubv4.NewGitObjectID("abcd1234"),
					},
					nil,
					githubv4mock.ErrorResponse("expected test failure"),
				),
			),
			requestArgs: map[string]any{
				"method":     "create",
				"owner":      "owner",
				"repo":       "repo",
				"pullNumber": float64(42),
				"commitID":   "abcd1234",
			},
			expectToolError:    true,
			expectedToolErrMsg: "expected test failure",
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()

			// Setup client with mock
			client := githubv4.NewClient(tc.mockedClient)
			serverTool := PullRequestReviewWrite(translations.NullTranslationHelper)
			deps := BaseDeps{
				GQLClient: client,
			}
			handler := serverTool.Handler(deps)

			// Create call request
			request := createMCPRequest(tc.requestArgs)

			// Call handler
			result, err := handler(ContextWithDeps(context.Background(), deps), &request)
			require.NoError(t, err)

			textContent := getTextResult(t, result)

			if tc.expectToolError {
				require.True(t, result.IsError)
				assert.Contains(t, textContent.Text, tc.expectedToolErrMsg)
				return
			}

			// Parse the result and get the text content if no error
			require.Equal(t, "pending pull request created", textContent.Text)
		})
	}
}

func TestAddPullRequestReviewCommentToPendingReview(t *testing.T) {
	t.Parallel()

	// Verify tool definition once
	serverTool := AddCommentToPendingReview(translations.NullTranslationHelper)
	tool := serverTool.Tool
	require.NoError(t, toolsnaps.Test(tool.Name, tool))

	assert.Equal(t, "add_comment_to_pending_review", tool.Name)
	assert.NotEmpty(t, tool.Description)
	schema := tool.InputSchema.(*jsonschema.Schema)
	assert.Contains(t, schema.Properties, "owner")
	assert.Contains(t, schema.Properties, "repo")
	assert.Contains(t, schema.Properties, "pullNumber")
	assert.Contains(t, schema.Properties, "path")
	assert.Contains(t, schema.Properties, "body")
	assert.Contains(t, schema.Properties, "subjectType")
	assert.Contains(t, schema.Properties, "line")
	assert.Contains(t, schema.Properties, "side")
	assert.Contains(t, schema.Properties, "startLine")
	assert.Contains(t, schema.Properties, "startSide")
	assert.ElementsMatch(t, schema.Required, []string{"owner", "repo", "pullNumber", "path", "body", "subjectType"})

	tests := []struct {
		name               string
		mockedClient       *http.Client
		requestArgs        map[string]any
		expectToolError    bool
		expectedToolErrMsg string
	}{
		{
			name: "successful line comment addition",
			requestArgs: map[string]any{
				"owner":       "owner",
				"repo":        "repo",
				"pullNumber":  float64(42),
				"path":        "file.go",
				"body":        "This is a test comment",
				"subjectType": "LINE",
				"line":        float64(10),
				"side":        "RIGHT",
				"startLine":   float64(5),
				"startSide":   "RIGHT",
			},
			mockedClient: githubv4mock.NewMockedHTTPClient(
				viewerQuery("williammartin"),
				getLatestPendingReviewQuery(getLatestPendingReviewQueryParams{
					author: "williammartin",
					owner:  "owner",
					repo:   "repo",
					prNum:  42,

					reviews: []getLatestPendingReviewQueryReview{
						{
							id:    "PR_kwDODKw3uc6WYN1T",
							state: "PENDING",
							url:   "https://github.com/owner/repo/pull/42",
						},
					},
				}),
				githubv4mock.NewMutationMatcher(
					struct {
						AddPullRequestReviewThread struct {
							Thread struct {
								ID githubv4.String // We don't need this, but a selector is required or GQL complains.
							}
						} `graphql:"addPullRequestReviewThread(input: $input)"`
					}{},
					githubv4.AddPullRequestReviewThreadInput{
						Path:                githubv4.String("file.go"),
						Body:                githubv4.String("This is a test comment"),
						SubjectType:         githubv4mock.Ptr(githubv4.PullRequestReviewThreadSubjectTypeLine),
						Line:                githubv4.NewInt(10),
						Side:                githubv4mock.Ptr(githubv4.DiffSideRight),
						StartLine:           githubv4.NewInt(5),
						StartSide:           githubv4mock.Ptr(githubv4.DiffSideRight),
						PullRequestReviewID: githubv4.NewID("PR_kwDODKw3uc6WYN1T"),
					},
					nil,
					githubv4mock.DataResponse(map[string]any{
						"addPullRequestReviewThread": map[string]any{
							"thread": map[string]any{
								"id": "MDEyOlB1bGxSZXF1ZXN0UmV2aWV3VGhyZWFkMTIzNDU2",
							},
						},
					}),
				),
			),
		},
		{
			name: "successful line comment with string pullNumber and line",
			requestArgs: map[string]any{
				"owner":       "owner",
				"repo":        "repo",
				"pullNumber":  "42", // Some MCP clients send numeric values as strings
				"path":        "file.go",
				"body":        "This is a test comment",
				"subjectType": "LINE",
				"line":        "10", // string line number
				"side":        "RIGHT",
				"startLine":   "5", // string startLine
				"startSide":   "RIGHT",
			},
			mockedClient: githubv4mock.NewMockedHTTPClient(
				viewerQuery("williammartin"),
				getLatestPendingReviewQuery(getLatestPendingReviewQueryParams{
					author: "williammartin",
					owner:  "owner",
					repo:   "repo",
					prNum:  42,

					reviews: []getLatestPendingReviewQueryReview{
						{
							id:    "PR_kwDODKw3uc6WYN1T",
							state: "PENDING",
							url:   "https://github.com/owner/repo/pull/42",
						},
					},
				}),
				githubv4mock.NewMutationMatcher(
					struct {
						AddPullRequestReviewThread struct {
							Thread struct {
								ID githubv4.String
							}
						} `graphql:"addPullRequestReviewThread(input: $input)"`
					}{},
					githubv4.AddPullRequestReviewThreadInput{
						Path:                githubv4.String("file.go"),
						Body:                githubv4.String("This is a test comment"),
						SubjectType:         githubv4mock.Ptr(githubv4.PullRequestReviewThreadSubjectTypeLine),
						Line:                githubv4.NewInt(10),
						Side:                githubv4mock.Ptr(githubv4.DiffSideRight),
						StartLine:           githubv4.NewInt(5),
						StartSide:           githubv4mock.Ptr(githubv4.DiffSideRight),
						PullRequestReviewID: githubv4.NewID("PR_kwDODKw3uc6WYN1T"),
					},
					nil,
					githubv4mock.DataResponse(map[string]any{
						"addPullRequestReviewThread": map[string]any{
							"thread": map[string]any{
								"id": "MDEyOlB1bGxSZXF1ZXN0UmV2aWV3VGhyZWFkMTIzNDU2",
							},
						},
					}),
				),
			),
		},
		{
			name: "missing required parameter owner",
			requestArgs: map[string]any{
				"repo":        "gated-probe",
				"pullNumber":  float64(1),
				"path":        "f.go",
				"body":        "x",
				"subjectType": "LINE",
			},
			expectToolError:    true,
			expectedToolErrMsg: "missing required parameter: owner",
		},
		{
			name: "missing required parameter path",
			requestArgs: map[string]any{
				"owner":       "owner",
				"repo":        "repo",
				"pullNumber":  float64(42),
				"body":        "This is a test comment",
				"subjectType": "LINE",
			},
			expectToolError:    true,
			expectedToolErrMsg: "missing required parameter: path",
		},
		{
			name: "thread ID is nil - invalid line number",
			requestArgs: map[string]any{
				"owner":       "owner",
				"repo":        "repo",
				"pullNumber":  float64(42),
				"path":        "file.go",
				"body":        "Comment on non-existent line",
				"subjectType": "LINE",
				"line":        float64(999),
				"side":        "RIGHT",
			},
			mockedClient: githubv4mock.NewMockedHTTPClient(
				viewerQuery("williammartin"),
				getLatestPendingReviewQuery(getLatestPendingReviewQueryParams{
					author: "williammartin",
					owner:  "owner",
					repo:   "repo",
					prNum:  42,

					reviews: []getLatestPendingReviewQueryReview{
						{
							id:    "PR_kwDODKw3uc6WYN1T",
							state: "PENDING",
							url:   "https://github.com/owner/repo/pull/42",
						},
					},
				}),
				githubv4mock.NewMutationMatcher(
					struct {
						AddPullRequestReviewThread struct {
							Thread struct {
								ID githubv4.ID
							}
						} `graphql:"addPullRequestReviewThread(input: $input)"`
					}{},
					githubv4.AddPullRequestReviewThreadInput{
						Path:                githubv4.String("file.go"),
						Body:                githubv4.String("Comment on non-existent line"),
						SubjectType:         githubv4mock.Ptr(githubv4.PullRequestReviewThreadSubjectTypeLine),
						Line:                githubv4.NewInt(999),
						Side:                githubv4mock.Ptr(githubv4.DiffSideRight),
						StartLine:           nil,
						StartSide:           nil,
						PullRequestReviewID: githubv4.NewID("PR_kwDODKw3uc6WYN1T"),
					},
					nil,
					githubv4mock.DataResponse(map[string]any{
						"addPullRequestReviewThread": map[string]any{
							"thread": map[string]any{
								"id": nil,
							},
						},
					}),
				),
			),
			expectToolError:    true,
			expectedToolErrMsg: "Failed to add comment to pending review",
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()

			// Setup client with mock
			client := githubv4.NewClient(tc.mockedClient)
			serverTool := AddCommentToPendingReview(translations.NullTranslationHelper)
			deps := BaseDeps{
				GQLClient: client,
			}
			handler := serverTool.Handler(deps)

			// Create call request
			request := createMCPRequest(tc.requestArgs)

			// Call handler
			result, err := handler(ContextWithDeps(context.Background(), deps), &request)
			require.NoError(t, err)

			textContent := getTextResult(t, result)

			if tc.expectToolError {
				require.True(t, result.IsError)
				assert.Contains(t, textContent.Text, tc.expectedToolErrMsg)
				return
			}

			// Parse the result and get the text content if no error
			require.Equal(t, textContent.Text, "pull request review comment successfully added to pending review")
		})
	}
}

func TestSubmitPendingPullRequestReview(t *testing.T) {
	t.Parallel()

	// Verify tool definition once
	serverTool := PullRequestReviewWrite(translations.NullTranslationHelper)
	tool := serverTool.Tool
	require.NoError(t, toolsnaps.Test(tool.Name, tool))

	assert.Equal(t, "pull_request_review_write", tool.Name)
	assert.NotEmpty(t, tool.Description)
	schema := tool.InputSchema.(*jsonschema.Schema)
	assert.Contains(t, schema.Properties, "method")
	assert.Contains(t, schema.Properties, "owner")
	assert.Contains(t, schema.Properties, "repo")
	assert.Contains(t, schema.Properties, "pullNumber")
	assert.Contains(t, schema.Properties, "event")
	assert.Contains(t, schema.Properties, "body")
	assert.ElementsMatch(t, schema.Required, []string{"method", "owner", "repo", "pullNumber"})

	tests := []struct {
		name               string
		mockedClient       *http.Client
		requestArgs        map[string]any
		expectToolError    bool
		expectedToolErrMsg string
	}{
		{
			name: "successful review submission",
			requestArgs: map[string]any{
				"method":     "submit_pending",
				"owner":      "owner",
				"repo":       "repo",
				"pullNumber": float64(42),
				"event":      "COMMENT",
				"body":       "This is a test review",
			},
			mockedClient: githubv4mock.NewMockedHTTPClient(
				viewerQuery("williammartin"),
				getLatestPendingReviewQuery(getLatestPendingReviewQueryParams{
					author: "williammartin",
					owner:  "owner",
					repo:   "repo",
					prNum:  42,

					reviews: []getLatestPendingReviewQueryReview{
						{
							id:    "PR_kwDODKw3uc6WYN1T",
							state: "PENDING",
							url:   "https://github.com/owner/repo/pull/42",
						},
					},
				}),
				githubv4mock.NewMutationMatcher(
					struct {
						SubmitPullRequestReview struct {
							PullRequestReview struct {
								ID githubv4.ID
							}
						} `graphql:"submitPullRequestReview(input: $input)"`
					}{},
					githubv4.SubmitPullRequestReviewInput{
						PullRequestReviewID: githubv4.NewID("PR_kwDODKw3uc6WYN1T"),
						Event:               githubv4.PullRequestReviewEventComment,
						Body:                githubv4.NewString("This is a test review"),
					},
					nil,
					githubv4mock.DataResponse(map[string]any{}),
				),
			),
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()

			// Setup client with mock
			client := githubv4.NewClient(tc.mockedClient)
			serverTool := PullRequestReviewWrite(translations.NullTranslationHelper)
			deps := BaseDeps{
				GQLClient: client,
			}
			handler := serverTool.Handler(deps)

			// Create call request
			request := createMCPRequest(tc.requestArgs)

			// Call handler
			result, err := handler(ContextWithDeps(context.Background(), deps), &request)
			require.NoError(t, err)

			textContent := getTextResult(t, result)

			if tc.expectToolError {
				require.True(t, result.IsError)
				assert.Contains(t, textContent.Text, tc.expectedToolErrMsg)
				return
			}

			// Parse the result and get the text content if no error
			require.Equal(t, "pending pull request review successfully submitted", textContent.Text)
		})
	}
}

func TestDeletePendingPullRequestReview(t *testing.T) {
	t.Parallel()

	// Verify tool definition once
	serverTool := PullRequestReviewWrite(translations.NullTranslationHelper)
	tool := serverTool.Tool
	require.NoError(t, toolsnaps.Test(tool.Name, tool))

	assert.Equal(t, "pull_request_review_write", tool.Name)
	assert.NotEmpty(t, tool.Description)
	schema := tool.InputSchema.(*jsonschema.Schema)
	assert.Contains(t, schema.Properties, "method")
	assert.Contains(t, schema.Properties, "owner")
	assert.Contains(t, schema.Properties, "repo")
	assert.Contains(t, schema.Properties, "pullNumber")
	assert.ElementsMatch(t, schema.Required, []string{"method", "owner", "repo", "pullNumber"})

	tests := []struct {
		name               string
		requestArgs        map[string]any
		mockedClient       *http.Client
		expectToolError    bool
		expectedToolErrMsg string
	}{
		{
			name: "successful review deletion",
			requestArgs: map[string]any{
				"method":     "delete_pending",
				"owner":      "owner",
				"repo":       "repo",
				"pullNumber": float64(42),
			},
			mockedClient: githubv4mock.NewMockedHTTPClient(
				viewerQuery("williammartin"),
				getLatestPendingReviewQuery(getLatestPendingReviewQueryParams{
					author: "williammartin",
					owner:  "owner",
					repo:   "repo",
					prNum:  42,

					reviews: []getLatestPendingReviewQueryReview{
						{
							id:    "PR_kwDODKw3uc6WYN1T",
							state: "PENDING",
							url:   "https://github.com/owner/repo/pull/42",
						},
					},
				}),
				githubv4mock.NewMutationMatcher(
					struct {
						DeletePullRequestReview struct {
							PullRequestReview struct {
								ID githubv4.ID
							}
						} `graphql:"deletePullRequestReview(input: $input)"`
					}{},
					githubv4.DeletePullRequestReviewInput{
						PullRequestReviewID: githubv4.NewID("PR_kwDODKw3uc6WYN1T"),
					},
					nil,
					githubv4mock.DataResponse(map[string]any{}),
				),
			),
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()

			// Setup client with mock
			client := githubv4.NewClient(tc.mockedClient)
			serverTool := PullRequestReviewWrite(translations.NullTranslationHelper)
			deps := BaseDeps{
				GQLClient: client,
			}
			handler := serverTool.Handler(deps)

			// Create call request
			request := createMCPRequest(tc.requestArgs)

			// Call handler
			result, err := handler(ContextWithDeps(context.Background(), deps), &request)
			require.NoError(t, err)

			textContent := getTextResult(t, result)

			if tc.expectToolError {
				require.True(t, result.IsError)
				assert.Contains(t, textContent.Text, tc.expectedToolErrMsg)
				return
			}

			// Parse the result and get the text content if no error
			require.Equal(t, "pending pull request review successfully deleted", textContent.Text)
		})
	}
}

func TestGetPullRequestDiff(t *testing.T) {
	t.Parallel()

	// Verify tool definition once
	serverTool := PullRequestRead(translations.NullTranslationHelper)
	tool := serverTool.Tool
	require.NoError(t, toolsnaps.Test(tool.Name, tool))

	assert.Equal(t, "pull_request_read", tool.Name)
	assert.NotEmpty(t, tool.Description)
	schema := tool.InputSchema.(*jsonschema.Schema)
	assert.Contains(t, schema.Properties, "method")
	assert.Contains(t, schema.Properties, "owner")
	assert.Contains(t, schema.Properties, "repo")
	assert.Contains(t, schema.Properties, "pullNumber")
	assert.ElementsMatch(t, schema.Required, []string{"method", "owner", "repo", "pullNumber"})

	stubbedDiff := `diff --git a/README.md b/README.md
index 5d6e7b2..8a4f5c3 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,6 @@
 # Hello-World

 Hello World project for GitHub

+## New Section
+
+This is a new section added in the pull request.`

	tests := []struct {
		name               string
		requestArgs        map[string]any
		mockedClient       *http.Client
		expectToolError    bool
		expectedToolErrMsg string
	}{
		{
			name: "successful diff retrieval",
			requestArgs: map[string]any{
				"method":     "get_diff",
				"owner":      "owner",
				"repo":       "repo",
				"pullNumber": float64(42),
			},
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				GetReposPullsByOwnerByRepoByPullNumber: expectPath(t, "/repos/owner/repo/pulls/42").andThen(
					mockResponse(t, http.StatusOK, stubbedDiff),
				),
			}),
			expectToolError: false,
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()

			// Setup client with mock
			client := mustNewGHClient(t, tc.mockedClient)
			serverTool := PullRequestRead(translations.NullTranslationHelper)
			deps := BaseDeps{
				Client:          client,
				RepoAccessCache: stubRepoAccessCache(nil, 5*time.Minute),
				Flags:           stubFeatureFlags(map[string]bool{"lockdown-mode": false}),
			}
			handler := serverTool.Handler(deps)

			// Create call request
			request := createMCPRequest(tc.requestArgs)

			// Call handler
			result, err := handler(ContextWithDeps(context.Background(), deps), &request)
			require.NoError(t, err)

			textContent := getTextResult(t, result)

			if tc.expectToolError {
				require.True(t, result.IsError)
				assert.Contains(t, textContent.Text, tc.expectedToolErrMsg)
				return
			}

			// Parse the result and get the text content if no error
			require.Equal(t, stubbedDiff, textContent.Text)
		})
	}
}

func viewerQuery(login string) githubv4mock.Matcher {
	return githubv4mock.NewQueryMatcher(
		struct {
			Viewer struct {
				Login githubv4.String
			} `graphql:"viewer"`
		}{},
		map[string]any{},
		githubv4mock.DataResponse(map[string]any{
			"viewer": map[string]any{
				"login": login,
			},
		}),
	)
}

type getLatestPendingReviewQueryReview struct {
	id    string
	state string
	url   string
}

type getLatestPendingReviewQueryParams struct {
	author string
	owner  string
	repo   string
	prNum  int32

	reviews []getLatestPendingReviewQueryReview
}

func getLatestPendingReviewQuery(p getLatestPendingReviewQueryParams) githubv4mock.Matcher {
	return githubv4mock.NewQueryMatcher(
		struct {
			Repository struct {
				PullRequest struct {
					Reviews struct {
						Nodes []struct {
							ID    githubv4.ID
							State githubv4.PullRequestReviewState
							URL   githubv4.URI
						}
					} `graphql:"reviews(first: 1, author: $author)"`
				} `graphql:"pullRequest(number: $prNum)"`
			} `graphql:"repository(owner: $owner, name: $name)"`
		}{},
		map[string]any{
			"author": githubv4.String(p.author),
			"owner":  githubv4.String(p.owner),
			"name":   githubv4.String(p.repo),
			"prNum":  githubv4.Int(p.prNum),
		},
		githubv4mock.DataResponse(
			map[string]any{
				"repository": map[string]any{
					"pullRequest": map[string]any{
						"reviews": map[string]any{
							"nodes": []any{
								map[string]any{
									"id":    p.reviews[0].id,
									"state": p.reviews[0].state,
									"url":   p.reviews[0].url,
								},
							},
						},
					},
				},
			},
		),
	)
}

func TestAddReplyToPullRequestComment(t *testing.T) {
	t.Parallel()

	// Verify tool definition once
	serverTool := AddReplyToPullRequestComment(translations.NullTranslationHelper)
	tool := serverTool.Tool
	require.NoError(t, toolsnaps.Test(tool.Name, tool))

	assert.Equal(t, "add_reply_to_pull_request_comment", tool.Name)
	assert.NotEmpty(t, tool.Description)
	schema := tool.InputSchema.(*jsonschema.Schema)
	assert.Contains(t, schema.Properties, "owner")
	assert.Contains(t, schema.Properties, "repo")
	assert.Contains(t, schema.Properties, "pullNumber")
	assert.Contains(t, schema.Properties, "commentId")
	assert.Contains(t, schema.Properties, "body")
	assert.Contains(t, schema.Properties, "reaction")
	assert.ElementsMatch(t, schema.Required, []string{"owner", "repo", "commentId"})

	// Setup mock reply comment for success case
	mockReplyComment := &github.PullRequestComment{
		ID:        github.Ptr(int64(456)),
		Body:      github.Ptr("This is a reply to the comment"),
		InReplyTo: github.Ptr(int64(123)),
		HTMLURL:   github.Ptr("https://github.com/owner/repo/pull/42#discussion_r456"),
		User: &github.User{
			Login: github.Ptr("responder"),
		},
		CreatedAt: &github.Timestamp{Time: time.Now()},
		UpdatedAt: &github.Timestamp{Time: time.Now()},
	}
	mockReaction := &github.Reaction{
		ID:      github.Ptr(int64(789)),
		Content: github.Ptr("rocket"),
	}
	replyCreatedAfterReactionFailure := &atomic.Bool{}

	tests := []struct {
		name               string
		mockedClient       *http.Client
		requestArgs        map[string]any
		expectToolError    bool
		expectedToolErrMsg string
		unexpectedCall     *atomic.Bool
	}{
		{
			name: "successful reply to pull request comment",
			requestArgs: map[string]any{
				"owner":      "owner",
				"repo":       "repo",
				"pullNumber": float64(42),
				"commentId":  float64(123),
				"body":       "This is a reply to the comment",
			},
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				PostReposPullsCommentsByOwnerByRepoByPullNumber: func(w http.ResponseWriter, _ *http.Request) {
					w.WriteHeader(http.StatusCreated)
					responseData, _ := json.Marshal(mockReplyComment)
					_, _ = w.Write(responseData)
				},
			}),
		},
		{
			name: "missing required parameter owner",
			requestArgs: map[string]any{
				"repo":       "repo",
				"pullNumber": float64(42),
				"commentId":  float64(123),
				"body":       "This is a reply to the comment",
			},
			expectToolError:    true,
			expectedToolErrMsg: "missing required parameter: owner",
		},
		{
			name: "missing required parameter repo",
			requestArgs: map[string]any{
				"owner":      "owner",
				"pullNumber": float64(42),
				"commentId":  float64(123),
				"body":       "This is a reply to the comment",
			},
			expectToolError:    true,
			expectedToolErrMsg: "missing required parameter: repo",
		},
		{
			name: "missing required parameter pullNumber when replying",
			requestArgs: map[string]any{
				"owner":     "owner",
				"repo":      "repo",
				"commentId": float64(123),
				"body":      "This is a reply to the comment",
			},
			expectToolError:    true,
			expectedToolErrMsg: "missing required parameter: pullNumber",
		},
		{
			name: "missing required parameter pullNumber when replying with reaction",
			requestArgs: map[string]any{
				"owner":     "owner",
				"repo":      "repo",
				"commentId": float64(123),
				"body":      "This is a reply to the comment",
				"reaction":  "rocket",
			},
			expectToolError:    true,
			expectedToolErrMsg: "missing required parameter: pullNumber",
		},
		{
			name: "missing required parameter commentId",
			requestArgs: map[string]any{
				"owner":      "owner",
				"repo":       "repo",
				"pullNumber": float64(42),
				"body":       "This is a reply to the comment",
			},
			expectToolError:    true,
			expectedToolErrMsg: "missing required parameter: commentId",
		},
		{
			name: "negative commentId",
			requestArgs: map[string]any{
				"owner":     "owner",
				"repo":      "repo",
				"commentId": float64(-123),
				"reaction":  "rocket",
			},
			expectToolError:    true,
			expectedToolErrMsg: "commentId must be greater than 0",
		},
		{
			name: "missing body and reaction",
			requestArgs: map[string]any{
				"owner":      "owner",
				"repo":       "repo",
				"pullNumber": float64(42),
				"commentId":  float64(123),
			},
			expectToolError:    true,
			expectedToolErrMsg: "at least one of body or reaction is required",
		},
		{
			name: "successful reaction to pull request comment",
			requestArgs: map[string]any{
				"owner":     "owner",
				"repo":      "repo",
				"commentId": float64(123),
				"reaction":  "rocket",
			},
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				PostReposPullsCommentsReactionsByOwnerByRepoByCommentID: mockResponse(t, http.StatusCreated, mockReaction),
			}),
		},
		{
			name: "successful reply and reaction to pull request comment",
			requestArgs: map[string]any{
				"owner":      "owner",
				"repo":       "repo",
				"pullNumber": float64(42),
				"commentId":  float64(123),
				"body":       "This is a reply to the comment",
				"reaction":   "rocket",
			},
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				PostReposPullsCommentsByOwnerByRepoByPullNumber: func(w http.ResponseWriter, _ *http.Request) {
					w.WriteHeader(http.StatusCreated)
					responseData, _ := json.Marshal(mockReplyComment)
					_, _ = w.Write(responseData)
				},
				PostReposPullsCommentsReactionsByOwnerByRepoByCommentID: mockResponse(t, http.StatusCreated, mockReaction),
			}),
		},
		{
			name: "API error when adding reply",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				PostReposPullsCommentsByOwnerByRepoByPullNumber: func(w http.ResponseWriter, _ *http.Request) {
					w.WriteHeader(http.StatusNotFound)
					_, _ = w.Write([]byte(`{"message": "Not Found"}`))
				},
			}),
			requestArgs: map[string]any{
				"owner":      "owner",
				"repo":       "repo",
				"pullNumber": float64(42),
				"commentId":  float64(123),
				"body":       "This is a reply to the comment",
			},
			expectToolError:    true,
			expectedToolErrMsg: "failed to add reply to pull request comment",
		},
		{
			name: "does not create reply when reaction fails",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				PostReposPullsCommentsReactionsByOwnerByRepoByCommentID: func(w http.ResponseWriter, _ *http.Request) {
					w.WriteHeader(http.StatusInternalServerError)
					_, _ = w.Write([]byte(`{"message": "server error"}`))
				},
				PostReposPullsCommentsByOwnerByRepoByPullNumber: func(w http.ResponseWriter, _ *http.Request) {
					replyCreatedAfterReactionFailure.Store(true)
					w.WriteHeader(http.StatusCreated)
					responseData, _ := json.Marshal(mockReplyComment)
					_, _ = w.Write(responseData)
				},
			}),
			requestArgs: map[string]any{
				"owner":      "owner",
				"repo":       "repo",
				"pullNumber": float64(42),
				"commentId":  float64(123),
				"body":       "This is a reply to the comment",
				"reaction":   "rocket",
			},
			expectToolError:    true,
			expectedToolErrMsg: "failed to add reaction to pull request review comment",
			unexpectedCall:     replyCreatedAfterReactionFailure,
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()

			// Setup client with mock
			client := mustNewGHClient(t, tc.mockedClient)
			serverTool := AddReplyToPullRequestComment(translations.NullTranslationHelper)
			deps := BaseDeps{
				Client: client,
			}
			handler := serverTool.Handler(deps)

			// Create call request
			request := createMCPRequest(tc.requestArgs)

			// Call handler
			result, err := handler(ContextWithDeps(context.Background(), deps), &request)
			require.NoError(t, err)

			if tc.expectToolError {
				require.True(t, result.IsError)
				errorContent := getErrorResult(t, result)
				assert.Contains(t, errorContent.Text, tc.expectedToolErrMsg)
				if tc.unexpectedCall != nil {
					assert.False(t, tc.unexpectedCall.Load())
				}
				return
			}

			// Parse the result and verify it's not an error
			require.False(t, result.IsError)
			textContent := getTextResult(t, result)
			if _, ok := tc.requestArgs["body"]; ok {
				assert.Contains(t, textContent.Text, "This is a reply to the comment")
			}
			if _, ok := tc.requestArgs["reaction"]; ok {
				assert.Contains(t, textContent.Text, "789")
			}
		})
	}
}

func TestResolveReviewThread(t *testing.T) {
	t.Parallel()

	tests := []struct {
		name               string
		requestArgs        map[string]any
		mockedClient       *http.Client
		expectToolError    bool
		expectedToolErrMsg string
		expectedResult     string
	}{
		{
			name: "successful resolve thread",
			requestArgs: map[string]any{
				"method":     "resolve_thread",
				"owner":      "owner",
				"repo":       "repo",
				"pullNumber": float64(42),
				"threadId":   "PRRT_kwDOTest123",
			},
			mockedClient: githubv4mock.NewMockedHTTPClient(
				githubv4mock.NewMutationMatcher(
					struct {
						ResolveReviewThread struct {
							Thread struct {
								ID         githubv4.ID
								IsResolved githubv4.Boolean
							}
						} `graphql:"resolveReviewThread(input: $input)"`
					}{},
					githubv4.ResolveReviewThreadInput{
						ThreadID: githubv4.ID("PRRT_kwDOTest123"),
					},
					nil,
					githubv4mock.DataResponse(map[string]any{
						"resolveReviewThread": map[string]any{
							"thread": map[string]any{
								"id":         "PRRT_kwDOTest123",
								"isResolved": true,
							},
						},
					}),
				),
			),
			expectedResult: "review thread resolved successfully",
		},
		{
			name: "successful unresolve thread",
			requestArgs: map[string]any{
				"method":     "unresolve_thread",
				"owner":      "owner",
				"repo":       "repo",
				"pullNumber": float64(42),
				"threadId":   "PRRT_kwDOTest123",
			},
			mockedClient: githubv4mock.NewMockedHTTPClient(
				githubv4mock.NewMutationMatcher(
					struct {
						UnresolveReviewThread struct {
							Thread struct {
								ID         githubv4.ID
								IsResolved githubv4.Boolean
							}
						} `graphql:"unresolveReviewThread(input: $input)"`
					}{},
					githubv4.UnresolveReviewThreadInput{
						ThreadID: githubv4.ID("PRRT_kwDOTest123"),
					},
					nil,
					githubv4mock.DataResponse(map[string]any{
						"unresolveReviewThread": map[string]any{
							"thread": map[string]any{
								"id":         "PRRT_kwDOTest123",
								"isResolved": false,
							},
						},
					}),
				),
			),
			expectedResult: "review thread unresolved successfully",
		},
		{
			name: "empty threadId for resolve",
			requestArgs: map[string]any{
				"method":     "resolve_thread",
				"owner":      "owner",
				"repo":       "repo",
				"pullNumber": float64(42),
				"threadId":   "",
			},
			mockedClient:       githubv4mock.NewMockedHTTPClient(),
			expectToolError:    true,
			expectedToolErrMsg: "threadId is required",
		},
		{
			name: "empty threadId for unresolve",
			requestArgs: map[string]any{
				"method":     "unresolve_thread",
				"owner":      "owner",
				"repo":       "repo",
				"pullNumber": float64(42),
				"threadId":   "",
			},
			mockedClient:       githubv4mock.NewMockedHTTPClient(),
			expectToolError:    true,
			expectedToolErrMsg: "threadId is required",
		},
		{
			name: "omitted threadId for resolve",
			requestArgs: map[string]any{
				"method":     "resolve_thread",
				"owner":      "owner",
				"repo":       "repo",
				"pullNumber": float64(42),
			},
			mockedClient:       githubv4mock.NewMockedHTTPClient(),
			expectToolError:    true,
			expectedToolErrMsg: "threadId is required",
		},
		{
			name: "omitted threadId for unresolve",
			requestArgs: map[string]any{
				"method":     "unresolve_thread",
				"owner":      "owner",
				"repo":       "repo",
				"pullNumber": float64(42),
			},
			mockedClient:       githubv4mock.NewMockedHTTPClient(),
			expectToolError:    true,
			expectedToolErrMsg: "threadId is required",
		},
		{
			name: "thread not found",
			requestArgs: map[string]any{
				"method":     "resolve_thread",
				"owner":      "owner",
				"repo":       "repo",
				"pullNumber": float64(42),
				"threadId":   "PRRT_invalid",
			},
			mockedClient: githubv4mock.NewMockedHTTPClient(
				githubv4mock.NewMutationMatcher(
					struct {
						ResolveReviewThread struct {
							Thread struct {
								ID         githubv4.ID
								IsResolved githubv4.Boolean
							}
						} `graphql:"resolveReviewThread(input: $input)"`
					}{},
					githubv4.ResolveReviewThreadInput{
						ThreadID: githubv4.ID("PRRT_invalid"),
					},
					nil,
					githubv4mock.ErrorResponse("Could not resolve to a PullRequestReviewThread with the id of 'PRRT_invalid'"),
				),
			),
			expectToolError:    true,
			expectedToolErrMsg: "Could not resolve to a PullRequestReviewThread",
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()

			// Setup client with mock
			client := githubv4.NewClient(tc.mockedClient)
			serverTool := PullRequestReviewWrite(translations.NullTranslationHelper)
			deps := BaseDeps{
				GQLClient: client,
			}
			handler := serverTool.Handler(deps)

			// Create call request
			request := createMCPRequest(tc.requestArgs)

			// Call handler
			result, err := handler(ContextWithDeps(context.Background(), deps), &request)
			require.NoError(t, err)

			textContent := getTextResult(t, result)

			if tc.expectToolError {
				require.True(t, result.IsError)
				assert.Contains(t, textContent.Text, tc.expectedToolErrMsg)
				return
			}

			require.False(t, result.IsError)
			assert.Equal(t, tc.expectedResult, textContent.Text)
		})
	}
}
