package github

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"strings"
	"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/lockdown"
	"github.com/github/github-mcp-server/pkg/translations"
	"github.com/google/go-github/v82/github"
	"github.com/google/jsonschema-go/jsonschema"
	"github.com/shurcooL/githubv4"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

var defaultGQLClient *githubv4.Client = githubv4.NewClient(newRepoAccessHTTPClient())
var repoAccessCache *lockdown.RepoAccessCache = stubRepoAccessCache(defaultGQLClient, 15*time.Minute)

type repoAccessKey struct {
	owner    string
	repo     string
	username string
}

type repoAccessValue struct {
	isPrivate  bool
	permission string
}

type repoAccessMockTransport struct {
	responses map[repoAccessKey]repoAccessValue
}

func newRepoAccessHTTPClient() *http.Client {
	responses := map[repoAccessKey]repoAccessValue{
		{owner: "owner2", repo: "repo2", username: "testuser2"}: {isPrivate: true},
		{owner: "owner", repo: "repo", username: "testuser"}:    {isPrivate: false, permission: "READ"},
	}

	return &http.Client{Transport: &repoAccessMockTransport{responses: responses}}
}

func (rt *repoAccessMockTransport) RoundTrip(req *http.Request) (*http.Response, error) {
	if req.Body == nil {
		return nil, fmt.Errorf("missing request body")
	}

	var payload struct {
		Query     string         `json:"query"`
		Variables map[string]any `json:"variables"`
	}

	if err := json.NewDecoder(req.Body).Decode(&payload); err != nil {
		return nil, err
	}
	_ = req.Body.Close()

	owner := toString(payload.Variables["owner"])
	repo := toString(payload.Variables["name"])
	username := toString(payload.Variables["username"])

	value, ok := rt.responses[repoAccessKey{owner: owner, repo: repo, username: username}]
	if !ok {
		value = repoAccessValue{isPrivate: false, permission: "WRITE"}
	}

	edges := []any{}
	if value.permission != "" {
		edges = append(edges, map[string]any{
			"permission": value.permission,
			"node": map[string]any{
				"login": username,
			},
		})
	}

	responseBody, err := json.Marshal(map[string]any{
		"data": map[string]any{
			"repository": map[string]any{
				"isPrivate": value.isPrivate,
				"collaborators": map[string]any{
					"edges": edges,
				},
			},
		},
	})
	if err != nil {
		return nil, err
	}

	resp := &http.Response{
		StatusCode: http.StatusOK,
		Header:     make(http.Header),
		Body:       io.NopCloser(bytes.NewReader(responseBody)),
	}
	resp.Header.Set("Content-Type", "application/json")
	return resp, nil
}

func toString(v any) string {
	switch value := v.(type) {
	case string:
		return value
	case fmt.Stringer:
		return value.String()
	case nil:
		return ""
	default:
		return fmt.Sprintf("%v", value)
	}
}

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

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

	// Setup mock issue for success case
	mockIssue := &github.Issue{
		Number:  github.Ptr(42),
		Title:   github.Ptr("Test Issue"),
		Body:    github.Ptr("This is a test issue"),
		State:   github.Ptr("open"),
		HTMLURL: github.Ptr("https://github.com/owner/repo/issues/42"),
		User: &github.User{
			Login: github.Ptr("testuser"),
		},
		Repository: &github.Repository{
			Name: github.Ptr("repo"),
			Owner: &github.User{
				Login: github.Ptr("owner"),
			},
		},
	}
	mockIssue2 := &github.Issue{
		Number:  github.Ptr(422),
		Title:   github.Ptr("Test Issue 2"),
		Body:    github.Ptr("This is a test issue 2"),
		State:   github.Ptr("open"),
		HTMLURL: github.Ptr("https://github.com/owner/repo/issues/42"),
		User: &github.User{
			Login: github.Ptr("testuser2"),
		},
		Repository: &github.Repository{
			Name: github.Ptr("repo2"),
			Owner: &github.User{
				Login: github.Ptr("owner2"),
			},
		},
	}

	tests := []struct {
		name               string
		mockedClient       *http.Client
		gqlHTTPClient      *http.Client
		requestArgs        map[string]any
		expectHandlerError bool
		expectResultError  bool
		expectedIssue      *github.Issue
		expectedErrMsg     string
		lockdownEnabled    bool
	}{
		{
			name: "successful issue retrieval",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				GetReposIssuesByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusOK, mockIssue),
			}),
			requestArgs: map[string]any{
				"method":       "get",
				"owner":        "owner2",
				"repo":         "repo2",
				"issue_number": float64(42),
			},
			expectedIssue: mockIssue,
		},
		{
			name: "issue not found",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				GetReposIssuesByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusNotFound, `{"message": "Issue not found"}`),
			}),
			requestArgs: map[string]any{
				"method":       "get",
				"owner":        "owner",
				"repo":         "repo",
				"issue_number": float64(999),
			},
			expectHandlerError: true,
			expectedErrMsg:     "failed to get issue",
		},
		{
			name: "lockdown enabled - private repository",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				GetReposIssuesByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusOK, mockIssue2),
			}),
			gqlHTTPClient: githubv4mock.NewMockedHTTPClient(
				githubv4mock.NewQueryMatcher(
					struct {
						Repository struct {
							IsPrivate     githubv4.Boolean
							Collaborators struct {
								Edges []struct {
									Permission githubv4.String
									Node       struct {
										Login githubv4.String
									}
								}
							} `graphql:"collaborators(query: $username, first: 1)"`
						} `graphql:"repository(owner: $owner, name: $name)"`
					}{},
					map[string]any{
						"owner":    githubv4.String("owner2"),
						"name":     githubv4.String("repo2"),
						"username": githubv4.String("testuser2"),
					},
					githubv4mock.DataResponse(map[string]any{
						"repository": map[string]any{
							"isPrivate": true,
							"collaborators": map[string]any{
								"edges": []any{},
							},
						},
					}),
				),
			),
			requestArgs: map[string]any{
				"method":       "get",
				"owner":        "owner2",
				"repo":         "repo2",
				"issue_number": float64(422),
			},
			expectedIssue:   mockIssue2,
			lockdownEnabled: true,
		},
		{
			name: "lockdown enabled - user lacks push access",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				GetReposIssuesByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusOK, mockIssue),
			}),
			gqlHTTPClient: githubv4mock.NewMockedHTTPClient(
				githubv4mock.NewQueryMatcher(
					struct {
						Repository struct {
							IsPrivate     githubv4.Boolean
							Collaborators struct {
								Edges []struct {
									Permission githubv4.String
									Node       struct {
										Login githubv4.String
									}
								}
							} `graphql:"collaborators(query: $username, first: 1)"`
						} `graphql:"repository(owner: $owner, name: $name)"`
					}{},
					map[string]any{
						"owner":    githubv4.String("owner"),
						"name":     githubv4.String("repo"),
						"username": githubv4.String("testuser"),
					},
					githubv4mock.DataResponse(map[string]any{
						"repository": map[string]any{
							"isPrivate": false,
							"collaborators": map[string]any{
								"edges": []any{
									map[string]any{
										"permission": "READ",
										"node": map[string]any{
											"login": "testuser",
										},
									},
								},
							},
						},
					}),
				),
			),
			requestArgs: map[string]any{
				"method":       "get",
				"owner":        "owner",
				"repo":         "repo",
				"issue_number": float64(42),
			},
			expectResultError: true,
			expectedErrMsg:    "access to issue details is restricted by lockdown mode",
			lockdownEnabled:   true,
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			client := github.NewClient(tc.mockedClient)

			var gqlClient *githubv4.Client
			cache := repoAccessCache
			if tc.gqlHTTPClient != nil {
				gqlClient = githubv4.NewClient(tc.gqlHTTPClient)
				cache = stubRepoAccessCache(gqlClient, 15*time.Minute)
			} else {
				gqlClient = githubv4.NewClient(nil)
			}

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

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

			if tc.expectHandlerError {
				require.Error(t, err)
				assert.Contains(t, err.Error(), tc.expectedErrMsg)
				return
			}

			require.NoError(t, err)
			require.NotNil(t, result)

			if tc.expectResultError {
				errorContent := getErrorResult(t, result)
				assert.Contains(t, errorContent.Text, tc.expectedErrMsg)
				return
			}

			textContent := getTextResult(t, result)

			var returnedIssue MinimalIssue
			err = json.Unmarshal([]byte(textContent.Text), &returnedIssue)
			require.NoError(t, err)
			assert.Equal(t, tc.expectedIssue.GetNumber(), returnedIssue.Number)
			assert.Equal(t, tc.expectedIssue.GetTitle(), returnedIssue.Title)
			assert.Equal(t, tc.expectedIssue.GetBody(), returnedIssue.Body)
			assert.Equal(t, tc.expectedIssue.GetState(), returnedIssue.State)
			assert.Equal(t, tc.expectedIssue.GetHTMLURL(), returnedIssue.HTMLURL)
			assert.Equal(t, tc.expectedIssue.GetUser().GetLogin(), returnedIssue.User.Login)
		})
	}
}

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

	assert.Equal(t, "add_issue_comment", tool.Name)
	assert.NotEmpty(t, tool.Description)

	assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "owner")
	assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "repo")
	assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "issue_number")
	assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "body")
	assert.ElementsMatch(t, tool.InputSchema.(*jsonschema.Schema).Required, []string{"owner", "repo", "issue_number", "body"})

	// Setup mock comment for success case
	mockComment := &github.IssueComment{
		ID:   github.Ptr(int64(123)),
		Body: github.Ptr("This is a test comment"),
		User: &github.User{
			Login: github.Ptr("testuser"),
		},
		HTMLURL: github.Ptr("https://github.com/owner/repo/issues/42#issuecomment-123"),
	}

	tests := []struct {
		name            string
		mockedClient    *http.Client
		requestArgs     map[string]any
		expectError     bool
		expectedComment *github.IssueComment
		expectedErrMsg  string
	}{
		{
			name: "successful comment creation",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				PostReposIssuesCommentsByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusCreated, mockComment),
			}),
			requestArgs: map[string]any{
				"owner":        "owner",
				"repo":         "repo",
				"issue_number": float64(42),
				"body":         "This is a test comment",
			},
			expectError:     false,
			expectedComment: mockComment,
		},
		{
			name: "comment creation fails",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				PostReposIssuesCommentsByOwnerByRepoByIssueNumber: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
					w.WriteHeader(http.StatusUnprocessableEntity)
					_, _ = w.Write([]byte(`{"message": "Invalid request"}`))
				}),
			}),
			requestArgs: map[string]any{
				"owner":        "owner",
				"repo":         "repo",
				"issue_number": float64(42),
				"body":         "",
			},
			expectError:    false,
			expectedErrMsg: "missing required parameter: body",
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			// Setup client with mock
			client := github.NewClient(tc.mockedClient)
			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.Error(t, err)
				assert.Contains(t, err.Error(), tc.expectedErrMsg)
				return
			}

			if tc.expectedErrMsg != "" {
				require.NotNil(t, 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 result contains minimal response
			var minimalResponse MinimalResponse
			err = json.Unmarshal([]byte(textContent.Text), &minimalResponse)
			require.NoError(t, err)
			assert.Equal(t, fmt.Sprintf("%d", tc.expectedComment.GetID()), minimalResponse.ID)
			assert.Equal(t, tc.expectedComment.GetHTMLURL(), minimalResponse.URL)

		})
	}
}

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

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

	// Setup mock search results
	mockSearchResult := &github.IssuesSearchResult{
		Total:             github.Ptr(2),
		IncompleteResults: github.Ptr(false),
		Issues: []*github.Issue{
			{
				Number:   github.Ptr(42),
				Title:    github.Ptr("Bug: Something is broken"),
				Body:     github.Ptr("This is a bug report"),
				State:    github.Ptr("open"),
				HTMLURL:  github.Ptr("https://github.com/owner/repo/issues/42"),
				Comments: github.Ptr(5),
				User: &github.User{
					Login: github.Ptr("user1"),
				},
			},
			{
				Number:   github.Ptr(43),
				Title:    github.Ptr("Feature: Add new functionality"),
				Body:     github.Ptr("This is a feature request"),
				State:    github.Ptr("open"),
				HTMLURL:  github.Ptr("https://github.com/owner/repo/issues/43"),
				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 issues search with all parameters",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				GetSearchIssues: expectQueryParams(
					t,
					map[string]string{
						"q":        "is:issue 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: "issues 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:issue is:open",
						"sort":     "created",
						"order":    "asc",
						"page":     "1",
						"per_page": "30",
					},
				).andThen(
					mockResponse(t, http.StatusOK, mockSearchResult),
				),
			}),
			requestArgs: map[string]any{
				"query": "is:open",
				"owner": "test-owner",
				"repo":  "test-repo",
				"sort":  "created",
				"order": "asc",
			},
			expectError:    false,
			expectedResult: mockSearchResult,
		},
		{
			name: "issues search with only owner parameter (should ignore it)",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				GetSearchIssues: expectQueryParams(
					t,
					map[string]string{
						"q":        "is:issue bug",
						"page":     "1",
						"per_page": "30",
					},
				).andThen(
					mockResponse(t, http.StatusOK, mockSearchResult),
				),
			}),
			requestArgs: map[string]any{
				"query": "bug",
				"owner": "test-owner",
			},
			expectError:    false,
			expectedResult: mockSearchResult,
		},
		{
			name: "issues search with only repo parameter (should ignore it)",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				GetSearchIssues: expectQueryParams(
					t,
					map[string]string{
						"q":        "is:issue feature",
						"page":     "1",
						"per_page": "30",
					},
				).andThen(
					mockResponse(t, http.StatusOK, mockSearchResult),
				),
			}),
			requestArgs: map[string]any{
				"query": "feature",
				"repo":  "test-repo",
			},
			expectError:    false,
			expectedResult: mockSearchResult,
		},
		{
			name: "issues search with minimal parameters",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				GetSearchIssues: mockResponse(t, http.StatusOK, mockSearchResult),
			}),
			requestArgs: map[string]any{
				"query": "is:issue repo:owner/repo is:open",
			},
			expectError:    false,
			expectedResult: mockSearchResult,
		},
		{
			name: "query with existing is:issue filter - no duplication",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				GetSearchIssues: expectQueryParams(
					t,
					map[string]string{
						"q":        "repo:github/github-mcp-server is:issue is:open (label:critical OR label:urgent)",
						"page":     "1",
						"per_page": "30",
					},
				).andThen(
					mockResponse(t, http.StatusOK, mockSearchResult),
				),
			}),
			requestArgs: map[string]any{
				"query": "repo:github/github-mcp-server is:issue is:open (label:critical OR label:urgent)",
			},
			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:issue repo:github/github-mcp-server critical",
						"page":     "1",
						"per_page": "30",
					},
				).andThen(
					mockResponse(t, http.StatusOK, mockSearchResult),
				),
			}),
			requestArgs: map[string]any{
				"query": "repo:github/github-mcp-server critical",
				"owner": "different-owner",
				"repo":  "different-repo",
			},
			expectError:    false,
			expectedResult: mockSearchResult,
		},
		{
			name: "query with both is: and repo: filters already present",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				GetSearchIssues: expectQueryParams(
					t,
					map[string]string{
						"q":        "is:issue repo:octocat/Hello-World bug",
						"page":     "1",
						"per_page": "30",
					},
				).andThen(
					mockResponse(t, http.StatusOK, mockSearchResult),
				),
			}),
			requestArgs: map[string]any{
				"query": "is:issue repo:octocat/Hello-World bug",
			},
			expectError:    false,
			expectedResult: mockSearchResult,
		},
		{
			name: "complex query with multiple OR operators and existing filters",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				GetSearchIssues: expectQueryParams(
					t,
					map[string]string{
						"q":        "repo:github/github-mcp-server is:issue (label:critical OR label:urgent OR label:high-priority OR label:blocker)",
						"page":     "1",
						"per_page": "30",
					},
				).andThen(
					mockResponse(t, http.StatusOK, mockSearchResult),
				),
			}),
			requestArgs: map[string]any{
				"query": "repo:github/github-mcp-server is:issue (label:critical OR label:urgent OR label:high-priority OR label:blocker)",
			},
			expectError:    false,
			expectedResult: mockSearchResult,
		},
		{
			name: "search issues fails",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				GetSearchIssues: http.HandlerFunc(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 issues",
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			// Setup client with mock
			client := github.NewClient(tc.mockedClient)
			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) // No Go error, but result should be an error
				require.NotNil(t, result)
				require.True(t, result.IsError, "expected result to be an error")
				textContent := getErrorResult(t, result)
				assert.Contains(t, textContent.Text, tc.expectedErrMsg)
				return
			}

			require.NoError(t, err)
			require.False(t, result.IsError, "expected result to not be an error")

			// 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_CreateIssue(t *testing.T) {
	// Verify tool definition once
	serverTool := IssueWrite(translations.NullTranslationHelper)
	tool := serverTool.Tool
	require.NoError(t, toolsnaps.Test(tool.Name, tool))

	assert.Equal(t, "issue_write", tool.Name)
	assert.NotEmpty(t, tool.Description)
	assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "method")
	assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "owner")
	assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "repo")
	assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "title")
	assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "body")
	assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "assignees")
	assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "labels")
	assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "milestone")
	assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "type")
	assert.ElementsMatch(t, tool.InputSchema.(*jsonschema.Schema).Required, []string{"method", "owner", "repo"})

	// Setup mock issue for success case
	mockIssue := &github.Issue{
		Number:    github.Ptr(123),
		Title:     github.Ptr("Test Issue"),
		Body:      github.Ptr("This is a test issue"),
		State:     github.Ptr("open"),
		HTMLURL:   github.Ptr("https://github.com/owner/repo/issues/123"),
		Assignees: []*github.User{{Login: github.Ptr("user1")}, {Login: github.Ptr("user2")}},
		Labels:    []*github.Label{{Name: github.Ptr("bug")}, {Name: github.Ptr("help wanted")}},
		Milestone: &github.Milestone{Number: github.Ptr(5)},
		Type:      &github.IssueType{Name: github.Ptr("Bug")},
	}

	tests := []struct {
		name           string
		mockedClient   *http.Client
		requestArgs    map[string]any
		expectError    bool
		expectedIssue  *github.Issue
		expectedErrMsg string
	}{
		{
			name: "successful issue creation with all fields",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				PostReposIssuesByOwnerByRepo: expectRequestBody(t, map[string]any{
					"title":     "Test Issue",
					"body":      "This is a test issue",
					"labels":    []any{"bug", "help wanted"},
					"assignees": []any{"user1", "user2"},
					"milestone": float64(5),
					"type":      "Bug",
				}).andThen(
					mockResponse(t, http.StatusCreated, mockIssue),
				),
			}),
			requestArgs: map[string]any{
				"method":    "create",
				"owner":     "owner",
				"repo":      "repo",
				"title":     "Test Issue",
				"body":      "This is a test issue",
				"assignees": []any{"user1", "user2"},
				"labels":    []any{"bug", "help wanted"},
				"milestone": float64(5),
				"type":      "Bug",
			},
			expectError:   false,
			expectedIssue: mockIssue,
		},
		{
			name: "successful issue creation with minimal fields",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				PostReposIssuesByOwnerByRepo: mockResponse(t, http.StatusCreated, &github.Issue{
					Number:  github.Ptr(124),
					Title:   github.Ptr("Minimal Issue"),
					HTMLURL: github.Ptr("https://github.com/owner/repo/issues/124"),
					State:   github.Ptr("open"),
				}),
			}),
			requestArgs: map[string]any{
				"method":    "create",
				"owner":     "owner",
				"repo":      "repo",
				"title":     "Minimal Issue",
				"assignees": nil, // Expect no failure with nil optional value.
			},
			expectError: false,
			expectedIssue: &github.Issue{
				Number:  github.Ptr(124),
				Title:   github.Ptr("Minimal Issue"),
				HTMLURL: github.Ptr("https://github.com/owner/repo/issues/124"),
				State:   github.Ptr("open"),
			},
		},
		{
			name: "issue creation fails",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				PostReposIssuesByOwnerByRepo: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
					w.WriteHeader(http.StatusUnprocessableEntity)
					_, _ = w.Write([]byte(`{"message": "Validation failed"}`))
				}),
			}),
			requestArgs: map[string]any{
				"method": "create",
				"owner":  "owner",
				"repo":   "repo",
				"title":  "",
			},
			expectError:    false,
			expectedErrMsg: "missing required parameter: title",
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			// Setup client with mock
			client := github.NewClient(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 {
				require.Error(t, err)
				assert.Contains(t, err.Error(), tc.expectedErrMsg)
				return
			}

			if tc.expectedErrMsg != "" {
				require.NotNil(t, result)
				textContent := getTextResult(t, result)
				assert.Contains(t, textContent.Text, tc.expectedErrMsg)
				return
			}

			require.NoError(t, err)
			textContent := getTextResult(t, result)

			// Unmarshal and verify the minimal result
			var returnedIssue MinimalResponse
			err = json.Unmarshal([]byte(textContent.Text), &returnedIssue)
			require.NoError(t, err)

			assert.Equal(t, tc.expectedIssue.GetHTMLURL(), returnedIssue.URL)
		})
	}
}

// Test_IssueWrite_InsidersMode_UIGate verifies the insiders mode UI gate
// behavior: UI clients get a form message, non-UI clients execute directly.
func Test_IssueWrite_InsidersMode_UIGate(t *testing.T) {
	t.Parallel()

	mockIssue := &github.Issue{
		Number:  github.Ptr(1),
		Title:   github.Ptr("Test"),
		HTMLURL: github.Ptr("https://github.com/owner/repo/issues/1"),
	}

	serverTool := IssueWrite(translations.NullTranslationHelper)

	client := github.NewClient(MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
		PostReposIssuesByOwnerByRepo: mockResponse(t, http.StatusCreated, mockIssue),
	}))

	deps := BaseDeps{
		Client:    client,
		GQLClient: githubv4.NewClient(nil),
		Flags:     FeatureFlags{InsidersMode: true},
	}
	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{
			"method": "create",
			"owner":  "owner",
			"repo":   "repo",
			"title":  "Test",
		})
		result, err := handler(ContextWithDeps(context.Background(), deps), &request)
		require.NoError(t, err)

		textContent := getTextResult(t, result)
		assert.Contains(t, textContent.Text, "Ready to create an issue")
	})

	t.Run("UI client with _ui_submitted executes directly", func(t *testing.T) {
		request := createMCPRequestWithSession(t, ClientNameVSCodeInsiders, true, map[string]any{
			"method":        "create",
			"owner":         "owner",
			"repo":          "repo",
			"title":         "Test",
			"_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/issues/1",
			"tool should return the created issue URL")
	})

	t.Run("non-UI client executes directly without _ui_submitted", func(t *testing.T) {
		request := createMCPRequest(map[string]any{
			"method": "create",
			"owner":  "owner",
			"repo":   "repo",
			"title":  "Test",
		})
		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/issues/1",
			"non-UI client should execute directly")
	})

	t.Run("UI client with state change skips form and executes directly", func(t *testing.T) {
		mockBaseIssue := &github.Issue{
			Number:  github.Ptr(1),
			Title:   github.Ptr("Test"),
			State:   github.Ptr("open"),
			HTMLURL: github.Ptr("https://github.com/owner/repo/issues/1"),
		}
		issueIDQueryResponse := githubv4mock.DataResponse(map[string]any{
			"repository": map[string]any{
				"issue": map[string]any{
					"id": "I_kwDOA0xdyM50BPaO",
				},
			},
		})
		closeSuccessResponse := githubv4mock.DataResponse(map[string]any{
			"closeIssue": map[string]any{
				"issue": map[string]any{
					"id":     "I_kwDOA0xdyM50BPaO",
					"number": 1,
					"url":    "https://github.com/owner/repo/issues/1",
					"state":  "CLOSED",
				},
			},
		})
		completedReason := IssueClosedStateReasonCompleted

		closeClient := github.NewClient(MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
			PatchReposIssuesByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusOK, mockBaseIssue),
		}))
		closeGQLClient := githubv4.NewClient(githubv4mock.NewMockedHTTPClient(
			githubv4mock.NewQueryMatcher(
				struct {
					Repository struct {
						Issue struct {
							ID githubv4.ID
						} `graphql:"issue(number: $issueNumber)"`
					} `graphql:"repository(owner: $owner, name: $repo)"`
				}{},
				map[string]any{
					"owner":       githubv4.String("owner"),
					"repo":        githubv4.String("repo"),
					"issueNumber": githubv4.Int(1),
				},
				issueIDQueryResponse,
			),
			githubv4mock.NewMutationMatcher(
				struct {
					CloseIssue struct {
						Issue struct {
							ID     githubv4.ID
							Number githubv4.Int
							URL    githubv4.String
							State  githubv4.String
						}
					} `graphql:"closeIssue(input: $input)"`
				}{},
				CloseIssueInput{
					IssueID:     "I_kwDOA0xdyM50BPaO",
					StateReason: &completedReason,
				},
				nil,
				closeSuccessResponse,
			),
		))

		closeDeps := BaseDeps{
			Client:    closeClient,
			GQLClient: closeGQLClient,
			Flags:     FeatureFlags{InsidersMode: true},
		}
		closeHandler := serverTool.Handler(closeDeps)

		request := createMCPRequestWithSession(t, ClientNameVSCodeInsiders, true, map[string]any{
			"method":       "update",
			"owner":        "owner",
			"repo":         "repo",
			"issue_number": float64(1),
			"state":        "closed",
			"state_reason": "completed",
		})
		result, err := closeHandler(ContextWithDeps(context.Background(), closeDeps), &request)
		require.NoError(t, err)

		textContent := getTextResult(t, result)
		assert.NotContains(t, textContent.Text, "Ready to update issue",
			"state change should skip UI form")
		assert.Contains(t, textContent.Text, "https://github.com/owner/repo/issues/1",
			"state change should execute directly and return issue URL")
	})

	t.Run("UI client update without state change returns form message", func(t *testing.T) {
		request := createMCPRequestWithSession(t, ClientNameVSCodeInsiders, true, map[string]any{
			"method":       "update",
			"owner":        "owner",
			"repo":         "repo",
			"issue_number": float64(1),
			"title":        "New Title",
		})
		result, err := handler(ContextWithDeps(context.Background(), deps), &request)
		require.NoError(t, err)

		textContent := getTextResult(t, result)
		assert.Contains(t, textContent.Text, "Ready to update issue #1",
			"update without state should show UI form")
	})
}

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

	assert.Equal(t, "list_issues", tool.Name)
	assert.NotEmpty(t, tool.Description)
	assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "owner")
	assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "repo")
	assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "state")
	assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "labels")
	assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "orderBy")
	assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "direction")
	assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "since")
	assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "after")
	assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "perPage")
	assert.ElementsMatch(t, tool.InputSchema.(*jsonschema.Schema).Required, []string{"owner", "repo"})

	// Mock issues data
	mockIssuesAll := []map[string]any{
		{
			"number":     123,
			"title":      "First Issue",
			"body":       "This is the first test issue",
			"state":      "OPEN",
			"databaseId": 1001,
			"createdAt":  "2023-01-01T00:00:00Z",
			"updatedAt":  "2023-01-01T00:00:00Z",
			"author":     map[string]any{"login": "user1"},
			"labels": map[string]any{
				"nodes": []map[string]any{
					{"name": "bug", "id": "label1", "description": "Bug label"},
				},
			},
			"comments": map[string]any{
				"totalCount": 5,
			},
		},
		{
			"number":     456,
			"title":      "Second Issue",
			"body":       "This is the second test issue",
			"state":      "OPEN",
			"databaseId": 1002,
			"createdAt":  "2023-02-01T00:00:00Z",
			"updatedAt":  "2023-02-01T00:00:00Z",
			"author":     map[string]any{"login": "user2"},
			"labels": map[string]any{
				"nodes": []map[string]any{
					{"name": "enhancement", "id": "label2", "description": "Enhancement label"},
				},
			},
			"comments": map[string]any{
				"totalCount": 3,
			},
		},
	}

	mockIssuesOpen := []map[string]any{mockIssuesAll[0], mockIssuesAll[1]}
	mockIssuesClosed := []map[string]any{
		{
			"number":     789,
			"title":      "Closed Issue",
			"body":       "This is a closed issue",
			"state":      "CLOSED",
			"databaseId": 1003,
			"createdAt":  "2023-03-01T00:00:00Z",
			"updatedAt":  "2023-03-01T00:00:00Z",
			"author":     map[string]any{"login": "user3"},
			"labels": map[string]any{
				"nodes": []map[string]any{},
			},
			"comments": map[string]any{
				"totalCount": 1,
			},
		},
	}

	// Mock responses
	mockResponseListAll := githubv4mock.DataResponse(map[string]any{
		"repository": map[string]any{
			"issues": map[string]any{
				"nodes": mockIssuesAll,
				"pageInfo": map[string]any{
					"hasNextPage":     false,
					"hasPreviousPage": false,
					"startCursor":     "",
					"endCursor":       "",
				},
				"totalCount": 2,
			},
		},
	})

	mockResponseOpenOnly := githubv4mock.DataResponse(map[string]any{
		"repository": map[string]any{
			"issues": map[string]any{
				"nodes": mockIssuesOpen,
				"pageInfo": map[string]any{
					"hasNextPage":     false,
					"hasPreviousPage": false,
					"startCursor":     "",
					"endCursor":       "",
				},
				"totalCount": 2,
			},
		},
	})

	mockResponseClosedOnly := githubv4mock.DataResponse(map[string]any{
		"repository": map[string]any{
			"issues": map[string]any{
				"nodes": mockIssuesClosed,
				"pageInfo": map[string]any{
					"hasNextPage":     false,
					"hasPreviousPage": false,
					"startCursor":     "",
					"endCursor":       "",
				},
				"totalCount": 1,
			},
		},
	})

	mockErrorRepoNotFound := githubv4mock.ErrorResponse("repository not found")

	// Variables matching what GraphQL receives after JSON marshaling/unmarshaling
	varsListAll := map[string]any{
		"owner":     "owner",
		"repo":      "repo",
		"states":    []any{"OPEN", "CLOSED"},
		"orderBy":   "CREATED_AT",
		"direction": "DESC",
		"first":     float64(30),
		"after":     (*string)(nil),
	}

	varsOpenOnly := map[string]any{
		"owner":     "owner",
		"repo":      "repo",
		"states":    []any{"OPEN"},
		"orderBy":   "CREATED_AT",
		"direction": "DESC",
		"first":     float64(30),
		"after":     (*string)(nil),
	}

	varsClosedOnly := map[string]any{
		"owner":     "owner",
		"repo":      "repo",
		"states":    []any{"CLOSED"},
		"orderBy":   "CREATED_AT",
		"direction": "DESC",
		"first":     float64(30),
		"after":     (*string)(nil),
	}

	varsWithLabels := map[string]any{
		"owner":     "owner",
		"repo":      "repo",
		"states":    []any{"OPEN", "CLOSED"},
		"labels":    []any{"bug", "enhancement"},
		"orderBy":   "CREATED_AT",
		"direction": "DESC",
		"first":     float64(30),
		"after":     (*string)(nil),
	}

	varsRepoNotFound := map[string]any{
		"owner":     "owner",
		"repo":      "nonexistent-repo",
		"states":    []any{"OPEN", "CLOSED"},
		"orderBy":   "CREATED_AT",
		"direction": "DESC",
		"first":     float64(30),
		"after":     (*string)(nil),
	}

	tests := []struct {
		name          string
		reqParams     map[string]any
		expectError   bool
		errContains   string
		expectedCount int
	}{
		{
			name: "list all issues",
			reqParams: map[string]any{
				"owner": "owner",
				"repo":  "repo",
			},
			expectError:   false,
			expectedCount: 2,
		},
		{
			name: "filter by open state",
			reqParams: map[string]any{
				"owner": "owner",
				"repo":  "repo",
				"state": "OPEN",
			},
			expectError:   false,
			expectedCount: 2,
		},
		{
			name: "filter by open state - lc",
			reqParams: map[string]any{
				"owner": "owner",
				"repo":  "repo",
				"state": "open",
			},
			expectError:   false,
			expectedCount: 2,
		},
		{
			name: "filter by closed state",
			reqParams: map[string]any{
				"owner": "owner",
				"repo":  "repo",
				"state": "CLOSED",
			},
			expectError:   false,
			expectedCount: 1,
		},
		{
			name: "filter by labels",
			reqParams: map[string]any{
				"owner":  "owner",
				"repo":   "repo",
				"labels": []any{"bug", "enhancement"},
			},
			expectError:   false,
			expectedCount: 2,
		},
		{
			name: "repository not found error",
			reqParams: map[string]any{
				"owner": "owner",
				"repo":  "nonexistent-repo",
			},
			expectError: true,
			errContains: "repository not found",
		},
	}

	// Define the actual query strings that match the implementation
	qBasicNoLabels := "query($after:String$direction:OrderDirection!$first:Int!$orderBy:IssueOrderField!$owner:String!$repo:String!$states:[IssueState!]!){repository(owner: $owner, name: $repo){issues(first: $first, after: $after, states: $states, orderBy: {field: $orderBy, direction: $direction}){nodes{number,title,body,state,databaseId,author{login},createdAt,updatedAt,labels(first: 100){nodes{name,id,description}},comments{totalCount}},pageInfo{hasNextPage,hasPreviousPage,startCursor,endCursor},totalCount}}}"
	qWithLabels := "query($after:String$direction:OrderDirection!$first:Int!$labels:[String!]!$orderBy:IssueOrderField!$owner:String!$repo:String!$states:[IssueState!]!){repository(owner: $owner, name: $repo){issues(first: $first, after: $after, labels: $labels, states: $states, orderBy: {field: $orderBy, direction: $direction}){nodes{number,title,body,state,databaseId,author{login},createdAt,updatedAt,labels(first: 100){nodes{name,id,description}},comments{totalCount}},pageInfo{hasNextPage,hasPreviousPage,startCursor,endCursor},totalCount}}}"

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			var httpClient *http.Client

			switch tc.name {
			case "list all issues":
				matcher := githubv4mock.NewQueryMatcher(qBasicNoLabels, varsListAll, mockResponseListAll)
				httpClient = githubv4mock.NewMockedHTTPClient(matcher)
			case "filter by open state":
				matcher := githubv4mock.NewQueryMatcher(qBasicNoLabels, varsOpenOnly, mockResponseOpenOnly)
				httpClient = githubv4mock.NewMockedHTTPClient(matcher)
			case "filter by open state - lc":
				matcher := githubv4mock.NewQueryMatcher(qBasicNoLabels, varsOpenOnly, mockResponseOpenOnly)
				httpClient = githubv4mock.NewMockedHTTPClient(matcher)
			case "filter by closed state":
				matcher := githubv4mock.NewQueryMatcher(qBasicNoLabels, varsClosedOnly, mockResponseClosedOnly)
				httpClient = githubv4mock.NewMockedHTTPClient(matcher)
			case "filter by labels":
				matcher := githubv4mock.NewQueryMatcher(qWithLabels, varsWithLabels, mockResponseListAll)
				httpClient = githubv4mock.NewMockedHTTPClient(matcher)
			case "repository not found error":
				matcher := githubv4mock.NewQueryMatcher(qBasicNoLabels, varsRepoNotFound, mockErrorRepoNotFound)
				httpClient = githubv4mock.NewMockedHTTPClient(matcher)
			}

			gqlClient := githubv4.NewClient(httpClient)
			deps := BaseDeps{
				GQLClient: gqlClient,
			}
			handler := serverTool.Handler(deps)

			req := createMCPRequest(tc.reqParams)
			res, err := handler(ContextWithDeps(context.Background(), deps), &req)
			text := getTextResult(t, res).Text

			if tc.expectError {
				require.True(t, res.IsError)
				assert.Contains(t, text, tc.errContains)
				return
			}
			require.NoError(t, err)

			// Parse the structured response with pagination info
			var response MinimalIssuesResponse
			err = json.Unmarshal([]byte(text), &response)
			require.NoError(t, err)

			assert.Len(t, response.Issues, tc.expectedCount, "Expected %d issues, got %d", tc.expectedCount, len(response.Issues))

			// Verify pagination metadata
			assert.Equal(t, tc.expectedCount, response.TotalCount)
			assert.False(t, response.PageInfo.HasNextPage)
			assert.False(t, response.PageInfo.HasPreviousPage)

			// Verify that returned issues have expected structure
			for _, issue := range response.Issues {
				assert.NotZero(t, issue.Number, "Issue should have number")
				assert.NotEmpty(t, issue.Title, "Issue should have title")
				assert.NotEmpty(t, issue.State, "Issue should have state")
				assert.NotEmpty(t, issue.CreatedAt, "Issue should have created_at")
				assert.NotEmpty(t, issue.UpdatedAt, "Issue should have updated_at")
				assert.NotNil(t, issue.User, "Issue should have user")
				assert.NotEmpty(t, issue.User.Login, "Issue user should have login")
				assert.Empty(t, issue.HTMLURL, "html_url should be empty (not populated by GraphQL fragment)")

				// Labels should be flattened to name strings
				for _, label := range issue.Labels {
					assert.NotEmpty(t, label, "Label should be a non-empty string")
				}
			}
		})
	}
}

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

	assert.Equal(t, "issue_write", tool.Name)
	assert.NotEmpty(t, tool.Description)
	assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "method")
	assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "owner")
	assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "repo")
	assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "issue_number")
	assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "title")
	assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "body")
	assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "labels")
	assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "assignees")
	assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "milestone")
	assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "type")
	assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "state")
	assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "state_reason")
	assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "duplicate_of")
	assert.ElementsMatch(t, tool.InputSchema.(*jsonschema.Schema).Required, []string{"method", "owner", "repo"})

	// Mock issues for reuse across test cases
	mockBaseIssue := &github.Issue{
		Number:    github.Ptr(123),
		Title:     github.Ptr("Title"),
		Body:      github.Ptr("Description"),
		State:     github.Ptr("open"),
		HTMLURL:   github.Ptr("https://github.com/owner/repo/issues/123"),
		Assignees: []*github.User{{Login: github.Ptr("assignee1")}, {Login: github.Ptr("assignee2")}},
		Labels:    []*github.Label{{Name: github.Ptr("bug")}, {Name: github.Ptr("priority")}},
		Milestone: &github.Milestone{Number: github.Ptr(5)},
		Type:      &github.IssueType{Name: github.Ptr("Bug")},
	}

	mockUpdatedIssue := &github.Issue{
		Number:      github.Ptr(123),
		Title:       github.Ptr("Updated Title"),
		Body:        github.Ptr("Updated Description"),
		State:       github.Ptr("closed"),
		StateReason: github.Ptr("duplicate"),
		HTMLURL:     github.Ptr("https://github.com/owner/repo/issues/123"),
		Assignees:   []*github.User{{Login: github.Ptr("assignee1")}, {Login: github.Ptr("assignee2")}},
		Labels:      []*github.Label{{Name: github.Ptr("bug")}, {Name: github.Ptr("priority")}},
		Milestone:   &github.Milestone{Number: github.Ptr(5)},
		Type:        &github.IssueType{Name: github.Ptr("Bug")},
	}

	mockReopenedIssue := &github.Issue{
		Number:      github.Ptr(123),
		Title:       github.Ptr("Title"),
		State:       github.Ptr("open"),
		StateReason: github.Ptr("reopened"),
		HTMLURL:     github.Ptr("https://github.com/owner/repo/issues/123"),
	}

	// Mock GraphQL responses for reuse across test cases
	issueIDQueryResponse := githubv4mock.DataResponse(map[string]any{
		"repository": map[string]any{
			"issue": map[string]any{
				"id": "I_kwDOA0xdyM50BPaO",
			},
		},
	})

	duplicateIssueIDQueryResponse := githubv4mock.DataResponse(map[string]any{
		"repository": map[string]any{
			"issue": map[string]any{
				"id": "I_kwDOA0xdyM50BPaO",
			},
			"duplicateIssue": map[string]any{
				"id": "I_kwDOA0xdyM50BPbP",
			},
		},
	})

	closeSuccessResponse := githubv4mock.DataResponse(map[string]any{
		"closeIssue": map[string]any{
			"issue": map[string]any{
				"id":     "I_kwDOA0xdyM50BPaO",
				"number": 123,
				"url":    "https://github.com/owner/repo/issues/123",
				"state":  "CLOSED",
			},
		},
	})

	reopenSuccessResponse := githubv4mock.DataResponse(map[string]any{
		"reopenIssue": map[string]any{
			"issue": map[string]any{
				"id":     "I_kwDOA0xdyM50BPaO",
				"number": 123,
				"url":    "https://github.com/owner/repo/issues/123",
				"state":  "OPEN",
			},
		},
	})

	duplicateStateReason := IssueClosedStateReasonDuplicate

	tests := []struct {
		name             string
		mockedRESTClient *http.Client
		mockedGQLClient  *http.Client
		requestArgs      map[string]any
		expectError      bool
		expectedIssue    *github.Issue
		expectedErrMsg   string
	}{
		{
			name: "partial update of non-state fields only",
			mockedRESTClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				PatchReposIssuesByOwnerByRepoByIssueNumber: expectRequestBody(t, map[string]any{
					"title": "Updated Title",
					"body":  "Updated Description",
				}).andThen(
					mockResponse(t, http.StatusOK, mockUpdatedIssue),
				),
			}),
			mockedGQLClient: githubv4mock.NewMockedHTTPClient(),
			requestArgs: map[string]any{
				"method":       "update",
				"owner":        "owner",
				"repo":         "repo",
				"issue_number": float64(123),
				"title":        "Updated Title",
				"body":         "Updated Description",
			},
			expectError:   false,
			expectedIssue: mockUpdatedIssue,
		},
		{
			name: "issue not found when updating non-state fields only",
			mockedRESTClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				PatchReposIssuesByOwnerByRepoByIssueNumber: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
					w.WriteHeader(http.StatusNotFound)
					_, _ = w.Write([]byte(`{"message": "Not Found"}`))
				}),
			}),
			mockedGQLClient: githubv4mock.NewMockedHTTPClient(),
			requestArgs: map[string]any{
				"method":       "update",
				"owner":        "owner",
				"repo":         "repo",
				"issue_number": float64(999),
				"title":        "Updated Title",
			},
			expectError:    true,
			expectedErrMsg: "failed to update issue",
		},
		{
			name: "close issue as duplicate",
			mockedRESTClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				PatchReposIssuesByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusOK, mockBaseIssue),
			}),
			mockedGQLClient: githubv4mock.NewMockedHTTPClient(
				githubv4mock.NewQueryMatcher(
					struct {
						Repository struct {
							Issue struct {
								ID githubv4.ID
							} `graphql:"issue(number: $issueNumber)"`
							DuplicateIssue struct {
								ID githubv4.ID
							} `graphql:"duplicateIssue: issue(number: $duplicateOf)"`
						} `graphql:"repository(owner: $owner, name: $repo)"`
					}{},
					map[string]any{
						"owner":       githubv4.String("owner"),
						"repo":        githubv4.String("repo"),
						"issueNumber": githubv4.Int(123),
						"duplicateOf": githubv4.Int(456),
					},
					duplicateIssueIDQueryResponse,
				),
				githubv4mock.NewMutationMatcher(
					struct {
						CloseIssue struct {
							Issue struct {
								ID     githubv4.ID
								Number githubv4.Int
								URL    githubv4.String
								State  githubv4.String
							}
						} `graphql:"closeIssue(input: $input)"`
					}{},
					CloseIssueInput{
						IssueID:          "I_kwDOA0xdyM50BPaO",
						StateReason:      &duplicateStateReason,
						DuplicateIssueID: githubv4.NewID("I_kwDOA0xdyM50BPbP"),
					},
					nil,
					closeSuccessResponse,
				),
			),
			requestArgs: map[string]any{
				"method":       "update",
				"owner":        "owner",
				"repo":         "repo",
				"issue_number": float64(123),
				"state":        "closed",
				"state_reason": "duplicate",
				"duplicate_of": float64(456),
			},
			expectError:   false,
			expectedIssue: mockUpdatedIssue,
		},
		{
			name: "reopen issue",
			mockedRESTClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				PatchReposIssuesByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusOK, mockBaseIssue),
			}),
			mockedGQLClient: githubv4mock.NewMockedHTTPClient(
				githubv4mock.NewQueryMatcher(
					struct {
						Repository struct {
							Issue struct {
								ID githubv4.ID
							} `graphql:"issue(number: $issueNumber)"`
						} `graphql:"repository(owner: $owner, name: $repo)"`
					}{},
					map[string]any{
						"owner":       githubv4.String("owner"),
						"repo":        githubv4.String("repo"),
						"issueNumber": githubv4.Int(123),
					},
					issueIDQueryResponse,
				),
				githubv4mock.NewMutationMatcher(
					struct {
						ReopenIssue struct {
							Issue struct {
								ID     githubv4.ID
								Number githubv4.Int
								URL    githubv4.String
								State  githubv4.String
							}
						} `graphql:"reopenIssue(input: $input)"`
					}{},
					githubv4.ReopenIssueInput{
						IssueID: "I_kwDOA0xdyM50BPaO",
					},
					nil,
					reopenSuccessResponse,
				),
			),
			requestArgs: map[string]any{
				"method":       "update",
				"owner":        "owner",
				"repo":         "repo",
				"issue_number": float64(123),
				"state":        "open",
			},
			expectError:   false,
			expectedIssue: mockReopenedIssue,
		},
		{
			name: "main issue not found when trying to close it",
			mockedRESTClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				PatchReposIssuesByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusOK, mockBaseIssue),
			}),
			mockedGQLClient: githubv4mock.NewMockedHTTPClient(
				githubv4mock.NewQueryMatcher(
					struct {
						Repository struct {
							Issue struct {
								ID githubv4.ID
							} `graphql:"issue(number: $issueNumber)"`
						} `graphql:"repository(owner: $owner, name: $repo)"`
					}{},
					map[string]any{
						"owner":       githubv4.String("owner"),
						"repo":        githubv4.String("repo"),
						"issueNumber": githubv4.Int(999),
					},
					githubv4mock.ErrorResponse("Could not resolve to an Issue with the number of 999."),
				),
			),
			requestArgs: map[string]any{
				"method":       "update",
				"owner":        "owner",
				"repo":         "repo",
				"issue_number": float64(999),
				"state":        "closed",
				"state_reason": "not_planned",
			},
			expectError:    true,
			expectedErrMsg: "Failed to find issues",
		},
		{
			name: "duplicate issue not found when closing as duplicate",
			mockedRESTClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				PatchReposIssuesByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusOK, mockBaseIssue),
			}),
			mockedGQLClient: githubv4mock.NewMockedHTTPClient(
				githubv4mock.NewQueryMatcher(
					struct {
						Repository struct {
							Issue struct {
								ID githubv4.ID
							} `graphql:"issue(number: $issueNumber)"`
							DuplicateIssue struct {
								ID githubv4.ID
							} `graphql:"duplicateIssue: issue(number: $duplicateOf)"`
						} `graphql:"repository(owner: $owner, name: $repo)"`
					}{},
					map[string]any{
						"owner":       githubv4.String("owner"),
						"repo":        githubv4.String("repo"),
						"issueNumber": githubv4.Int(123),
						"duplicateOf": githubv4.Int(999),
					},
					githubv4mock.ErrorResponse("Could not resolve to an Issue with the number of 999."),
				),
			),
			requestArgs: map[string]any{
				"method":       "update",
				"owner":        "owner",
				"repo":         "repo",
				"issue_number": float64(123),
				"state":        "closed",
				"state_reason": "duplicate",
				"duplicate_of": float64(999),
			},
			expectError:    true,
			expectedErrMsg: "Failed to find issues",
		},
		{
			name: "close as duplicate with combined non-state updates",
			mockedRESTClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				PatchReposIssuesByOwnerByRepoByIssueNumber: expectRequestBody(t, map[string]any{
					"title":     "Updated Title",
					"body":      "Updated Description",
					"labels":    []any{"bug", "priority"},
					"assignees": []any{"assignee1", "assignee2"},
					"milestone": float64(5),
					"type":      "Bug",
				}).andThen(
					mockResponse(t, http.StatusOK, &github.Issue{
						Number:    github.Ptr(123),
						Title:     github.Ptr("Updated Title"),
						Body:      github.Ptr("Updated Description"),
						Labels:    []*github.Label{{Name: github.Ptr("bug")}, {Name: github.Ptr("priority")}},
						Assignees: []*github.User{{Login: github.Ptr("assignee1")}, {Login: github.Ptr("assignee2")}},
						Milestone: &github.Milestone{Number: github.Ptr(5)},
						Type:      &github.IssueType{Name: github.Ptr("Bug")},
						State:     github.Ptr("open"), // Still open after REST update
						HTMLURL:   github.Ptr("https://github.com/owner/repo/issues/123"),
					}),
				),
			}),
			mockedGQLClient: githubv4mock.NewMockedHTTPClient(
				githubv4mock.NewQueryMatcher(
					struct {
						Repository struct {
							Issue struct {
								ID githubv4.ID
							} `graphql:"issue(number: $issueNumber)"`
							DuplicateIssue struct {
								ID githubv4.ID
							} `graphql:"duplicateIssue: issue(number: $duplicateOf)"`
						} `graphql:"repository(owner: $owner, name: $repo)"`
					}{},
					map[string]any{
						"owner":       githubv4.String("owner"),
						"repo":        githubv4.String("repo"),
						"issueNumber": githubv4.Int(123),
						"duplicateOf": githubv4.Int(456),
					},
					duplicateIssueIDQueryResponse,
				),
				githubv4mock.NewMutationMatcher(
					struct {
						CloseIssue struct {
							Issue struct {
								ID     githubv4.ID
								Number githubv4.Int
								URL    githubv4.String
								State  githubv4.String
							}
						} `graphql:"closeIssue(input: $input)"`
					}{},
					CloseIssueInput{
						IssueID:          "I_kwDOA0xdyM50BPaO",
						StateReason:      &duplicateStateReason,
						DuplicateIssueID: githubv4.NewID("I_kwDOA0xdyM50BPbP"),
					},
					nil,
					closeSuccessResponse,
				),
			),
			requestArgs: map[string]any{
				"method":       "update",
				"owner":        "owner",
				"repo":         "repo",
				"issue_number": float64(123),
				"title":        "Updated Title",
				"body":         "Updated Description",
				"labels":       []any{"bug", "priority"},
				"assignees":    []any{"assignee1", "assignee2"},
				"milestone":    float64(5),
				"type":         "Bug",
				"state":        "closed",
				"state_reason": "duplicate",
				"duplicate_of": float64(456),
			},
			expectError:   false,
			expectedIssue: mockUpdatedIssue,
		},
		{
			name:             "duplicate_of without duplicate state_reason should fail",
			mockedRESTClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}),
			mockedGQLClient:  githubv4mock.NewMockedHTTPClient(),
			requestArgs: map[string]any{
				"method":       "update",
				"owner":        "owner",
				"repo":         "repo",
				"issue_number": float64(123),
				"state":        "closed",
				"state_reason": "completed",
				"duplicate_of": float64(456),
			},
			expectError:    true,
			expectedErrMsg: "duplicate_of can only be used when state_reason is 'duplicate'",
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			// Setup clients with mocks
			restClient := github.NewClient(tc.mockedRESTClient)
			gqlClient := githubv4.NewClient(tc.mockedGQLClient)
			deps := BaseDeps{
				Client:    restClient,
				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)
			if result.IsError {
				t.Fatalf("Unexpected error result: %s", getErrorResult(t, result).Text)
			}

			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.expectedIssue.GetHTMLURL(), updateResp.URL)
		})
	}
}

func Test_ParseISOTimestamp(t *testing.T) {
	tests := []struct {
		name         string
		input        string
		expectedErr  bool
		expectedTime time.Time
	}{
		{
			name:         "valid RFC3339 format",
			input:        "2023-01-15T14:30:00Z",
			expectedErr:  false,
			expectedTime: time.Date(2023, 1, 15, 14, 30, 0, 0, time.UTC),
		},
		{
			name:         "valid date only format",
			input:        "2023-01-15",
			expectedErr:  false,
			expectedTime: time.Date(2023, 1, 15, 0, 0, 0, 0, time.UTC),
		},
		{
			name:        "empty timestamp",
			input:       "",
			expectedErr: true,
		},
		{
			name:        "invalid format",
			input:       "15/01/2023",
			expectedErr: true,
		},
		{
			name:        "invalid date",
			input:       "2023-13-45",
			expectedErr: true,
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			parsedTime, err := parseISOTimestamp(tc.input)

			if tc.expectedErr {
				assert.Error(t, err)
			} else {
				assert.NoError(t, err)
				assert.Equal(t, tc.expectedTime, parsedTime)
			}
		})
	}
}

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

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

	// Setup mock comments for success case
	mockComments := []*github.IssueComment{
		{
			ID:   github.Ptr(int64(123)),
			Body: github.Ptr("This is the first comment"),
			User: &github.User{
				Login: github.Ptr("user1"),
			},
			CreatedAt: &github.Timestamp{Time: time.Now().Add(-time.Hour * 24)},
		},
		{
			ID:   github.Ptr(int64(456)),
			Body: github.Ptr("This is the second comment"),
			User: &github.User{
				Login: github.Ptr("user2"),
			},
			CreatedAt: &github.Timestamp{Time: time.Now().Add(-time.Hour)},
		},
	}

	tests := []struct {
		name             string
		mockedClient     *http.Client
		gqlHTTPClient    *http.Client
		requestArgs      map[string]any
		expectError      bool
		expectedComments []*github.IssueComment
		expectedErrMsg   string
		lockdownEnabled  bool
	}{
		{
			name: "successful comments retrieval",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				GetReposIssuesCommentsByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusOK, mockComments),
			}),
			requestArgs: map[string]any{
				"method":       "get_comments",
				"owner":        "owner",
				"repo":         "repo",
				"issue_number": float64(42),
			},
			expectError:      false,
			expectedComments: mockComments,
		},
		{
			name: "successful comments retrieval with pagination",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				GetReposIssuesCommentsByOwnerByRepoByIssueNumber: expectQueryParams(t, map[string]string{
					"page":     "2",
					"per_page": "10",
				}).andThen(
					mockResponse(t, http.StatusOK, mockComments),
				),
			}),
			requestArgs: map[string]any{
				"method":       "get_comments",
				"owner":        "owner",
				"repo":         "repo",
				"issue_number": float64(42),
				"page":         float64(2),
				"perPage":      float64(10),
			},
			expectError:      false,
			expectedComments: mockComments,
		},
		{
			name: "issue not found",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				GetReposIssuesCommentsByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusNotFound, `{"message": "Issue not found"}`),
			}),
			requestArgs: map[string]any{
				"method":       "get_comments",
				"owner":        "owner",
				"repo":         "repo",
				"issue_number": float64(999),
			},
			expectError:    true,
			expectedErrMsg: "failed to get issue comments",
		},
		{
			name: "lockdown enabled filters comments without push access",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				GetReposIssuesCommentsByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusOK, []*github.IssueComment{
					{
						ID:   github.Ptr(int64(789)),
						Body: github.Ptr("Maintainer comment"),
						User: &github.User{Login: github.Ptr("maintainer")},
					},
					{
						ID:   github.Ptr(int64(790)),
						Body: github.Ptr("External user comment"),
						User: &github.User{Login: github.Ptr("testuser")},
					},
				}),
			}),
			gqlHTTPClient: newRepoAccessHTTPClient(),
			requestArgs: map[string]any{
				"method":       "get_comments",
				"owner":        "owner",
				"repo":         "repo",
				"issue_number": float64(42),
			},
			expectError: false,
			expectedComments: []*github.IssueComment{
				{
					ID:   github.Ptr(int64(789)),
					Body: github.Ptr("Maintainer comment"),
					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 := github.NewClient(tc.mockedClient)
			var gqlClient *githubv4.Client
			if tc.gqlHTTPClient != nil {
				gqlClient = githubv4.NewClient(tc.gqlHTTPClient)
			} else {
				gqlClient = githubv4.NewClient(nil)
			}
			cache := stubRepoAccessCache(gqlClient, 15*time.Minute)
			flags := stubFeatureFlags(map[string]bool{"lockdown-mode": tc.lockdownEnabled})
			deps := BaseDeps{
				Client:          client,
				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.Error(t, err)
				assert.Contains(t, err.Error(), tc.expectedErrMsg)
				return
			}

			require.NoError(t, err)
			textContent := getTextResult(t, result)

			// Unmarshal and verify the result
			var returnedComments []MinimalIssueComment
			err = json.Unmarshal([]byte(textContent.Text), &returnedComments)
			require.NoError(t, err)
			assert.Equal(t, len(tc.expectedComments), len(returnedComments))
			for i := range tc.expectedComments {
				require.NotNil(t, tc.expectedComments[i].User)
				require.NotNil(t, returnedComments[i].User)
				assert.Equal(t, tc.expectedComments[i].GetID(), returnedComments[i].ID)
				assert.Equal(t, tc.expectedComments[i].GetBody(), returnedComments[i].Body)
				assert.Equal(t, tc.expectedComments[i].GetUser().GetLogin(), returnedComments[i].User.Login)
			}
		})
	}
}

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

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

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

	tests := []struct {
		name               string
		requestArgs        map[string]any
		mockedClient       *http.Client
		expectToolError    bool
		expectedToolErrMsg string
	}{
		{
			name: "successful issue labels listing",
			requestArgs: map[string]any{
				"method":       "get_labels",
				"owner":        "owner",
				"repo":         "repo",
				"issue_number": float64(123),
			},
			mockedClient: githubv4mock.NewMockedHTTPClient(
				githubv4mock.NewQueryMatcher(
					struct {
						Repository struct {
							Issue struct {
								Labels struct {
									Nodes []struct {
										ID          githubv4.ID
										Name        githubv4.String
										Color       githubv4.String
										Description githubv4.String
									}
									TotalCount githubv4.Int
								} `graphql:"labels(first: 100)"`
							} `graphql:"issue(number: $issueNumber)"`
						} `graphql:"repository(owner: $owner, name: $repo)"`
					}{},
					map[string]any{
						"owner":       githubv4.String("owner"),
						"repo":        githubv4.String("repo"),
						"issueNumber": githubv4.Int(123),
					},
					githubv4mock.DataResponse(map[string]any{
						"repository": map[string]any{
							"issue": map[string]any{
								"labels": map[string]any{
									"nodes": []any{
										map[string]any{
											"id":          githubv4.ID("label-1"),
											"name":        githubv4.String("bug"),
											"color":       githubv4.String("d73a4a"),
											"description": githubv4.String("Something isn't working"),
										},
									},
									"totalCount": githubv4.Int(1),
								},
							},
						},
					}),
				),
			),
			expectToolError: false,
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			gqlClient := githubv4.NewClient(tc.mockedClient)
			client := github.NewClient(nil)
			deps := BaseDeps{
				Client:          client,
				GQLClient:       gqlClient,
				RepoAccessCache: stubRepoAccessCache(gqlClient, 15*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)

			require.NoError(t, err)
			assert.NotNil(t, result)

			if tc.expectToolError {
				assert.True(t, result.IsError)
				if tc.expectedToolErrMsg != "" {
					textContent := getErrorResult(t, result)
					assert.Contains(t, textContent.Text, tc.expectedToolErrMsg)
				}
			} else {
				assert.False(t, result.IsError)
			}
		})
	}
}

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

	assert.Equal(t, "sub_issue_write", tool.Name)
	assert.NotEmpty(t, tool.Description)
	assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "method")
	assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "owner")
	assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "repo")
	assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "issue_number")
	assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "sub_issue_id")
	assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "replace_parent")
	assert.ElementsMatch(t, tool.InputSchema.(*jsonschema.Schema).Required, []string{"method", "owner", "repo", "issue_number", "sub_issue_id"})

	// Setup mock issue for success case (matches GitHub API response format)
	mockIssue := &github.Issue{
		Number:  github.Ptr(42),
		Title:   github.Ptr("Parent Issue"),
		Body:    github.Ptr("This is the parent issue with a sub-issue"),
		State:   github.Ptr("open"),
		HTMLURL: github.Ptr("https://github.com/owner/repo/issues/42"),
		User: &github.User{
			Login: github.Ptr("testuser"),
		},
		Labels: []*github.Label{
			{
				Name:        github.Ptr("enhancement"),
				Color:       github.Ptr("84b6eb"),
				Description: github.Ptr("New feature or request"),
			},
		},
	}

	tests := []struct {
		name           string
		mockedClient   *http.Client
		requestArgs    map[string]any
		expectError    bool
		expectedIssue  *github.Issue
		expectedErrMsg string
	}{
		{
			name: "successful sub-issue addition with all parameters",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				PostReposIssuesSubIssuesByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusCreated, mockIssue),
			}),
			requestArgs: map[string]any{
				"method":         "add",
				"owner":          "owner",
				"repo":           "repo",
				"issue_number":   float64(42),
				"sub_issue_id":   float64(123),
				"replace_parent": true,
			},
			expectError:   false,
			expectedIssue: mockIssue,
		},
		{
			name: "successful sub-issue addition with minimal parameters",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				PostReposIssuesSubIssuesByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusCreated, mockIssue),
			}),
			requestArgs: map[string]any{
				"method":       "add",
				"owner":        "owner",
				"repo":         "repo",
				"issue_number": float64(42),
				"sub_issue_id": float64(456),
			},
			expectError:   false,
			expectedIssue: mockIssue,
		},
		{
			name: "successful sub-issue addition with replace_parent false",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				PostReposIssuesSubIssuesByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusCreated, mockIssue),
			}),
			requestArgs: map[string]any{
				"method":         "add",
				"owner":          "owner",
				"repo":           "repo",
				"issue_number":   float64(42),
				"sub_issue_id":   float64(789),
				"replace_parent": false,
			},
			expectError:   false,
			expectedIssue: mockIssue,
		},
		{
			name: "parent issue not found",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				PostReposIssuesSubIssuesByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusNotFound, `{"message": "Parent issue not found"}`),
			}),
			requestArgs: map[string]any{
				"method":       "add",
				"owner":        "owner",
				"repo":         "repo",
				"issue_number": float64(999),
				"sub_issue_id": float64(123),
			},
			expectError:    false,
			expectedErrMsg: "failed to add sub-issue",
		},
		{
			name: "sub-issue not found",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				PostReposIssuesSubIssuesByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusNotFound, `{"message": "Sub-issue not found"}`),
			}),
			requestArgs: map[string]any{
				"method":       "add",
				"owner":        "owner",
				"repo":         "repo",
				"issue_number": float64(42),
				"sub_issue_id": float64(999),
			},
			expectError:    false,
			expectedErrMsg: "failed to add sub-issue",
		},
		{
			name: "validation failed - sub-issue cannot be parent of itself",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				PostReposIssuesSubIssuesByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusUnprocessableEntity, `{"message": "Validation failed", "errors": [{"message": "Sub-issue cannot be a parent of itself"}]}`),
			}),
			requestArgs: map[string]any{
				"method":       "add",
				"owner":        "owner",
				"repo":         "repo",
				"issue_number": float64(42),
				"sub_issue_id": float64(42),
			},
			expectError:    false,
			expectedErrMsg: "failed to add sub-issue",
		},
		{
			name: "insufficient permissions",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				PostReposIssuesSubIssuesByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusForbidden, `{"message": "Must have write access to repository"}`),
			}),
			requestArgs: map[string]any{
				"method":       "add",
				"owner":        "owner",
				"repo":         "repo",
				"issue_number": float64(42),
				"sub_issue_id": float64(123),
			},
			expectError:    false,
			expectedErrMsg: "failed to add sub-issue",
		},
		{
			name:         "missing required parameter owner",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}),
			requestArgs: map[string]any{
				"method":       "add",
				"repo":         "repo",
				"issue_number": float64(42),
				"sub_issue_id": float64(123),
			},
			expectError:    false,
			expectedErrMsg: "missing required parameter: owner",
		},
		{
			name:         "missing required parameter sub_issue_id",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}),
			requestArgs: map[string]any{
				"method":       "add",
				"owner":        "owner",
				"repo":         "repo",
				"issue_number": float64(42),
			},
			expectError:    false,
			expectedErrMsg: "missing required parameter: sub_issue_id",
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			// Setup client with mock
			client := github.NewClient(tc.mockedClient)
			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.Error(t, err)
				assert.Contains(t, err.Error(), tc.expectedErrMsg)
				return
			}

			if tc.expectedErrMsg != "" {
				require.NotNil(t, 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 result
			var returnedIssue github.Issue
			err = json.Unmarshal([]byte(textContent.Text), &returnedIssue)
			require.NoError(t, err)
			assert.Equal(t, *tc.expectedIssue.Number, *returnedIssue.Number)
			assert.Equal(t, *tc.expectedIssue.Title, *returnedIssue.Title)
			assert.Equal(t, *tc.expectedIssue.Body, *returnedIssue.Body)
			assert.Equal(t, *tc.expectedIssue.State, *returnedIssue.State)
			assert.Equal(t, *tc.expectedIssue.HTMLURL, *returnedIssue.HTMLURL)
			assert.Equal(t, *tc.expectedIssue.User.Login, *returnedIssue.User.Login)
		})
	}
}

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

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

	// Setup mock sub-issues for success case
	mockSubIssues := []*github.Issue{
		{
			Number:  github.Ptr(123),
			Title:   github.Ptr("Sub-issue 1"),
			Body:    github.Ptr("This is the first sub-issue"),
			State:   github.Ptr("open"),
			HTMLURL: github.Ptr("https://github.com/owner/repo/issues/123"),
			User: &github.User{
				Login: github.Ptr("user1"),
			},
			Labels: []*github.Label{
				{
					Name:        github.Ptr("bug"),
					Color:       github.Ptr("d73a4a"),
					Description: github.Ptr("Something isn't working"),
				},
			},
		},
		{
			Number:  github.Ptr(124),
			Title:   github.Ptr("Sub-issue 2"),
			Body:    github.Ptr("This is the second sub-issue"),
			State:   github.Ptr("closed"),
			HTMLURL: github.Ptr("https://github.com/owner/repo/issues/124"),
			User: &github.User{
				Login: github.Ptr("user2"),
			},
			Assignees: []*github.User{
				{Login: github.Ptr("assignee1")},
			},
		},
	}

	tests := []struct {
		name              string
		mockedClient      *http.Client
		requestArgs       map[string]any
		expectError       bool
		expectedSubIssues []*github.Issue
		expectedErrMsg    string
	}{
		{
			name: "successful sub-issues listing with minimal parameters",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				GetReposIssuesSubIssuesByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusOK, mockSubIssues),
			}),
			requestArgs: map[string]any{
				"method":       "get_sub_issues",
				"owner":        "owner",
				"repo":         "repo",
				"issue_number": float64(42),
			},
			expectError:       false,
			expectedSubIssues: mockSubIssues,
		},
		{
			name: "successful sub-issues listing with pagination",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				GetReposIssuesSubIssuesByOwnerByRepoByIssueNumber: expectQueryParams(t, map[string]string{
					"page":     "2",
					"per_page": "10",
				}).andThen(
					mockResponse(t, http.StatusOK, mockSubIssues),
				),
			}),
			requestArgs: map[string]any{
				"method":       "get_sub_issues",
				"owner":        "owner",
				"repo":         "repo",
				"issue_number": float64(42),
				"page":         float64(2),
				"perPage":      float64(10),
			},
			expectError:       false,
			expectedSubIssues: mockSubIssues,
		},
		{
			name: "successful sub-issues listing with empty result",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				GetReposIssuesSubIssuesByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusOK, []*github.Issue{}),
			}),
			requestArgs: map[string]any{
				"method":       "get_sub_issues",
				"owner":        "owner",
				"repo":         "repo",
				"issue_number": float64(42),
			},
			expectError:       false,
			expectedSubIssues: []*github.Issue{},
		},
		{
			name: "parent issue not found",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				GetReposIssuesSubIssuesByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusNotFound, `{"message": "Not Found"}`),
			}),
			requestArgs: map[string]any{
				"method":       "get_sub_issues",
				"owner":        "owner",
				"repo":         "repo",
				"issue_number": float64(999),
			},
			expectError:    false,
			expectedErrMsg: "failed to list sub-issues",
		},
		{
			name: "repository not found",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				GetReposIssuesSubIssuesByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusNotFound, `{"message": "Not Found"}`),
			}),
			requestArgs: map[string]any{
				"method":       "get_sub_issues",
				"owner":        "nonexistent",
				"repo":         "repo",
				"issue_number": float64(42),
			},
			expectError:    false,
			expectedErrMsg: "failed to list sub-issues",
		},
		{
			name: "sub-issues feature gone/deprecated",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				GetReposIssuesSubIssuesByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusGone, `{"message": "This feature has been deprecated"}`),
			}),
			requestArgs: map[string]any{
				"method":       "get_sub_issues",
				"owner":        "owner",
				"repo":         "repo",
				"issue_number": float64(42),
			},
			expectError:    false,
			expectedErrMsg: "failed to list sub-issues",
		},
		{
			name:         "missing required parameter owner",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}),
			requestArgs: map[string]any{
				"method":       "get_sub_issues",
				"repo":         "repo",
				"issue_number": float64(42),
			},
			expectError:    false,
			expectedErrMsg: "missing required parameter: owner",
		},
		{
			name:         "missing required parameter issue_number",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}),
			requestArgs: map[string]any{
				"method": "get_sub_issues",
				"owner":  "owner",
				"repo":   "repo",
			},
			expectError:    false,
			expectedErrMsg: "missing required parameter: issue_number",
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			// Setup client with mock
			client := github.NewClient(tc.mockedClient)
			gqlClient := githubv4.NewClient(nil)
			deps := BaseDeps{
				Client:          client,
				GQLClient:       gqlClient,
				RepoAccessCache: stubRepoAccessCache(gqlClient, 15*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.Error(t, err)
				assert.Contains(t, err.Error(), tc.expectedErrMsg)
				return
			}

			if tc.expectedErrMsg != "" {
				require.NotNil(t, 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 result
			var returnedSubIssues []*github.Issue
			err = json.Unmarshal([]byte(textContent.Text), &returnedSubIssues)
			require.NoError(t, err)

			assert.Len(t, returnedSubIssues, len(tc.expectedSubIssues))
			for i, subIssue := range returnedSubIssues {
				if i < len(tc.expectedSubIssues) {
					assert.Equal(t, *tc.expectedSubIssues[i].Number, *subIssue.Number)
					assert.Equal(t, *tc.expectedSubIssues[i].Title, *subIssue.Title)
					assert.Equal(t, *tc.expectedSubIssues[i].State, *subIssue.State)
					assert.Equal(t, *tc.expectedSubIssues[i].HTMLURL, *subIssue.HTMLURL)
					assert.Equal(t, *tc.expectedSubIssues[i].User.Login, *subIssue.User.Login)

					if tc.expectedSubIssues[i].Body != nil {
						assert.Equal(t, *tc.expectedSubIssues[i].Body, *subIssue.Body)
					}
				}
			}
		})
	}
}

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

	assert.Equal(t, "sub_issue_write", tool.Name)
	assert.NotEmpty(t, tool.Description)
	assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "method")
	assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "owner")
	assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "repo")
	assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "issue_number")
	assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "sub_issue_id")
	assert.ElementsMatch(t, tool.InputSchema.(*jsonschema.Schema).Required, []string{"method", "owner", "repo", "issue_number", "sub_issue_id"})

	// Setup mock issue for success case (matches GitHub API response format - the updated parent issue)
	mockIssue := &github.Issue{
		Number:  github.Ptr(42),
		Title:   github.Ptr("Parent Issue"),
		Body:    github.Ptr("This is the parent issue after sub-issue removal"),
		State:   github.Ptr("open"),
		HTMLURL: github.Ptr("https://github.com/owner/repo/issues/42"),
		User: &github.User{
			Login: github.Ptr("testuser"),
		},
		Labels: []*github.Label{
			{
				Name:        github.Ptr("enhancement"),
				Color:       github.Ptr("84b6eb"),
				Description: github.Ptr("New feature or request"),
			},
		},
	}

	tests := []struct {
		name           string
		mockedClient   *http.Client
		requestArgs    map[string]any
		expectError    bool
		expectedIssue  *github.Issue
		expectedErrMsg string
	}{
		{
			name: "successful sub-issue removal",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				DeleteReposIssuesSubIssueByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusOK, mockIssue),
			}),
			requestArgs: map[string]any{
				"method":       "remove",
				"owner":        "owner",
				"repo":         "repo",
				"issue_number": float64(42),
				"sub_issue_id": float64(123),
			},
			expectError:   false,
			expectedIssue: mockIssue,
		},
		{
			name: "parent issue not found",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				DeleteReposIssuesSubIssueByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusNotFound, `{"message": "Not Found"}`),
			}),
			requestArgs: map[string]any{
				"method":       "remove",
				"owner":        "owner",
				"repo":         "repo",
				"issue_number": float64(999),
				"sub_issue_id": float64(123),
			},
			expectError:    false,
			expectedErrMsg: "failed to remove sub-issue",
		},
		{
			name: "sub-issue not found",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				DeleteReposIssuesSubIssueByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusNotFound, `{"message": "Sub-issue not found"}`),
			}),
			requestArgs: map[string]any{
				"method":       "remove",
				"owner":        "owner",
				"repo":         "repo",
				"issue_number": float64(42),
				"sub_issue_id": float64(999),
			},
			expectError:    false,
			expectedErrMsg: "failed to remove sub-issue",
		},
		{
			name: "bad request - invalid sub_issue_id",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				DeleteReposIssuesSubIssueByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusBadRequest, `{"message": "Invalid sub_issue_id"}`),
			}),
			requestArgs: map[string]any{
				"method":       "remove",
				"owner":        "owner",
				"repo":         "repo",
				"issue_number": float64(42),
				"sub_issue_id": float64(-1),
			},
			expectError:    false,
			expectedErrMsg: "failed to remove sub-issue",
		},
		{
			name: "repository not found",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				DeleteReposIssuesSubIssueByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusNotFound, `{"message": "Not Found"}`),
			}),
			requestArgs: map[string]any{
				"method":       "remove",
				"owner":        "nonexistent",
				"repo":         "repo",
				"issue_number": float64(42),
				"sub_issue_id": float64(123),
			},
			expectError:    false,
			expectedErrMsg: "failed to remove sub-issue",
		},
		{
			name: "insufficient permissions",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				DeleteReposIssuesSubIssueByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusForbidden, `{"message": "Must have write access to repository"}`),
			}),
			requestArgs: map[string]any{
				"method":       "remove",
				"owner":        "owner",
				"repo":         "repo",
				"issue_number": float64(42),
				"sub_issue_id": float64(123),
			},
			expectError:    false,
			expectedErrMsg: "failed to remove sub-issue",
		},
		{
			name:         "missing required parameter owner",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}),
			requestArgs: map[string]any{
				"method":       "remove",
				"repo":         "repo",
				"issue_number": float64(42),
				"sub_issue_id": float64(123),
			},
			expectError:    false,
			expectedErrMsg: "missing required parameter: owner",
		},
		{
			name:         "missing required parameter sub_issue_id",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}),
			requestArgs: map[string]any{
				"method":       "remove",
				"owner":        "owner",
				"repo":         "repo",
				"issue_number": float64(42),
			},
			expectError:    false,
			expectedErrMsg: "missing required parameter: sub_issue_id",
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			// Setup client with mock
			client := github.NewClient(tc.mockedClient)
			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.Error(t, err)
				assert.Contains(t, err.Error(), tc.expectedErrMsg)
				return
			}

			if tc.expectedErrMsg != "" {
				require.NotNil(t, 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 result
			var returnedIssue github.Issue
			err = json.Unmarshal([]byte(textContent.Text), &returnedIssue)
			require.NoError(t, err)
			assert.Equal(t, *tc.expectedIssue.Number, *returnedIssue.Number)
			assert.Equal(t, *tc.expectedIssue.Title, *returnedIssue.Title)
			assert.Equal(t, *tc.expectedIssue.Body, *returnedIssue.Body)
			assert.Equal(t, *tc.expectedIssue.State, *returnedIssue.State)
			assert.Equal(t, *tc.expectedIssue.HTMLURL, *returnedIssue.HTMLURL)
			assert.Equal(t, *tc.expectedIssue.User.Login, *returnedIssue.User.Login)
		})
	}
}

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

	assert.Equal(t, "sub_issue_write", tool.Name)
	assert.NotEmpty(t, tool.Description)
	assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "method")
	assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "owner")
	assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "repo")
	assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "issue_number")
	assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "sub_issue_id")
	assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "after_id")
	assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "before_id")
	assert.ElementsMatch(t, tool.InputSchema.(*jsonschema.Schema).Required, []string{"method", "owner", "repo", "issue_number", "sub_issue_id"})

	// Setup mock issue for success case (matches GitHub API response format - the updated parent issue)
	mockIssue := &github.Issue{
		Number:  github.Ptr(42),
		Title:   github.Ptr("Parent Issue"),
		Body:    github.Ptr("This is the parent issue with reprioritized sub-issues"),
		State:   github.Ptr("open"),
		HTMLURL: github.Ptr("https://github.com/owner/repo/issues/42"),
		User: &github.User{
			Login: github.Ptr("testuser"),
		},
		Labels: []*github.Label{
			{
				Name:        github.Ptr("enhancement"),
				Color:       github.Ptr("84b6eb"),
				Description: github.Ptr("New feature or request"),
			},
		},
	}

	tests := []struct {
		name           string
		mockedClient   *http.Client
		requestArgs    map[string]any
		expectError    bool
		expectedIssue  *github.Issue
		expectedErrMsg string
	}{
		{
			name: "successful reprioritization with after_id",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				PatchReposIssuesSubIssuesPriorityByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusOK, mockIssue),
			}),
			requestArgs: map[string]any{
				"method":       "reprioritize",
				"owner":        "owner",
				"repo":         "repo",
				"issue_number": float64(42),
				"sub_issue_id": float64(123),
				"after_id":     float64(456),
			},
			expectError:   false,
			expectedIssue: mockIssue,
		},
		{
			name: "successful reprioritization with before_id",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				PatchReposIssuesSubIssuesPriorityByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusOK, mockIssue),
			}),
			requestArgs: map[string]any{
				"method":       "reprioritize",
				"owner":        "owner",
				"repo":         "repo",
				"issue_number": float64(42),
				"sub_issue_id": float64(123),
				"before_id":    float64(789),
			},
			expectError:   false,
			expectedIssue: mockIssue,
		},
		{
			name:         "validation error - neither after_id nor before_id specified",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}),
			requestArgs: map[string]any{
				"method":       "reprioritize",
				"owner":        "owner",
				"repo":         "repo",
				"issue_number": float64(42),
				"sub_issue_id": float64(123),
			},
			expectError:    false,
			expectedErrMsg: "either after_id or before_id must be specified",
		},
		{
			name:         "validation error - both after_id and before_id specified",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}),
			requestArgs: map[string]any{
				"method":       "reprioritize",
				"owner":        "owner",
				"repo":         "repo",
				"issue_number": float64(42),
				"sub_issue_id": float64(123),
				"after_id":     float64(456),
				"before_id":    float64(789),
			},
			expectError:    false,
			expectedErrMsg: "only one of after_id or before_id should be specified, not both",
		},
		{
			name: "parent issue not found",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				PatchReposIssuesSubIssuesPriorityByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusNotFound, `{"message": "Not Found"}`),
			}),
			requestArgs: map[string]any{
				"method":       "reprioritize",
				"owner":        "owner",
				"repo":         "repo",
				"issue_number": float64(999),
				"sub_issue_id": float64(123),
				"after_id":     float64(456),
			},
			expectError:    false,
			expectedErrMsg: "failed to reprioritize sub-issue",
		},
		{
			name: "sub-issue not found",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				PatchReposIssuesSubIssuesPriorityByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusNotFound, `{"message": "Sub-issue not found"}`),
			}),
			requestArgs: map[string]any{
				"method":       "reprioritize",
				"owner":        "owner",
				"repo":         "repo",
				"issue_number": float64(42),
				"sub_issue_id": float64(999),
				"after_id":     float64(456),
			},
			expectError:    false,
			expectedErrMsg: "failed to reprioritize sub-issue",
		},
		{
			name: "validation failed - positioning sub-issue not found",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				PatchReposIssuesSubIssuesPriorityByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusUnprocessableEntity, `{"message": "Validation failed", "errors": [{"message": "Positioning sub-issue not found"}]}`),
			}),
			requestArgs: map[string]any{
				"method":       "reprioritize",
				"owner":        "owner",
				"repo":         "repo",
				"issue_number": float64(42),
				"sub_issue_id": float64(123),
				"after_id":     float64(999),
			},
			expectError:    false,
			expectedErrMsg: "failed to reprioritize sub-issue",
		},
		{
			name: "insufficient permissions",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				PatchReposIssuesSubIssuesPriorityByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusForbidden, `{"message": "Must have write access to repository"}`),
			}),
			requestArgs: map[string]any{
				"method":       "reprioritize",
				"owner":        "owner",
				"repo":         "repo",
				"issue_number": float64(42),
				"sub_issue_id": float64(123),
				"after_id":     float64(456),
			},
			expectError:    false,
			expectedErrMsg: "failed to reprioritize sub-issue",
		},
		{
			name: "service unavailable",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				PatchReposIssuesSubIssuesPriorityByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusServiceUnavailable, `{"message": "Service Unavailable"}`),
			}),
			requestArgs: map[string]any{
				"method":       "reprioritize",
				"owner":        "owner",
				"repo":         "repo",
				"issue_number": float64(42),
				"sub_issue_id": float64(123),
				"before_id":    float64(456),
			},
			expectError:    false,
			expectedErrMsg: "failed to reprioritize sub-issue",
		},
		{
			name:         "missing required parameter owner",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}),
			requestArgs: map[string]any{
				"method":       "reprioritize",
				"repo":         "repo",
				"issue_number": float64(42),
				"sub_issue_id": float64(123),
				"after_id":     float64(456),
			},
			expectError:    false,
			expectedErrMsg: "missing required parameter: owner",
		},
		{
			name:         "missing required parameter sub_issue_id",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}),
			requestArgs: map[string]any{
				"method":       "reprioritize",
				"owner":        "owner",
				"repo":         "repo",
				"issue_number": float64(42),
				"after_id":     float64(456),
			},
			expectError:    false,
			expectedErrMsg: "missing required parameter: sub_issue_id",
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			// Setup client with mock
			client := github.NewClient(tc.mockedClient)
			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.Error(t, err)
				assert.Contains(t, err.Error(), tc.expectedErrMsg)
				return
			}

			if tc.expectedErrMsg != "" {
				require.NotNil(t, 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 result
			var returnedIssue github.Issue
			err = json.Unmarshal([]byte(textContent.Text), &returnedIssue)
			require.NoError(t, err)
			assert.Equal(t, *tc.expectedIssue.Number, *returnedIssue.Number)
			assert.Equal(t, *tc.expectedIssue.Title, *returnedIssue.Title)
			assert.Equal(t, *tc.expectedIssue.Body, *returnedIssue.Body)
			assert.Equal(t, *tc.expectedIssue.State, *returnedIssue.State)
			assert.Equal(t, *tc.expectedIssue.HTMLURL, *returnedIssue.HTMLURL)
			assert.Equal(t, *tc.expectedIssue.User.Login, *returnedIssue.User.Login)
		})
	}
}

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

	assert.Equal(t, "list_issue_types", tool.Name)
	assert.NotEmpty(t, tool.Description)
	assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "owner")
	assert.ElementsMatch(t, tool.InputSchema.(*jsonschema.Schema).Required, []string{"owner"})

	// Setup mock issue types for success case
	mockIssueTypes := []*github.IssueType{
		{
			ID:          github.Ptr(int64(1)),
			Name:        github.Ptr("bug"),
			Description: github.Ptr("Something isn't working"),
			Color:       github.Ptr("d73a4a"),
		},
		{
			ID:          github.Ptr(int64(2)),
			Name:        github.Ptr("feature"),
			Description: github.Ptr("New feature or enhancement"),
			Color:       github.Ptr("a2eeef"),
		},
	}

	tests := []struct {
		name               string
		mockedClient       *http.Client
		requestArgs        map[string]any
		expectError        bool
		expectedIssueTypes []*github.IssueType
		expectedErrMsg     string
	}{
		{
			name: "successful issue types retrieval",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				"GET /orgs/testorg/issue-types": mockResponse(t, http.StatusOK, mockIssueTypes),
			}),
			requestArgs: map[string]any{
				"owner": "testorg",
			},
			expectError:        false,
			expectedIssueTypes: mockIssueTypes,
		},
		{
			name: "organization not found",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				"GET /orgs/nonexistent/issue-types": mockResponse(t, http.StatusNotFound, `{"message": "Organization not found"}`),
			}),
			requestArgs: map[string]any{
				"owner": "nonexistent",
			},
			expectError:    true,
			expectedErrMsg: "failed to list issue types",
		},
		{
			name: "missing owner parameter",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				"GET /orgs/testorg/issue-types": mockResponse(t, http.StatusOK, mockIssueTypes),
			}),
			requestArgs:    map[string]any{},
			expectError:    false, // This should be handled by parameter validation, error returned in result
			expectedErrMsg: "missing required parameter: owner",
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			// Setup client with mock
			client := github.NewClient(tc.mockedClient)
			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
				}
				// Check if error is returned as tool result error
				require.NotNil(t, result)
				require.True(t, result.IsError)
				errorContent := getErrorResult(t, result)
				assert.Contains(t, errorContent.Text, tc.expectedErrMsg)
				return
			}

			// Check if it's a parameter validation error (returned as tool result error)
			if result != nil && result.IsError {
				errorContent := getErrorResult(t, result)
				if tc.expectedErrMsg != "" && strings.Contains(errorContent.Text, tc.expectedErrMsg) {
					return // This is expected for parameter validation errors
				}
			}

			require.NoError(t, err)
			require.NotNil(t, result)
			require.False(t, result.IsError)
			textContent := getTextResult(t, result)

			// Unmarshal and verify the result
			var returnedIssueTypes []*github.IssueType
			err = json.Unmarshal([]byte(textContent.Text), &returnedIssueTypes)
			require.NoError(t, err)

			if tc.expectedIssueTypes != nil {
				require.Equal(t, len(tc.expectedIssueTypes), len(returnedIssueTypes))
				for i, expected := range tc.expectedIssueTypes {
					assert.Equal(t, *expected.Name, *returnedIssueTypes[i].Name)
					assert.Equal(t, *expected.Description, *returnedIssueTypes[i].Description)
					assert.Equal(t, *expected.Color, *returnedIssueTypes[i].Color)
					assert.Equal(t, *expected.ID, *returnedIssueTypes[i].ID)
				}
			}
		})
	}
}
