package github

import (
	"context"
	"encoding/base64"
	"encoding/json"
	"net/http"
	"net/url"
	"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/raw"
	"github.com/github/github-mcp-server/pkg/translations"
	"github.com/github/github-mcp-server/pkg/utils"
	"github.com/google/go-github/v89/github"
	"github.com/google/jsonschema-go/jsonschema"
	"github.com/modelcontextprotocol/go-sdk/mcp"
	"github.com/shurcooL/githubv4"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

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

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

	assert.Equal(t, "get_file_contents", tool.Name)
	assert.NotEmpty(t, tool.Description)
	assert.Contains(t, schema.Properties, "owner")
	assert.Contains(t, schema.Properties, "repo")
	assert.Contains(t, schema.Properties, "path")
	assert.Contains(t, schema.Properties, "ref")
	assert.Contains(t, schema.Properties, "sha")
	assert.Contains(t, schema.Properties, "fields")
	assert.ElementsMatch(t, schema.Required, []string{"owner", "repo"})

	// Mock response for raw content
	mockRawContent := []byte("# Test Repository\n\nThis is a test repository.")

	// Setup mock directory content for success case
	mockDirContent := []*github.RepositoryContent{
		{
			Type:    github.Ptr("file"),
			Name:    github.Ptr("README.md"),
			Path:    github.Ptr("README.md"),
			SHA:     github.Ptr("abc123"),
			Size:    github.Ptr(42),
			HTMLURL: github.Ptr("https://github.com/owner/repo/blob/main/README.md"),
		},
		{
			Type:    github.Ptr("dir"),
			Name:    github.Ptr("src"),
			Path:    github.Ptr("src"),
			SHA:     github.Ptr("def456"),
			HTMLURL: github.Ptr("https://github.com/owner/repo/tree/main/src"),
		},
	}

	tests := []struct {
		name           string
		mockedClient   *http.Client
		requestArgs    map[string]any
		expectError    bool
		expectedResult any
		expectedErrMsg string
		expectStatus   int
		expectedMsg    string // optional: expected message text to verify in result
	}{
		{
			name: "successful text content fetch",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				GetReposGitRefByOwnerByRepoByRef: mockResponse(t, http.StatusOK, "{\"ref\": \"refs/heads/main\", \"object\": {\"sha\": \"\"}}"),
				GetReposByOwnerByRepo:            mockResponse(t, http.StatusOK, "{\"name\": \"repo\", \"default_branch\": \"main\"}"),
				GetReposContentsByOwnerByRepoByPath: func(w http.ResponseWriter, _ *http.Request) {
					w.WriteHeader(http.StatusOK)
					// Base64 encode the content as GitHub API does
					encodedContent := base64.StdEncoding.EncodeToString(mockRawContent)
					fileContent := &github.RepositoryContent{
						Name:     github.Ptr("README.md"),
						Path:     github.Ptr("README.md"),
						SHA:      github.Ptr("abc123"),
						Type:     github.Ptr("file"),
						Content:  github.Ptr(encodedContent),
						Size:     github.Ptr(len(mockRawContent)),
						Encoding: github.Ptr("base64"),
					}
					contentBytes, _ := json.Marshal(fileContent)
					_, _ = w.Write(contentBytes)
				},
			}),
			requestArgs: map[string]any{
				"owner": "owner",
				"repo":  "repo",
				"path":  "README.md",
				"ref":   "refs/heads/main",
			},
			expectError: false,
			expectedResult: mcp.ResourceContents{
				URI:      "repo://owner/repo/refs/heads/main/contents/README.md",
				Text:     "# Test Repository\n\nThis is a test repository.",
				MIMEType: "text/plain; charset=utf-8",
			},
		},
		{
			name: "successful binary file content fetch (PNG)",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				GetReposGitRefByOwnerByRepoByRef: mockResponse(t, http.StatusOK, "{\"ref\": \"refs/heads/main\", \"object\": {\"sha\": \"\"}}"),
				GetReposByOwnerByRepo:            mockResponse(t, http.StatusOK, "{\"name\": \"repo\", \"default_branch\": \"main\"}"),
				GetReposContentsByOwnerByRepoByPath: func(w http.ResponseWriter, _ *http.Request) {
					w.WriteHeader(http.StatusOK)
					// PNG magic bytes followed by some data
					pngContent := []byte("\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01")
					encodedContent := base64.StdEncoding.EncodeToString(pngContent)
					fileContent := &github.RepositoryContent{
						Name:     github.Ptr("test.png"),
						Path:     github.Ptr("test.png"),
						SHA:      github.Ptr("def456"),
						Type:     github.Ptr("file"),
						Content:  github.Ptr(encodedContent),
						Size:     github.Ptr(len(pngContent)),
						Encoding: github.Ptr("base64"),
					}
					contentBytes, _ := json.Marshal(fileContent)
					_, _ = w.Write(contentBytes)
				},
			}),
			requestArgs: map[string]any{
				"owner": "owner",
				"repo":  "repo",
				"path":  "test.png",
				"ref":   "refs/heads/main",
			},
			expectError: false,
			expectedResult: mcp.ResourceContents{
				URI:      "repo://owner/repo/refs/heads/main/contents/test.png",
				Blob:     []byte(base64.StdEncoding.EncodeToString([]byte("\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01"))),
				MIMEType: "image/png",
			},
		},
		{
			name: "successful binary file content fetch (PDF)",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				GetReposGitRefByOwnerByRepoByRef: mockResponse(t, http.StatusOK, "{\"ref\": \"refs/heads/main\", \"object\": {\"sha\": \"\"}}"),
				GetReposByOwnerByRepo:            mockResponse(t, http.StatusOK, "{\"name\": \"repo\", \"default_branch\": \"main\"}"),
				GetReposContentsByOwnerByRepoByPath: func(w http.ResponseWriter, _ *http.Request) {
					w.WriteHeader(http.StatusOK)
					// PDF magic bytes
					pdfContent := []byte("%PDF-1.4 fake pdf content")
					encodedContent := base64.StdEncoding.EncodeToString(pdfContent)
					fileContent := &github.RepositoryContent{
						Name:     github.Ptr("document.pdf"),
						Path:     github.Ptr("document.pdf"),
						SHA:      github.Ptr("pdf123"),
						Type:     github.Ptr("file"),
						Content:  github.Ptr(encodedContent),
						Size:     github.Ptr(len(pdfContent)),
						Encoding: github.Ptr("base64"),
					}
					contentBytes, _ := json.Marshal(fileContent)
					_, _ = w.Write(contentBytes)
				},
			}),
			requestArgs: map[string]any{
				"owner": "owner",
				"repo":  "repo",
				"path":  "document.pdf",
				"ref":   "refs/heads/main",
			},
			expectError: false,
			expectedResult: mcp.ResourceContents{
				URI:      "repo://owner/repo/refs/heads/main/contents/document.pdf",
				Blob:     []byte(base64.StdEncoding.EncodeToString([]byte("%PDF-1.4 fake pdf content"))),
				MIMEType: "application/pdf",
			},
		},
		{
			name: "successful directory content fetch",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				GetReposByOwnerByRepo:            mockResponse(t, http.StatusOK, "{\"name\": \"repo\", \"default_branch\": \"main\"}"),
				GetReposGitRefByOwnerByRepoByRef: mockResponse(t, http.StatusOK, "{\"ref\": \"refs/heads/main\", \"object\": {\"sha\": \"\"}}"),
				GetReposContentsByOwnerByRepoByPath: expectQueryParams(t, map[string]string{}).andThen(
					mockResponse(t, http.StatusOK, mockDirContent),
				),
				GetRawReposContentsByOwnerByRepoByPath: expectQueryParams(t, map[string]string{"branch": "main"}).andThen(
					mockResponse(t, http.StatusNotFound, nil),
				),
			}),
			requestArgs: map[string]any{
				"owner": "owner",
				"repo":  "repo",
				"path":  "src/",
			},
			expectError:    false,
			expectedResult: mockDirContent,
		},
		{
			name: "successful text content fetch with leading slash in path",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				GetReposGitRefByOwnerByRepoByRef: mockResponse(t, http.StatusOK, "{\"ref\": \"refs/heads/main\", \"object\": {\"sha\": \"\"}}"),
				GetReposByOwnerByRepo:            mockResponse(t, http.StatusOK, "{\"name\": \"repo\", \"default_branch\": \"main\"}"),
				GetReposContentsByOwnerByRepoByPath: func(w http.ResponseWriter, _ *http.Request) {
					w.WriteHeader(http.StatusOK)
					// Base64 encode the content as GitHub API does
					encodedContent := base64.StdEncoding.EncodeToString(mockRawContent)
					fileContent := &github.RepositoryContent{
						Name:     github.Ptr("README.md"),
						Path:     github.Ptr("README.md"),
						SHA:      github.Ptr("abc123"),
						Type:     github.Ptr("file"),
						Content:  github.Ptr(encodedContent),
						Size:     github.Ptr(len(mockRawContent)),
						Encoding: github.Ptr("base64"),
					}
					contentBytes, _ := json.Marshal(fileContent)
					_, _ = w.Write(contentBytes)
				},
			}),
			requestArgs: map[string]any{
				"owner": "owner",
				"repo":  "repo",
				"path":  "/README.md",
				"ref":   "refs/heads/main",
			},
			expectError: false,
			expectedResult: mcp.ResourceContents{
				URI:      "repo://owner/repo/refs/heads/main/contents/README.md",
				Text:     "# Test Repository\n\nThis is a test repository.",
				MIMEType: "text/plain; charset=utf-8",
			},
		},
		{
			name: "successful text content fetch with note when ref falls back to default branch",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				GetReposByOwnerByRepo: mockResponse(t, http.StatusOK, "{\"name\": \"repo\", \"default_branch\": \"develop\"}"),
				GetReposGitRefByOwnerByRepoByRef: func(w http.ResponseWriter, r *http.Request) {
					path := strings.ReplaceAll(r.URL.Path, "%2F", "/")
					switch {
					case strings.Contains(path, "heads/main"):
						w.WriteHeader(http.StatusNotFound)
						_, _ = w.Write([]byte(`{"message": "Not Found"}`))
					case strings.Contains(path, "heads/develop"):
						w.WriteHeader(http.StatusOK)
						_, _ = w.Write([]byte(`{"ref": "refs/heads/develop", "object": {"sha": "abc123def456abc123def456abc123def456abc1", "type": "commit", "url": "https://api.github.com/repos/owner/repo/git/commits/abc123def456abc123def456abc123def456abc1"}}`))
					default:
						w.WriteHeader(http.StatusNotFound)
						_, _ = w.Write([]byte(`{"message": "Not Found"}`))
					}
				},
				"GET /repos/{owner}/{repo}/git/refs/{ref}": func(w http.ResponseWriter, r *http.Request) {
					path := strings.ReplaceAll(r.URL.Path, "%2F", "/")
					switch {
					case strings.Contains(path, "heads/main"):
						w.WriteHeader(http.StatusNotFound)
						_, _ = w.Write([]byte(`{"message": "Not Found"}`))
					case strings.Contains(path, "heads/develop"):
						w.WriteHeader(http.StatusOK)
						_, _ = w.Write([]byte(`{"ref": "refs/heads/develop", "object": {"sha": "abc123def456abc123def456abc123def456abc1", "type": "commit", "url": "https://api.github.com/repos/owner/repo/git/commits/abc123def456abc123def456abc123def456abc1"}}`))
					default:
						w.WriteHeader(http.StatusNotFound)
						_, _ = w.Write([]byte(`{"message": "Not Found"}`))
					}
				},
				"GET /repos/{owner}/{repo}/git/refs/{ref:.*}": func(w http.ResponseWriter, r *http.Request) {
					path := strings.ReplaceAll(r.URL.Path, "%2F", "/")
					switch {
					case strings.Contains(path, "heads/main"):
						w.WriteHeader(http.StatusNotFound)
						_, _ = w.Write([]byte(`{"message": "Not Found"}`))
					case strings.Contains(path, "heads/develop"):
						w.WriteHeader(http.StatusOK)
						_, _ = w.Write([]byte(`{"ref": "refs/heads/develop", "object": {"sha": "abc123def456abc123def456abc123def456abc1", "type": "commit", "url": "https://api.github.com/repos/owner/repo/git/commits/abc123def456abc123def456abc123def456abc1"}}`))
					default:
						w.WriteHeader(http.StatusNotFound)
						_, _ = w.Write([]byte(`{"message": "Not Found"}`))
					}
				},
				"GET /repos/owner/repo/git/ref/heads/main": func(w http.ResponseWriter, _ *http.Request) {
					w.WriteHeader(http.StatusNotFound)
					_, _ = w.Write([]byte(`{"message": "Not Found"}`))
				},
				"GET /repos/owner/repo/git/ref/heads/develop": func(w http.ResponseWriter, _ *http.Request) {
					w.WriteHeader(http.StatusOK)
					_, _ = w.Write([]byte(`{"ref": "refs/heads/develop", "object": {"sha": "abc123def456abc123def456abc123def456abc1", "type": "commit", "url": "https://api.github.com/repos/owner/repo/git/commits/abc123def456abc123def456abc123def456abc1"}}`))
				},
				GetReposContentsByOwnerByRepoByPath: func(w http.ResponseWriter, _ *http.Request) {
					w.WriteHeader(http.StatusOK)
					// Base64 encode the content as GitHub API does
					encodedContent := base64.StdEncoding.EncodeToString(mockRawContent)
					fileContent := &github.RepositoryContent{
						Name:     github.Ptr("README.md"),
						Path:     github.Ptr("README.md"),
						SHA:      github.Ptr("abc123"),
						Type:     github.Ptr("file"),
						Content:  github.Ptr(encodedContent),
						Size:     github.Ptr(len(mockRawContent)),
						Encoding: github.Ptr("base64"),
					}
					contentBytes, _ := json.Marshal(fileContent)
					_, _ = w.Write(contentBytes)
				},
			}),
			requestArgs: map[string]any{
				"owner": "owner",
				"repo":  "repo",
				"path":  "README.md",
				"ref":   "main",
			},
			expectError: false,
			expectedResult: mcp.ResourceContents{
				URI:      "repo://owner/repo/sha/abc123def456abc123def456abc123def456abc1/contents/README.md",
				Text:     "# Test Repository\n\nThis is a test repository.",
				MIMEType: "text/plain; charset=utf-8",
			},
			expectedMsg: " Note: the provided ref 'main' does not exist, default branch 'refs/heads/develop' was used instead.",
		},
		{
			name: "large file returns ResourceLink",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				GetReposGitRefByOwnerByRepoByRef: mockResponse(t, http.StatusOK, "{\"ref\": \"refs/heads/main\", \"object\": {\"sha\": \"\"}}"),
				GetReposByOwnerByRepo:            mockResponse(t, http.StatusOK, "{\"name\": \"repo\", \"default_branch\": \"main\"}"),
				GetReposContentsByOwnerByRepoByPath: func(w http.ResponseWriter, _ *http.Request) {
					w.WriteHeader(http.StatusOK)
					// File larger than 1MB - Contents API returns metadata but no content
					fileContent := &github.RepositoryContent{
						Name:        github.Ptr("large-file.bin"),
						Path:        github.Ptr("large-file.bin"),
						SHA:         github.Ptr("largesha123"),
						Type:        github.Ptr("file"),
						Size:        github.Ptr(2 * 1024 * 1024), // 2MB
						DownloadURL: github.Ptr("https://raw.githubusercontent.com/owner/repo/main/large-file.bin"),
					}
					contentBytes, _ := json.Marshal(fileContent)
					_, _ = w.Write(contentBytes)
				},
			}),
			requestArgs: map[string]any{
				"owner": "owner",
				"repo":  "repo",
				"path":  "large-file.bin",
				"ref":   "refs/heads/main",
			},
			expectError: false,
			expectedResult: &mcp.ResourceLink{
				URI:   "repo://owner/repo/refs/heads/main/contents/large-file.bin",
				Name:  "large-file.bin",
				Title: "File: large-file.bin",
			},
		},
		{
			name: "successful empty file content fetch",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				GetReposGitRefByOwnerByRepoByRef: mockResponse(t, http.StatusOK, "{\"ref\": \"refs/heads/main\", \"object\": {\"sha\": \"\"}}"),
				GetReposByOwnerByRepo:            mockResponse(t, http.StatusOK, "{\"name\": \"repo\", \"default_branch\": \"main\"}"),
				GetReposContentsByOwnerByRepoByPath: func(w http.ResponseWriter, _ *http.Request) {
					w.WriteHeader(http.StatusOK)
					fileContent := &github.RepositoryContent{
						Name:     github.Ptr(".gitkeep"),
						Path:     github.Ptr(".gitkeep"),
						SHA:      github.Ptr("empty123"),
						Type:     github.Ptr("file"),
						Content:  nil,
						Size:     github.Ptr(0),
						Encoding: github.Ptr("base64"),
					}
					contentBytes, _ := json.Marshal(fileContent)
					_, _ = w.Write(contentBytes)
				},
			}),
			requestArgs: map[string]any{
				"owner": "owner",
				"repo":  "repo",
				"path":  ".gitkeep",
				"ref":   "refs/heads/main",
			},
			expectError: false,
			expectedResult: mcp.ResourceContents{
				URI:      "repo://owner/repo/refs/heads/main/contents/.gitkeep",
				Text:     "",
				MIMEType: "text/plain",
			},
			expectedMsg: "successfully downloaded empty file",
		},
		{
			name: "content fetch fails",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				GetReposGitRefByOwnerByRepoByRef: mockResponse(t, http.StatusOK, "{\"ref\": \"refs/heads/main\", \"object\": {\"sha\": \"\"}}"),
				GetReposContentsByOwnerByRepoByPath: func(w http.ResponseWriter, _ *http.Request) {
					w.WriteHeader(http.StatusNotFound)
					_, _ = w.Write([]byte(`{"message": "Not Found"}`))
				},
				GetRawReposContentsByOwnerByRepoByPath: func(w http.ResponseWriter, _ *http.Request) {
					w.WriteHeader(http.StatusNotFound)
					_, _ = w.Write([]byte(`{"message": "Not Found"}`))
				},
			}),
			requestArgs: map[string]any{
				"owner": "owner",
				"repo":  "repo",
				"path":  "nonexistent.md",
				"ref":   "refs/heads/main",
			},
			expectError:    false,
			expectedResult: utils.NewToolResultError("Failed to get file contents. The path does not point to a file or directory, or the file does not exist in the repository."),
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			// Setup client with mock
			client := mustNewGHClient(t, tc.mockedClient)
			mockRawClient, err := raw.NewClient(client, &url.URL{Scheme: "https", Host: "raw.example.com", Path: "/"})
			require.NoError(t, err)
			deps := BaseDeps{
				Client:    client,
				RawClient: mockRawClient,
			}
			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)
				textContent := getErrorResult(t, result)
				assert.Contains(t, textContent.Text, tc.expectedErrMsg)
				return
			}

			require.NoError(t, err)
			// Use the correct result helper based on the expected type
			switch expected := tc.expectedResult.(type) {
			case mcp.ResourceContents:
				// Handle both text and blob resources
				resource := getResourceResult(t, result)
				assert.Equal(t, expected, *resource)

				// If expectedMsg is set, verify the message text
				if tc.expectedMsg != "" {
					require.Len(t, result.Content, 2)
					textContent, ok := result.Content[0].(*mcp.TextContent)
					require.True(t, ok, "expected Content[0] to be TextContent")
					assert.Contains(t, textContent.Text, tc.expectedMsg)
				}
			case []*github.RepositoryContent:
				// Directory content fetch returns a text result (JSON array)
				textContent := getTextResult(t, result)
				var returnedContents []*github.RepositoryContent
				err = json.Unmarshal([]byte(textContent.Text), &returnedContents)
				require.NoError(t, err, "Failed to unmarshal directory content result: %v", textContent.Text)
				assert.Len(t, returnedContents, len(expected))
				for i, content := range returnedContents {
					assert.Equal(t, *expected[i].Name, *content.Name)
					assert.Equal(t, *expected[i].Path, *content.Path)
					assert.Equal(t, *expected[i].Type, *content.Type)
				}
			case *mcp.ResourceLink:
				// Large file returns a ResourceLink
				require.Len(t, result.Content, 2)
				resourceLink, ok := result.Content[1].(*mcp.ResourceLink)
				require.True(t, ok, "expected Content[1] to be ResourceLink")
				assert.Equal(t, expected.URI, resourceLink.URI)
				assert.Equal(t, expected.Name, resourceLink.Name)
				assert.Equal(t, expected.Title, resourceLink.Title)
			case mcp.TextContent:
				textContent := getErrorResult(t, result)
				require.Equal(t, textContent, expected)
			}
		})
	}
}

func Test_GetFileContents_DirectoryFieldFiltering(t *testing.T) {
	mockDirContent := []*github.RepositoryContent{
		{
			Type:        github.Ptr("file"),
			Name:        github.Ptr("README.md"),
			Path:        github.Ptr("README.md"),
			SHA:         github.Ptr("abc123"),
			Size:        github.Ptr(42),
			URL:         github.Ptr("https://api.github.com/repos/owner/repo/contents/README.md"),
			HTMLURL:     github.Ptr("https://github.com/owner/repo/blob/main/README.md"),
			DownloadURL: github.Ptr("https://raw.githubusercontent.com/owner/repo/main/README.md"),
		},
		{
			Type:    github.Ptr("dir"),
			Name:    github.Ptr("src"),
			Path:    github.Ptr("src"),
			SHA:     github.Ptr("def456"),
			HTMLURL: github.Ptr("https://github.com/owner/repo/tree/main/src"),
		},
	}

	serverTool := GetFileContents(translations.NullTranslationHelper)
	client := mustNewGHClient(t, MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
		GetReposByOwnerByRepo:            mockResponse(t, http.StatusOK, "{\"name\": \"repo\", \"default_branch\": \"main\"}"),
		GetReposGitRefByOwnerByRepoByRef: mockResponse(t, http.StatusOK, "{\"ref\": \"refs/heads/main\", \"object\": {\"sha\": \"\"}}"),
		GetReposContentsByOwnerByRepoByPath: expectQueryParams(t, map[string]string{}).andThen(
			mockResponse(t, http.StatusOK, mockDirContent),
		),
		GetRawReposContentsByOwnerByRepoByPath: expectQueryParams(t, map[string]string{"branch": "main"}).andThen(
			mockResponse(t, http.StatusNotFound, nil),
		),
	}))
	deps := BaseDeps{Client: client}
	handler := serverTool.Handler(deps)

	request := createMCPRequest(map[string]any{
		"owner":  "owner",
		"repo":   "repo",
		"path":   "src/",
		"fields": []any{"name", "type"},
	})

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

	textContent := getTextResult(t, result)

	// Each directory entry is reduced to the requested fields only; heavier
	// fields such as html_url and download_url are dropped.
	var returned []map[string]any
	require.NoError(t, json.Unmarshal([]byte(textContent.Text), &returned))
	require.Len(t, returned, len(mockDirContent))
	for _, entry := range returned {
		require.Len(t, entry, 2)
		assert.Contains(t, entry, "name")
		assert.Contains(t, entry, "type")
	}

	assert.NotContains(t, textContent.Text, "html_url")
	assert.NotContains(t, textContent.Text, "download_url")
}

func Test_LegacyGetFileContents_Definition(t *testing.T) {
	serverTool := LegacyGetFileContents(translations.NullTranslationHelper)
	tool := serverTool.Tool
	// LegacyGetFileContents is the FeatureFlagFieldsParam-disabled variant and
	// owns the canonical get_file_contents.snap (no `fields`).
	require.NoError(t, toolsnaps.Test(tool.Name, tool))
	require.Equal(t, []string{FeatureFlagFieldsParam}, serverTool.FeatureFlagDisable)

	assert.Equal(t, "get_file_contents", tool.Name)
	schema, ok := tool.InputSchema.(*jsonschema.Schema)
	require.True(t, ok, "InputSchema should be *jsonschema.Schema")
	assert.NotContains(t, schema.Properties, "fields")
}

func Test_GetFileContents_DirectoryFieldsTelemetry(t *testing.T) {
	mockDirContent := []*github.RepositoryContent{
		{
			Type:        github.Ptr("file"),
			Name:        github.Ptr("README.md"),
			Path:        github.Ptr("README.md"),
			SHA:         github.Ptr("abc123"),
			Size:        github.Ptr(42),
			URL:         github.Ptr("https://api.github.com/repos/owner/repo/contents/README.md"),
			HTMLURL:     github.Ptr("https://github.com/owner/repo/blob/main/README.md"),
			DownloadURL: github.Ptr("https://raw.githubusercontent.com/owner/repo/main/README.md"),
		},
	}

	serverTool := GetFileContents(translations.NullTranslationHelper)
	client := mustNewGHClient(t, MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
		GetReposByOwnerByRepo:            mockResponse(t, http.StatusOK, "{\"name\": \"repo\", \"default_branch\": \"main\"}"),
		GetReposGitRefByOwnerByRepoByRef: mockResponse(t, http.StatusOK, "{\"ref\": \"refs/heads/main\", \"object\": {\"sha\": \"\"}}"),
		GetReposContentsByOwnerByRepoByPath: expectQueryParams(t, map[string]string{}).andThen(
			mockResponse(t, http.StatusOK, mockDirContent),
		),
		GetRawReposContentsByOwnerByRepoByPath: expectQueryParams(t, map[string]string{"branch": "main"}).andThen(
			mockResponse(t, http.StatusNotFound, nil),
		),
	}))
	deps, rec := depsWithRecordingMetrics(t, BaseDeps{Client: client})
	handler := serverTool.Handler(deps)

	request := createMCPRequest(map[string]any{
		"owner":  "owner",
		"repo":   "repo",
		"path":   "src/",
		"fields": []any{"name", "type"},
	})

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

	call, ok := rec.increment(metricFieldsToolCall)
	require.True(t, ok)
	assert.Equal(t, "get_file_contents", call.tags["tool"])
	assert.Equal(t, "true", call.tags["filtered"])

	full, ok := rec.counter(metricFieldsBytesFull)
	require.True(t, ok)
	sent, ok := rec.counter(metricFieldsBytesSent)
	require.True(t, ok)
	assert.Greater(t, full.value, sent.value, "filtering should remove bytes")
}

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

	serverTool := GetFileContents(translations.NullTranslationHelper)

	mockRawContent := []byte("hello")

	makeMockClient := func(isPrivate bool) *http.Client {
		return MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
			GetReposGitRefByOwnerByRepoByRef: mockResponse(t, http.StatusOK, "{\"ref\": \"refs/heads/main\", \"object\": {\"sha\": \"\"}}"),
			GetReposByOwnerByRepo: mockResponse(t, http.StatusOK, map[string]any{
				"name":           "repo",
				"default_branch": "main",
				"private":        isPrivate,
			}),
			GetReposContentsByOwnerByRepoByPath: func(w http.ResponseWriter, _ *http.Request) {
				w.WriteHeader(http.StatusOK)
				encodedContent := base64.StdEncoding.EncodeToString(mockRawContent)
				fileContent := &github.RepositoryContent{
					Name:     github.Ptr("README.md"),
					Path:     github.Ptr("README.md"),
					SHA:      github.Ptr("abc123"),
					Type:     github.Ptr("file"),
					Content:  github.Ptr(encodedContent),
					Size:     github.Ptr(len(mockRawContent)),
					Encoding: github.Ptr("base64"),
				}
				contentBytes, _ := json.Marshal(fileContent)
				_, _ = w.Write(contentBytes)
			},
		})
	}

	reqParams := map[string]any{
		"owner": "octocat",
		"repo":  "repo",
		"path":  "README.md",
		"ref":   "refs/heads/main",
	}

	t.Run("insiders mode disabled omits ifc label from result meta", func(t *testing.T) {
		deps := BaseDeps{
			Client: mustNewGHClient(t, makeMockClient(false)),
		}
		handler := serverTool.Handler(deps)

		request := createMCPRequest(reqParams)
		result, err := handler(ContextWithDeps(context.Background(), deps), &request)
		require.NoError(t, err)
		require.False(t, result.IsError)

		assert.Nil(t, result.Meta, "result meta should be nil when insiders mode is disabled")
	})

	t.Run("insiders mode enabled on public repo emits public untrusted label", func(t *testing.T) {
		deps := BaseDeps{
			Client:         mustNewGHClient(t, makeMockClient(false)),
			featureChecker: featureCheckerFor(FeatureFlagIFCLabels),
		}
		handler := serverTool.Handler(deps)

		request := createMCPRequest(reqParams)
		result, err := handler(ContextWithDeps(context.Background(), deps), &request)
		require.NoError(t, err)
		require.False(t, result.IsError)

		require.NotNil(t, result.Meta)
		ifcLabel, ok := result.Meta["ifc"]
		require.True(t, ok, "result meta should contain ifc key")

		ifcJSON, err := json.Marshal(ifcLabel)
		require.NoError(t, err)
		var ifcMap map[string]any
		require.NoError(t, json.Unmarshal(ifcJSON, &ifcMap))

		assert.Equal(t, "untrusted", ifcMap["integrity"])
		assert.Equal(t, "public", ifcMap["confidentiality"])
	})

	t.Run("insiders mode enabled on private repo emits private trusted label", func(t *testing.T) {
		deps := BaseDeps{
			Client:         mustNewGHClient(t, makeMockClient(true)),
			featureChecker: featureCheckerFor(FeatureFlagIFCLabels),
		}
		handler := serverTool.Handler(deps)

		request := createMCPRequest(reqParams)
		result, err := handler(ContextWithDeps(context.Background(), deps), &request)
		require.NoError(t, err)
		require.False(t, result.IsError)

		require.NotNil(t, result.Meta)
		ifcLabel, ok := result.Meta["ifc"]
		require.True(t, ok, "result meta should contain ifc key")

		ifcJSON, err := json.Marshal(ifcLabel)
		require.NoError(t, err)
		var ifcMap map[string]any
		require.NoError(t, json.Unmarshal(ifcJSON, &ifcMap))

		assert.Equal(t, "trusted", ifcMap["integrity"])
		assert.Equal(t, "private", ifcMap["confidentiality"])
	})

	t.Run("insiders mode skips ifc label when visibility lookup fails", func(t *testing.T) {
		mockedClient := MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
			GetReposGitRefByOwnerByRepoByRef: mockResponse(t, http.StatusOK, "{\"ref\": \"refs/heads/main\", \"object\": {\"sha\": \"\"}}"),
			GetReposByOwnerByRepo:            mockResponse(t, http.StatusInternalServerError, "boom"),
			GetReposContentsByOwnerByRepoByPath: func(w http.ResponseWriter, _ *http.Request) {
				w.WriteHeader(http.StatusOK)
				encodedContent := base64.StdEncoding.EncodeToString(mockRawContent)
				fileContent := &github.RepositoryContent{
					Name:     github.Ptr("README.md"),
					Path:     github.Ptr("README.md"),
					SHA:      github.Ptr("abc123"),
					Type:     github.Ptr("file"),
					Content:  github.Ptr(encodedContent),
					Size:     github.Ptr(len(mockRawContent)),
					Encoding: github.Ptr("base64"),
				}
				contentBytes, _ := json.Marshal(fileContent)
				_, _ = w.Write(contentBytes)
			},
		})
		deps := BaseDeps{
			Client:         mustNewGHClient(t, mockedClient),
			featureChecker: featureCheckerFor(FeatureFlagIFCLabels),
		}
		handler := serverTool.Handler(deps)

		request := createMCPRequest(reqParams)
		result, err := handler(ContextWithDeps(context.Background(), deps), &request)
		require.NoError(t, err)
		require.False(t, result.IsError, "tool call should still succeed when visibility lookup fails")

		if result.Meta != nil {
			_, hasIFC := result.Meta["ifc"]
			assert.False(t, hasIFC, "ifc label should be omitted when visibility lookup fails")
		}
	})
}

// Test_GetCommit_IFC_FeatureFlag verifies that the IFC security label is only
// attached to get_commit results when the ifc_labels feature flag is enabled,
// and that the label content matches the commit-contents rule (untrusted on
// public repos, trusted on private). It also confirms the label is omitted
// when the repository visibility lookup fails, so the result is never
// misclassified. get_commit is representative of every tool wired through the
// shared attachRepoVisibilityIFCLabel helper.
func Test_GetCommit_IFC_FeatureFlag(t *testing.T) {
	t.Parallel()

	serverTool := GetCommit(translations.NullTranslationHelper)

	mockCommit := &github.RepositoryCommit{
		SHA:     github.Ptr("abc123def456"),
		Commit:  &github.Commit{Message: github.Ptr("First commit")},
		HTMLURL: github.Ptr("https://github.com/owner/repo/commit/abc123def456"),
	}

	makeMockClient := func(isPrivate bool) *http.Client {
		return MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
			GetReposCommitsByOwnerByRepoByRef: mockResponse(t, http.StatusOK, mockCommit),
			GetReposByOwnerByRepo: mockResponse(t, http.StatusOK, map[string]any{
				"name":    "repo",
				"private": isPrivate,
			}),
		})
	}

	reqParams := map[string]any{
		"owner": "owner",
		"repo":  "repo",
		"sha":   "abc123def456",
	}

	t.Run("feature flag disabled omits ifc label from result meta", func(t *testing.T) {
		deps := BaseDeps{
			Client: mustNewGHClient(t, makeMockClient(false)),
		}
		handler := serverTool.Handler(deps)

		request := createMCPRequest(reqParams)
		result, err := handler(ContextWithDeps(context.Background(), deps), &request)
		require.NoError(t, err)
		require.False(t, result.IsError)

		assert.Nil(t, result.Meta, "result meta should be nil when IFC labels are disabled")
	})

	t.Run("feature flag enabled on public repo emits public untrusted label", func(t *testing.T) {
		deps := BaseDeps{
			Client:         mustNewGHClient(t, makeMockClient(false)),
			featureChecker: featureCheckerFor(FeatureFlagIFCLabels),
		}
		handler := serverTool.Handler(deps)

		request := createMCPRequest(reqParams)
		result, err := handler(ContextWithDeps(context.Background(), deps), &request)
		require.NoError(t, err)
		require.False(t, result.IsError)

		require.NotNil(t, result.Meta)
		ifcLabel, ok := result.Meta["ifc"]
		require.True(t, ok, "result meta should contain ifc key")

		ifcJSON, err := json.Marshal(ifcLabel)
		require.NoError(t, err)
		var ifcMap map[string]any
		require.NoError(t, json.Unmarshal(ifcJSON, &ifcMap))

		assert.Equal(t, "untrusted", ifcMap["integrity"])
		assert.Equal(t, "public", ifcMap["confidentiality"])
	})

	t.Run("feature flag enabled on private repo emits private trusted label", func(t *testing.T) {
		deps := BaseDeps{
			Client:         mustNewGHClient(t, makeMockClient(true)),
			featureChecker: featureCheckerFor(FeatureFlagIFCLabels),
		}
		handler := serverTool.Handler(deps)

		request := createMCPRequest(reqParams)
		result, err := handler(ContextWithDeps(context.Background(), deps), &request)
		require.NoError(t, err)
		require.False(t, result.IsError)

		require.NotNil(t, result.Meta)
		ifcLabel, ok := result.Meta["ifc"]
		require.True(t, ok, "result meta should contain ifc key")

		ifcJSON, err := json.Marshal(ifcLabel)
		require.NoError(t, err)
		var ifcMap map[string]any
		require.NoError(t, json.Unmarshal(ifcJSON, &ifcMap))

		assert.Equal(t, "trusted", ifcMap["integrity"])
		assert.Equal(t, "private", ifcMap["confidentiality"])
	})

	t.Run("feature flag enabled skips ifc label when visibility lookup fails", func(t *testing.T) {
		mockedClient := MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
			GetReposCommitsByOwnerByRepoByRef: mockResponse(t, http.StatusOK, mockCommit),
			GetReposByOwnerByRepo:             mockResponse(t, http.StatusInternalServerError, "boom"),
		})
		deps := BaseDeps{
			Client:         mustNewGHClient(t, mockedClient),
			featureChecker: featureCheckerFor(FeatureFlagIFCLabels),
		}
		handler := serverTool.Handler(deps)

		request := createMCPRequest(reqParams)
		result, err := handler(ContextWithDeps(context.Background(), deps), &request)
		require.NoError(t, err)
		require.False(t, result.IsError, "tool call should still succeed when visibility lookup fails")

		if result.Meta != nil {
			_, hasIFC := result.Meta["ifc"]
			assert.False(t, hasIFC, "ifc label should be omitted when visibility lookup fails")
		}
	})
}

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

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

	assert.Equal(t, "fork_repository", tool.Name)
	assert.NotEmpty(t, tool.Description)
	assert.Contains(t, schema.Properties, "owner")
	assert.Contains(t, schema.Properties, "repo")
	assert.Contains(t, schema.Properties, "organization")
	assert.ElementsMatch(t, schema.Required, []string{"owner", "repo"})

	// Setup mock forked repo for success case
	mockForkedRepo := &github.Repository{
		ID:       github.Ptr(int64(123456)),
		Name:     github.Ptr("repo"),
		FullName: github.Ptr("new-owner/repo"),
		Owner: &github.User{
			Login: github.Ptr("new-owner"),
		},
		HTMLURL:       github.Ptr("https://github.com/new-owner/repo"),
		DefaultBranch: github.Ptr("main"),
		Fork:          github.Ptr(true),
		ForksCount:    github.Ptr(0),
	}

	tests := []struct {
		name           string
		mockedClient   *http.Client
		requestArgs    map[string]any
		expectError    bool
		expectedRepo   *github.Repository
		expectedErrMsg string
	}{
		{
			name: "successful repository fork",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				PostReposForksByOwnerByRepo: mockResponse(t, http.StatusAccepted, mockForkedRepo),
			}),
			requestArgs: map[string]any{
				"owner": "owner",
				"repo":  "repo",
			},
			expectError:  false,
			expectedRepo: mockForkedRepo,
		},
		{
			name: "repository fork fails",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				PostReposForksByOwnerByRepo: func(w http.ResponseWriter, _ *http.Request) {
					w.WriteHeader(http.StatusForbidden)
					_, _ = w.Write([]byte(`{"message": "Forbidden"}`))
				},
			}),
			requestArgs: map[string]any{
				"owner": "owner",
				"repo":  "repo",
			},
			expectError:    true,
			expectedErrMsg: "failed to fork repository",
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			// Setup client with mock
			client := mustNewGHClient(t, 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)
				require.True(t, result.IsError)
				errorContent := getErrorResult(t, result)
				assert.Contains(t, errorContent.Text, tc.expectedErrMsg)
				return
			}

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

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

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

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

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

	assert.Equal(t, "create_branch", tool.Name)
	assert.NotEmpty(t, tool.Description)
	assert.Contains(t, schema.Properties, "owner")
	assert.Contains(t, schema.Properties, "repo")
	assert.Contains(t, schema.Properties, "branch")
	assert.Contains(t, schema.Properties, "from_branch")
	assert.ElementsMatch(t, schema.Required, []string{"owner", "repo", "branch"})

	// Setup mock repository for default branch test
	mockRepo := &github.Repository{
		DefaultBranch: github.Ptr("main"),
	}

	// Setup mock reference for from_branch tests
	mockSourceRef := &github.Reference{
		Ref: github.Ptr("refs/heads/main"),
		Object: &github.GitObject{
			SHA: github.Ptr("abc123def456"),
		},
	}

	// Setup mock created reference
	mockCreatedRef := &github.Reference{
		Ref: github.Ptr("refs/heads/new-feature"),
		Object: &github.GitObject{
			SHA: github.Ptr("abc123def456"),
		},
	}

	tests := []struct {
		name           string
		mockedClient   *http.Client
		requestArgs    map[string]any
		expectError    bool
		expectedRef    *github.Reference
		expectedErrMsg string
	}{
		{
			name: "successful branch creation with from_branch",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				GetReposGitRefByOwnerByRepoByRef:           mockResponse(t, http.StatusOK, mockSourceRef),
				"GET /repos/owner/repo/git/ref/heads/main": mockResponse(t, http.StatusOK, mockSourceRef),
				PostReposGitRefsByOwnerByRepo:              mockResponse(t, http.StatusCreated, mockCreatedRef),
			}),
			requestArgs: map[string]any{
				"owner":       "owner",
				"repo":        "repo",
				"branch":      "new-feature",
				"from_branch": "main",
			},
			expectError: false,
			expectedRef: mockCreatedRef,
		},
		{
			name: "successful branch creation with default branch",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				GetReposByOwnerByRepo:                      mockResponse(t, http.StatusOK, mockRepo),
				GetReposGitRefByOwnerByRepoByRef:           mockResponse(t, http.StatusOK, mockSourceRef),
				"GET /repos/owner/repo/git/ref/heads/main": mockResponse(t, http.StatusOK, mockSourceRef),
				PostReposGitRefsByOwnerByRepo: expectRequestBody(t, map[string]any{
					"ref": "refs/heads/new-feature",
					"sha": "abc123def456",
				}).andThen(
					mockResponse(t, http.StatusCreated, mockCreatedRef),
				),
			}),
			requestArgs: map[string]any{
				"owner":  "owner",
				"repo":   "repo",
				"branch": "new-feature",
			},
			expectError: false,
			expectedRef: mockCreatedRef,
		},
		{
			name: "fail to get repository",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				GetReposByOwnerByRepo: func(w http.ResponseWriter, _ *http.Request) {
					w.WriteHeader(http.StatusNotFound)
					_, _ = w.Write([]byte(`{"message": "Repository not found"}`))
				},
			}),
			requestArgs: map[string]any{
				"owner":  "owner",
				"repo":   "nonexistent-repo",
				"branch": "new-feature",
			},
			expectError:    true,
			expectedErrMsg: "failed to get repository",
		},
		{
			name: "fail to get reference",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				GetReposGitRefByOwnerByRepoByRef: func(w http.ResponseWriter, _ *http.Request) {
					w.WriteHeader(http.StatusNotFound)
					_, _ = w.Write([]byte(`{"message": "Reference not found"}`))
				},
			}),
			requestArgs: map[string]any{
				"owner":       "owner",
				"repo":        "repo",
				"branch":      "new-feature",
				"from_branch": "nonexistent-branch",
			},
			expectError:    true,
			expectedErrMsg: "failed to get reference",
		},
		{
			name: "fail to create branch",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				GetReposGitRefByOwnerByRepoByRef:           mockResponse(t, http.StatusOK, mockSourceRef),
				"GET /repos/owner/repo/git/ref/heads/main": mockResponse(t, http.StatusOK, mockSourceRef),
				PostReposGitRefsByOwnerByRepo: func(w http.ResponseWriter, _ *http.Request) {
					w.WriteHeader(http.StatusUnprocessableEntity)
					_, _ = w.Write([]byte(`{"message": "Reference already exists"}`))
				},
			}),
			requestArgs: map[string]any{
				"owner":       "owner",
				"repo":        "repo",
				"branch":      "existing-branch",
				"from_branch": "main",
			},
			expectError:    true,
			expectedErrMsg: "failed to create branch",
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			// Setup client with mock
			client := mustNewGHClient(t, 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)
				require.True(t, result.IsError)
				errorContent := getErrorResult(t, result)
				assert.Contains(t, errorContent.Text, tc.expectedErrMsg)
				return
			}

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

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

			// Unmarshal and verify the result
			var returnedRef github.Reference
			err = json.Unmarshal([]byte(textContent.Text), &returnedRef)
			require.NoError(t, err)
			assert.Equal(t, *tc.expectedRef.Ref, *returnedRef.Ref)
			assert.Equal(t, *tc.expectedRef.Object.SHA, *returnedRef.Object.SHA)
		})
	}
}

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

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

	assert.Equal(t, "get_commit", tool.Name)
	assert.NotEmpty(t, tool.Description)
	assert.Contains(t, schema.Properties, "owner")
	assert.Contains(t, schema.Properties, "repo")
	assert.Contains(t, schema.Properties, "sha")
	assert.ElementsMatch(t, schema.Required, []string{"owner", "repo", "sha"})

	mockCommit := &github.RepositoryCommit{
		SHA: github.Ptr("abc123def456"),
		Commit: &github.Commit{
			Message: github.Ptr("First commit"),
			Author: &github.CommitAuthor{
				Name:  github.Ptr("Test User"),
				Email: github.Ptr("test@example.com"),
				Date:  &github.Timestamp{Time: time.Now().Add(-48 * time.Hour)},
			},
		},
		Author: &github.User{
			Login: github.Ptr("testuser"),
		},
		HTMLURL: github.Ptr("https://github.com/owner/repo/commit/abc123def456"),
		Stats: &github.CommitStats{
			Additions: github.Ptr(10),
			Deletions: github.Ptr(2),
			Total:     github.Ptr(12),
		},
		Files: []*github.CommitFile{
			{
				Filename:  github.Ptr("file1.go"),
				Status:    github.Ptr("modified"),
				Additions: github.Ptr(10),
				Deletions: github.Ptr(2),
				Changes:   github.Ptr(12),
				Patch:     github.Ptr("@@ -1,2 +1,10 @@"),
			},
		},
	}

	tests := []struct {
		name           string
		mockedClient   *http.Client
		requestArgs    map[string]any
		expectError    bool
		expectedCommit *github.RepositoryCommit
		expectedErrMsg string
	}{
		{
			name: "successful commit fetch",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				GetReposCommitsByOwnerByRepoByRef: mockResponse(t, http.StatusOK, mockCommit),
			}),
			requestArgs: map[string]any{
				"owner": "owner",
				"repo":  "repo",
				"sha":   "abc123def456",
			},
			expectError:    false,
			expectedCommit: mockCommit,
		},
		{
			name: "commit fetch fails",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				GetReposCommitsByOwnerByRepoByRef: func(w http.ResponseWriter, _ *http.Request) {
					w.WriteHeader(http.StatusNotFound)
					_, _ = w.Write([]byte(`{"message": "Not Found"}`))
				},
			}),
			requestArgs: map[string]any{
				"owner": "owner",
				"repo":  "repo",
				"sha":   "nonexistent-sha",
			},
			expectError:    true,
			expectedErrMsg: "failed to get commit",
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			// Setup client with mock
			client := mustNewGHClient(t, 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)
				require.True(t, result.IsError)
				errorContent := getErrorResult(t, result)
				assert.Contains(t, errorContent.Text, tc.expectedErrMsg)
				return
			}

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

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

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

			assert.Equal(t, *tc.expectedCommit.SHA, *returnedCommit.SHA)
			assert.Equal(t, *tc.expectedCommit.Commit.Message, *returnedCommit.Commit.Message)
			assert.Equal(t, *tc.expectedCommit.Author.Login, *returnedCommit.Author.Login)
			assert.Equal(t, *tc.expectedCommit.HTMLURL, *returnedCommit.HTMLURL)
		})
	}
}

func Test_GetCommit_Detail(t *testing.T) {
	mockCommit := &github.RepositoryCommit{
		SHA:     github.Ptr("abc123def456"),
		HTMLURL: github.Ptr("https://github.com/owner/repo/commit/abc123def456"),
		Commit: &github.Commit{
			Message: github.Ptr("First commit"),
		},
		Stats: &github.CommitStats{
			Additions: github.Ptr(10),
			Deletions: github.Ptr(2),
			Total:     github.Ptr(12),
		},
		Files: []*github.CommitFile{
			{
				Filename:  github.Ptr("file1.go"),
				Status:    github.Ptr("modified"),
				Additions: github.Ptr(10),
				Deletions: github.Ptr(2),
				Changes:   github.Ptr(12),
				Patch:     github.Ptr("@@ -1,2 +1,10 @@\n+new line"),
			},
		},
	}

	cases := []struct {
		name        string
		args        map[string]any
		expectFiles bool
		expectStats bool
		expectPatch bool
		expectError string
	}{
		{
			name:        "default returns stats",
			args:        map[string]any{"owner": "owner", "repo": "repo", "sha": "abc123def456"},
			expectFiles: true,
			expectStats: true,
			expectPatch: false,
		},
		{
			name:        "detail=none omits stats and files",
			args:        map[string]any{"owner": "owner", "repo": "repo", "sha": "abc123def456", "detail": "none"},
			expectFiles: false,
			expectStats: false,
			expectPatch: false,
		},
		{
			name:        "detail=stats returns metadata without patch",
			args:        map[string]any{"owner": "owner", "repo": "repo", "sha": "abc123def456", "detail": "stats"},
			expectFiles: true,
			expectStats: true,
			expectPatch: false,
		},
		{
			name:        "detail=full_patch includes patch text",
			args:        map[string]any{"owner": "owner", "repo": "repo", "sha": "abc123def456", "detail": "full_patch"},
			expectFiles: true,
			expectStats: true,
			expectPatch: true,
		},
		{
			name:        "invalid detail value is rejected",
			args:        map[string]any{"owner": "owner", "repo": "repo", "sha": "abc123def456", "detail": "everything"},
			expectError: `invalid detail "everything"`,
		},
	}

	serverTool := GetCommit(translations.NullTranslationHelper)

	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			mockedClient := MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				GetReposCommitsByOwnerByRepoByRef: mockResponse(t, http.StatusOK, mockCommit),
			})
			client := mustNewGHClient(t, mockedClient)
			deps := BaseDeps{Client: client}
			handler := serverTool.Handler(deps)

			request := createMCPRequest(tc.args)
			result, err := handler(ContextWithDeps(context.Background(), deps), &request)
			require.NoError(t, err)

			if tc.expectError != "" {
				require.True(t, result.IsError)
				assert.Contains(t, getErrorResult(t, result).Text, tc.expectError)
				return
			}
			require.False(t, result.IsError)

			var returned MinimalCommit
			require.NoError(t, json.Unmarshal([]byte(getTextResult(t, result).Text), &returned))

			if tc.expectStats {
				require.NotNil(t, returned.Stats)
				assert.Equal(t, 12, returned.Stats.Total)
			} else {
				assert.Nil(t, returned.Stats)
			}

			if tc.expectFiles {
				require.Len(t, returned.Files, 1)
				assert.Equal(t, "file1.go", returned.Files[0].Filename)
				if tc.expectPatch {
					assert.Equal(t, "@@ -1,2 +1,10 @@\n+new line", returned.Files[0].Patch)
				} else {
					assert.Empty(t, returned.Files[0].Patch)
				}
			} else {
				assert.Empty(t, returned.Files)
			}
		})
	}
}

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

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

	assert.Equal(t, "list_commits", tool.Name)
	assert.NotEmpty(t, tool.Description)
	assert.Contains(t, schema.Properties, "owner")
	assert.Contains(t, schema.Properties, "repo")
	assert.Contains(t, schema.Properties, "sha")
	assert.Contains(t, schema.Properties, "author")
	assert.Contains(t, schema.Properties, "path")
	assert.Contains(t, schema.Properties, "since")
	assert.Contains(t, schema.Properties, "until")
	assert.Contains(t, schema.Properties, "page")
	assert.Contains(t, schema.Properties, "perPage")
	assert.Contains(t, schema.Properties, "fields")
	assert.ElementsMatch(t, schema.Required, []string{"owner", "repo"})

	// Setup mock commits for success case
	mockCommits := []*github.RepositoryCommit{
		{
			SHA: github.Ptr("abc123def456"),
			Commit: &github.Commit{
				Message: github.Ptr("First commit"),
				Author: &github.CommitAuthor{
					Name:  github.Ptr("Test User"),
					Email: github.Ptr("test@example.com"),
					Date:  &github.Timestamp{Time: time.Now().Add(-48 * time.Hour)},
				},
			},
			Author: &github.User{
				Login:     github.Ptr("testuser"),
				ID:        github.Ptr(int64(12345)),
				HTMLURL:   github.Ptr("https://github.com/testuser"),
				AvatarURL: github.Ptr("https://github.com/testuser.png"),
			},
			HTMLURL: github.Ptr("https://github.com/owner/repo/commit/abc123def456"),
			Stats: &github.CommitStats{
				Additions: github.Ptr(10),
				Deletions: github.Ptr(5),
				Total:     github.Ptr(15),
			},
			Files: []*github.CommitFile{
				{
					Filename:  github.Ptr("src/main.go"),
					Status:    github.Ptr("modified"),
					Additions: github.Ptr(8),
					Deletions: github.Ptr(3),
					Changes:   github.Ptr(11),
				},
				{
					Filename:  github.Ptr("README.md"),
					Status:    github.Ptr("added"),
					Additions: github.Ptr(2),
					Deletions: github.Ptr(2),
					Changes:   github.Ptr(4),
				},
			},
		},
		{
			SHA: github.Ptr("def456abc789"),
			Commit: &github.Commit{
				Message: github.Ptr("Second commit"),
				Author: &github.CommitAuthor{
					Name:  github.Ptr("Another User"),
					Email: github.Ptr("another@example.com"),
					Date:  &github.Timestamp{Time: time.Now().Add(-24 * time.Hour)},
				},
			},
			Author: &github.User{
				Login:     github.Ptr("anotheruser"),
				ID:        github.Ptr(int64(67890)),
				HTMLURL:   github.Ptr("https://github.com/anotheruser"),
				AvatarURL: github.Ptr("https://github.com/anotheruser.png"),
			},
			HTMLURL: github.Ptr("https://github.com/owner/repo/commit/def456abc789"),
			Stats: &github.CommitStats{
				Additions: github.Ptr(20),
				Deletions: github.Ptr(10),
				Total:     github.Ptr(30),
			},
			Files: []*github.CommitFile{
				{
					Filename:  github.Ptr("src/utils.go"),
					Status:    github.Ptr("added"),
					Additions: github.Ptr(20),
					Deletions: github.Ptr(10),
					Changes:   github.Ptr(30),
				},
			},
		},
	}

	tests := []struct {
		name            string
		mockedClient    *http.Client
		requestArgs     map[string]any
		expectError     bool
		expectedCommits []*github.RepositoryCommit
		expectedErrMsg  string
	}{
		{
			name: "successful commits fetch with default params",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				GetReposCommitsByOwnerByRepo: mockResponse(t, http.StatusOK, mockCommits),
			}),
			requestArgs: map[string]any{
				"owner": "owner",
				"repo":  "repo",
			},
			expectError:     false,
			expectedCommits: mockCommits,
		},
		{
			name: "successful commits fetch with branch",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				GetReposCommitsByOwnerByRepo: expectQueryParams(t, map[string]string{
					"author":   "username",
					"sha":      "main",
					"page":     "1",
					"per_page": "30",
				}).andThen(
					mockResponse(t, http.StatusOK, mockCommits),
				),
			}),
			requestArgs: map[string]any{
				"owner":  "owner",
				"repo":   "repo",
				"sha":    "main",
				"author": "username",
			},
			expectError:     false,
			expectedCommits: mockCommits,
		},
		{
			name: "successful commits fetch with path filter",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				GetReposCommitsByOwnerByRepo: expectQueryParams(t, map[string]string{
					"path":     "src/main.go",
					"page":     "1",
					"per_page": "30",
				}).andThen(
					mockResponse(t, http.StatusOK, mockCommits),
				),
			}),
			requestArgs: map[string]any{
				"owner": "owner",
				"repo":  "repo",
				"path":  "src/main.go",
			},
			expectError:     false,
			expectedCommits: mockCommits,
		},
		{
			name: "successful commits fetch with since and until",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				GetReposCommitsByOwnerByRepo: expectQueryParams(t, map[string]string{
					"since":    "2023-01-01T00:00:00Z",
					"until":    "2023-12-31T23:59:59Z",
					"page":     "1",
					"per_page": "30",
				}).andThen(
					mockResponse(t, http.StatusOK, mockCommits),
				),
			}),
			requestArgs: map[string]any{
				"owner": "owner",
				"repo":  "repo",
				"since": "2023-01-01T00:00:00Z",
				"until": "2023-12-31T23:59:59Z",
			},
			expectError:     false,
			expectedCommits: mockCommits,
		},
		{
			name: "successful commits fetch with path, since, and author",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				GetReposCommitsByOwnerByRepo: expectQueryParams(t, map[string]string{
					"path":     "projects/plugins/boost",
					"since":    "2023-06-15T00:00:00Z",
					"author":   "username",
					"page":     "1",
					"per_page": "30",
				}).andThen(
					mockResponse(t, http.StatusOK, mockCommits),
				),
			}),
			requestArgs: map[string]any{
				"owner":  "owner",
				"repo":   "repo",
				"path":   "projects/plugins/boost",
				"since":  "2023-06-15T00:00:00Z",
				"author": "username",
			},
			expectError:     false,
			expectedCommits: mockCommits,
		},
		{
			name:         "invalid since timestamp returns error",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}),
			requestArgs: map[string]any{
				"owner": "owner",
				"repo":  "repo",
				"since": "not-a-date",
			},
			expectError:    true,
			expectedErrMsg: "invalid since timestamp",
		},
		{
			name: "successful commits fetch with pagination",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				GetReposCommitsByOwnerByRepo: expectQueryParams(t, map[string]string{
					"page":     "2",
					"per_page": "10",
				}).andThen(
					mockResponse(t, http.StatusOK, mockCommits),
				),
			}),
			requestArgs: map[string]any{
				"owner":   "owner",
				"repo":    "repo",
				"page":    float64(2),
				"perPage": float64(10),
			},
			expectError:     false,
			expectedCommits: mockCommits,
		},
		{
			name: "commits fetch fails",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				GetReposCommitsByOwnerByRepo: func(w http.ResponseWriter, _ *http.Request) {
					w.WriteHeader(http.StatusNotFound)
					_, _ = w.Write([]byte(`{"message": "Not Found"}`))
				},
			}),
			requestArgs: map[string]any{
				"owner": "owner",
				"repo":  "nonexistent-repo",
			},
			expectError:    true,
			expectedErrMsg: "failed to list commits",
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			// Setup client with mock
			client := mustNewGHClient(t, 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)
				require.True(t, result.IsError)
				errorContent := getErrorResult(t, result)
				assert.Contains(t, errorContent.Text, tc.expectedErrMsg)
				return
			}

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

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

			// Unmarshal and verify the result
			var returnedCommits []MinimalCommit
			err = json.Unmarshal([]byte(textContent.Text), &returnedCommits)
			require.NoError(t, err)
			assert.Len(t, returnedCommits, len(tc.expectedCommits))
			for i, commit := range returnedCommits {
				assert.Equal(t, tc.expectedCommits[i].GetSHA(), commit.SHA)
				assert.Equal(t, tc.expectedCommits[i].GetHTMLURL(), commit.HTMLURL)
				if tc.expectedCommits[i].Commit != nil {
					assert.Equal(t, tc.expectedCommits[i].Commit.GetMessage(), commit.Commit.Message)
				}
				if tc.expectedCommits[i].Author != nil {
					assert.Equal(t, tc.expectedCommits[i].Author.GetLogin(), commit.Author.Login)
				}

				// Files and stats are never included in list_commits
				assert.Nil(t, commit.Files)
				assert.Nil(t, commit.Stats)
			}
		})
	}
}

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

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

	assert.Equal(t, "create_or_update_file", tool.Name)
	assert.NotEmpty(t, tool.Description)
	assert.Contains(t, schema.Properties, "owner")
	assert.Contains(t, schema.Properties, "repo")
	assert.Contains(t, schema.Properties, "path")
	assert.Contains(t, schema.Properties, "content")
	assert.Contains(t, schema.Properties, "message")
	assert.Contains(t, schema.Properties, "branch")
	assert.Contains(t, schema.Properties, "sha")
	assert.ElementsMatch(t, schema.Required, []string{"owner", "repo", "path", "content", "message", "branch"})

	// Setup mock file content response
	mockFileResponse := &github.RepositoryContentResponse{
		Content: &github.RepositoryContent{
			Name:        github.Ptr("example.md"),
			Path:        github.Ptr("docs/example.md"),
			SHA:         github.Ptr("abc123def456"),
			Size:        github.Ptr(42),
			HTMLURL:     github.Ptr("https://github.com/owner/repo/blob/main/docs/example.md"),
			DownloadURL: github.Ptr("https://raw.githubusercontent.com/owner/repo/main/docs/example.md"),
		},
		Commit: github.Commit{
			SHA:     github.Ptr("def456abc789"),
			Message: github.Ptr("Add example file"),
			Author: &github.CommitAuthor{
				Name:  github.Ptr("Test User"),
				Email: github.Ptr("test@example.com"),
				Date:  &github.Timestamp{Time: time.Now()},
			},
			HTMLURL: github.Ptr("https://github.com/owner/repo/commit/def456abc789"),
		},
	}

	tests := []struct {
		name            string
		mockedClient    *http.Client
		requestArgs     map[string]any
		expectError     bool
		expectedContent *github.RepositoryContentResponse
		expectedErrMsg  string
	}{
		{
			name: "successful file creation",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				PutReposContentsByOwnerByRepoByPath: expectRequestBody(t, map[string]any{
					"message": "Add example file",
					"content": "IyBFeGFtcGxlCgpUaGlzIGlzIGFuIGV4YW1wbGUgZmlsZS4=", // Base64 encoded content
					"branch":  "main",
				}).andThen(
					mockResponse(t, http.StatusOK, mockFileResponse),
				),
				"PUT /repos/{owner}/{repo}/contents/{path:.*}": expectRequestBody(t, map[string]any{
					"message": "Add example file",
					"content": "IyBFeGFtcGxlCgpUaGlzIGlzIGFuIGV4YW1wbGUgZmlsZS4=", // Base64 encoded content
					"branch":  "main",
				}).andThen(
					mockResponse(t, http.StatusOK, mockFileResponse),
				),
			}),
			requestArgs: map[string]any{
				"owner":   "owner",
				"repo":    "repo",
				"path":    "docs/example.md",
				"content": "# Example\n\nThis is an example file.",
				"message": "Add example file",
				"branch":  "main",
			},
			expectError:     false,
			expectedContent: mockFileResponse,
		},
		{
			name: "successful file update with SHA",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				"GET /repos/owner/repo/contents/docs/example.md": mockResponse(t, http.StatusOK, &github.RepositoryContent{
					SHA:  github.Ptr("abc123def456"),
					Type: github.Ptr("file"),
				}),
				"GET /repos/{owner}/{repo}/contents/{path:.*}": mockResponse(t, http.StatusOK, &github.RepositoryContent{
					SHA:  github.Ptr("abc123def456"),
					Type: github.Ptr("file"),
				}),
				PutReposContentsByOwnerByRepoByPath: expectRequestBody(t, map[string]any{
					"message": "Update example file",
					"content": "IyBVcGRhdGVkIEV4YW1wbGUKClRoaXMgZmlsZSBoYXMgYmVlbiB1cGRhdGVkLg==", // Base64 encoded content
					"branch":  "main",
					"sha":     "abc123def456",
				}).andThen(
					mockResponse(t, http.StatusOK, mockFileResponse),
				),
				"PUT /repos/{owner}/{repo}/contents/{path:.*}": expectRequestBody(t, map[string]any{
					"message": "Update example file",
					"content": "IyBVcGRhdGVkIEV4YW1wbGUKClRoaXMgZmlsZSBoYXMgYmVlbiB1cGRhdGVkLg==", // Base64 encoded content
					"branch":  "main",
					"sha":     "abc123def456",
				}).andThen(
					mockResponse(t, http.StatusOK, mockFileResponse),
				),
			}),
			requestArgs: map[string]any{
				"owner":   "owner",
				"repo":    "repo",
				"path":    "docs/example.md",
				"content": "# Updated Example\n\nThis file has been updated.",
				"message": "Update example file",
				"branch":  "main",
				"sha":     "abc123def456",
			},
			expectError:     false,
			expectedContent: mockFileResponse,
		},
		{
			name: "file creation fails",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				PutReposContentsByOwnerByRepoByPath: func(w http.ResponseWriter, _ *http.Request) {
					w.WriteHeader(http.StatusUnprocessableEntity)
					_, _ = w.Write([]byte(`{"message": "Invalid request"}`))
				},
				"PUT /repos/{owner}/{repo}/contents/{path:.*}": func(w http.ResponseWriter, _ *http.Request) {
					w.WriteHeader(http.StatusUnprocessableEntity)
					_, _ = w.Write([]byte(`{"message": "Invalid request"}`))
				},
			}),
			requestArgs: map[string]any{
				"owner":   "owner",
				"repo":    "repo",
				"path":    "docs/example.md",
				"content": "#Invalid Content",
				"message": "Invalid request",
				"branch":  "nonexistent-branch",
			},
			expectError:    true,
			expectedErrMsg: "failed to create/update file",
		},
		{
			name: "sha validation - current sha matches",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				"GET /repos/owner/repo/contents/docs/example.md": mockResponse(t, http.StatusOK, &github.RepositoryContent{
					SHA:  github.Ptr("abc123def456"),
					Type: github.Ptr("file"),
				}),
				"GET /repos/{owner}/{repo}/contents/{path:.*}": mockResponse(t, http.StatusOK, &github.RepositoryContent{
					SHA:  github.Ptr("abc123def456"),
					Type: github.Ptr("file"),
				}),
				PutReposContentsByOwnerByRepoByPath: expectRequestBody(t, map[string]any{
					"message": "Update example file",
					"content": "IyBVcGRhdGVkIEV4YW1wbGUKClRoaXMgZmlsZSBoYXMgYmVlbiB1cGRhdGVkLg==",
					"branch":  "main",
					"sha":     "abc123def456",
				}).andThen(
					mockResponse(t, http.StatusOK, mockFileResponse),
				),
				"PUT /repos/{owner}/{repo}/contents/{path:.*}": expectRequestBody(t, map[string]any{
					"message": "Update example file",
					"content": "IyBVcGRhdGVkIEV4YW1wbGUKClRoaXMgZmlsZSBoYXMgYmVlbiB1cGRhdGVkLg==",
					"branch":  "main",
					"sha":     "abc123def456",
				}).andThen(
					mockResponse(t, http.StatusOK, mockFileResponse),
				),
			}),
			requestArgs: map[string]any{
				"owner":   "owner",
				"repo":    "repo",
				"path":    "docs/example.md",
				"content": "# Updated Example\n\nThis file has been updated.",
				"message": "Update example file",
				"branch":  "main",
				"sha":     "abc123def456",
			},
			expectError:     false,
			expectedContent: mockFileResponse,
		},
		{
			name: "sha validation - stale sha detected",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				"GET /repos/owner/repo/contents/docs/example.md": mockResponse(t, http.StatusOK, &github.RepositoryContent{
					SHA:  github.Ptr("newsha999888"),
					Type: github.Ptr("file"),
				}),
				"GET /repos/{owner}/{repo}/contents/{path:.*}": mockResponse(t, http.StatusOK, &github.RepositoryContent{
					SHA:  github.Ptr("newsha999888"),
					Type: github.Ptr("file"),
				}),
			}),
			requestArgs: map[string]any{
				"owner":   "owner",
				"repo":    "repo",
				"path":    "docs/example.md",
				"content": "# Updated Example\n\nThis file has been updated.",
				"message": "Update example file",
				"branch":  "main",
				"sha":     "oldsha123456",
			},
			expectError:    true,
			expectedErrMsg: "SHA mismatch: provided SHA oldsha123456 is stale. Current file SHA is newsha999888",
		},
		{
			name: "sha validation - file doesn't exist (404), proceed with create",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				"GET /repos/owner/repo/contents/docs/example.md": func(w http.ResponseWriter, _ *http.Request) {
					w.WriteHeader(http.StatusNotFound)
				},
				"GET /repos/{owner}/{repo}/contents/{path:.*}": func(w http.ResponseWriter, _ *http.Request) {
					w.WriteHeader(http.StatusNotFound)
				},
				PutReposContentsByOwnerByRepoByPath: expectRequestBody(t, map[string]any{
					"message": "Create new file",
					"content": "IyBOZXcgRmlsZQoKVGhpcyBpcyBhIG5ldyBmaWxlLg==",
					"branch":  "main",
					"sha":     "ignoredsha", // SHA is sent but GitHub API ignores it for new files
				}).andThen(
					mockResponse(t, http.StatusCreated, mockFileResponse),
				),
				"PUT /repos/{owner}/{repo}/contents/{path:.*}": expectRequestBody(t, map[string]any{
					"message": "Create new file",
					"content": "IyBOZXcgRmlsZQoKVGhpcyBpcyBhIG5ldyBmaWxlLg==",
					"branch":  "main",
					"sha":     "ignoredsha", // SHA is sent but GitHub API ignores it for new files
				}).andThen(
					mockResponse(t, http.StatusCreated, mockFileResponse),
				),
			}),
			requestArgs: map[string]any{
				"owner":   "owner",
				"repo":    "repo",
				"path":    "docs/example.md",
				"content": "# New File\n\nThis is a new file.",
				"message": "Create new file",
				"branch":  "main",
				"sha":     "ignoredsha",
			},
			expectError:     false,
			expectedContent: mockFileResponse,
		},
		{
			name: "no sha provided - file exists, rejects update",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				"GET /repos/owner/repo/contents/docs/example.md": mockResponse(t, http.StatusOK, &github.RepositoryContent{
					SHA:  github.Ptr("existing123"),
					Type: github.Ptr("file"),
				}),
				"GET /repos/{owner}/{repo}/contents/{path:.*}": mockResponse(t, http.StatusOK, &github.RepositoryContent{
					SHA:  github.Ptr("existing123"),
					Type: github.Ptr("file"),
				}),
			}),
			requestArgs: map[string]any{
				"owner":   "owner",
				"repo":    "repo",
				"path":    "docs/example.md",
				"content": "# Updated\n\nUpdated without SHA.",
				"message": "Update without SHA",
				"branch":  "main",
			},
			expectError:    true,
			expectedErrMsg: "File already exists at docs/example.md",
		},
		{
			name: "no sha provided - file doesn't exist, no warning",
			mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
				"GET /repos/owner/repo/contents/docs/example.md": func(w http.ResponseWriter, _ *http.Request) {
					w.WriteHeader(http.StatusNotFound)
				},
				"GET /repos/{owner}/{repo}/contents/{path:.*}": func(w http.ResponseWriter, _ *http.Request) {
					w.WriteHeader(http.StatusNotFound)
				},
				PutReposContentsByOwnerByRepoByPath: expectRequestBody(t, map[string]any{
					"message": "Create new file",
					"content": "IyBOZXcgRmlsZQoKQ3JlYXRlZCB3aXRob3V0IFNIQQ==",
					"branch":  "main",
				}).andThen(
					mockResponse(t, http.StatusCreated, mockFileResponse),
				),
				"PUT /repos/{owner}/{repo}/contents/{path:.*}": expectRequestBody(t, map[string]any{
					"message": "Create new file",
					"content": "IyBOZXcgRmlsZQoKQ3JlYXRlZCB3aXRob3V0IFNIQQ==",
					"branch":  "main",
				}).andThen(
					mockResponse(t, http.StatusCreated, mockFileResponse),
				),
			}),
			requestArgs: map[string]any{
				"owner":   "owner",
				"repo":    "repo",
				"path":    "docs/example.md",
				"content": "# New File\n\nCreated without SHA",
				"message": "Create new file",
				"branch":  "main",
			},
			expectError:     false,
			expectedContent: mockFileResponse,
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			// Setup client with mock
			client := mustNewGHClient(t, 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)
				require.True(t, result.IsError)
				errorContent := getErrorResult(t, result)
				assert.Contains(t, errorContent.Text, tc.expectedErrMsg)
				return
			}

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

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

			// If expectedErrMsg is set (but expectError is false), this is a warning case
			if tc.expectedErrMsg != "" {
				assert.Contains(t, textContent.Text, tc.expectedErrMsg)
				return
			}

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

			// Verify content
			assert.Equal(t, tc.expectedContent.Content.GetName(), returnedContent.Content.Name)
			assert.Equal(t, tc.expectedContent.Content.GetPath(), returnedContent.Content.Path)
			assert.Equal(t, tc.expectedContent.Content.GetSHA(), returnedContent.Content.SHA)
			assert.Equal(t, tc.expectedContent.Content.GetSize(), returnedContent.Content.Size)
			assert.Equal(t, tc.expectedContent.Content.GetHTMLURL(), returnedContent.Content.HTMLURL)

			// Verify commit
			assert.Equal(t, tc.expectedContent.Commit.GetSHA(), returnedContent.Commit.SHA)
			assert.Equal(t, tc.expectedContent.Commit.GetMessage(), returnedContent.Commit.Message)
			assert.Equal(t, tc.expectedContent.Commit.GetHTMLURL(), returnedContent.Commit.HTMLURL)

			// Verify commit author
			require.NotNil(t, returnedContent.Commit.Author)
			assert.Equal(t, tc.expectedContent.Commit.Author.GetName(), returnedContent.Commit.Author.Name)
			assert.Equal(t, tc.expectedContent.Commit.Author.GetEmail(), returnedContent.Commit.Author.Email)
			assert.NotEmpty(t, returnedContent.Commit.Author.Date)
		})
	}
}

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

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

	assert.Equal(t, "create_repository", tool.Name)
	assert.NotEmpty(t, tool.Description)
	assert.Contains(t, schema.Properties, "name")
	assert.Contains(t, schema.Properties, "description")
	assert.Contains(t, schema.Properties, "organization")
	assert.Contains(t, schema.Properties, "private")
	assert.Contains(t, schema.Properties, "autoInit")
	assert.ElementsMatch(t, schema.Required, []string{"name"})

	// Setup mock repository response
	mockRepo := &github.Repository{
		Name:        github.Ptr("test-repo"),
		Description: github.Ptr("Test repository"),
		Private:     github.Ptr(true),
		HTMLURL:     github.Ptr("https://github.com/testuser/test-repo"),
		CreatedAt:   &github.Timestamp{Time: time.Now()},
		Owner: &github.User{
			Login: github.Ptr("testuser"),
		},
	}

	tests := []struct {
		name           string
		mockedClient   *http.Client
		requestArgs    map[string]any
		expectError    bool
		expectedRepo   *github.Repository
		expectedErrMsg string
	}{
		{
			name: "successful repository creation with all parameters",
			mockedClient: NewMockedHTTPClient(
				WithRequestMatchHandler(
					EndpointPattern("POST /user/repos"),
					expectRequestBody(t, map[string]any{
						"name":        "test-repo",
						"description": "Test repository",
						"private":     true,
						"auto_init":   true,
					}).andThen(
						mockResponse(t, http.StatusCreated, mockRepo),
					),
				),
			),
			requestArgs: map[string]any{
				"name":        "test-repo",
				"description": "Test repository",
				"private":     true,
				"autoInit":    true,
			},
			expectError:  false,
			expectedRepo: mockRepo,
		},
		{
			name: "successful repository creation in organization",
			mockedClient: NewMockedHTTPClient(
				WithRequestMatchHandler(
					EndpointPattern("POST /orgs/testorg/repos"),
					expectRequestBody(t, map[string]any{
						"name":        "test-repo",
						"description": "Test repository",
						"private":     false,
						"auto_init":   true,
					}).andThen(
						mockResponse(t, http.StatusCreated, mockRepo),
					),
				),
			),
			requestArgs: map[string]any{
				"name":         "test-repo",
				"description":  "Test repository",
				"organization": "testorg",
				"private":      false,
				"autoInit":     true,
			},
			expectError:  false,
			expectedRepo: mockRepo,
		},
		{
			name: "successful repository creation with minimal parameters defaults to private",
			mockedClient: NewMockedHTTPClient(
				WithRequestMatchHandler(
					EndpointPattern("POST /user/repos"),
					expectRequestBody(t, map[string]any{
						"name":        "test-repo",
						"auto_init":   false,
						"description": "",
						"private":     true,
					}).andThen(
						mockResponse(t, http.StatusCreated, mockRepo),
					),
				),
			),
			requestArgs: map[string]any{
				"name": "test-repo",
			},
			expectError:  false,
			expectedRepo: mockRepo,
		},
		{
			name: "successful public repository creation when private is explicitly false",
			mockedClient: NewMockedHTTPClient(
				WithRequestMatchHandler(
					EndpointPattern("POST /user/repos"),
					expectRequestBody(t, map[string]any{
						"name":        "test-repo",
						"auto_init":   false,
						"description": "",
						"private":     false,
					}).andThen(
						mockResponse(t, http.StatusCreated, mockRepo),
					),
				),
			),
			requestArgs: map[string]any{
				"name":    "test-repo",
				"private": false,
			},
			expectError:  false,
			expectedRepo: mockRepo,
		},
		{
			name: "repository creation fails",
			mockedClient: NewMockedHTTPClient(
				WithRequestMatchHandler(
					EndpointPattern("POST /user/repos"),
					http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
						w.WriteHeader(http.StatusUnprocessableEntity)
						_, _ = w.Write([]byte(`{"message": "Repository creation failed"}`))
					}),
				),
			),
			requestArgs: map[string]any{
				"name": "invalid-repo",
			},
			expectError:    true,
			expectedErrMsg: "failed to create repository",
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			// Setup client with mock
			client := mustNewGHClient(t, 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)
				require.True(t, result.IsError)
				errorContent := getErrorResult(t, result)
				assert.Contains(t, errorContent.Text, tc.expectedErrMsg)
				return
			}

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

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

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

			// Verify repository details
			assert.Equal(t, tc.expectedRepo.GetHTMLURL(), returnedRepo.URL)
		})
	}
}

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

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

	assert.Equal(t, "push_files", tool.Name)
	assert.NotEmpty(t, tool.Description)
	assert.Contains(t, schema.Properties, "owner")
	assert.Contains(t, schema.Properties, "repo")
	assert.Contains(t, schema.Properties, "branch")
	assert.Contains(t, schema.Properties, "files")
	assert.Contains(t, schema.Properties, "message")
	assert.ElementsMatch(t, schema.Required, []string{"owner", "repo", "branch", "files", "message"})

	// Setup mock objects
	mockRef := &github.Reference{
		Ref: github.Ptr("refs/heads/main"),
		Object: &github.GitObject{
			SHA: github.Ptr("abc123"),
			URL: github.Ptr("https://api.github.com/repos/owner/repo/git/trees/abc123"),
		},
	}

	mockCommit := &github.Commit{
		SHA: github.Ptr("abc123"),
		Tree: &github.Tree{
			SHA: github.Ptr("def456"),
		},
	}

	mockTree := &github.Tree{
		SHA: github.Ptr("ghi789"),
	}

	mockNewCommit := &github.Commit{
		SHA:     github.Ptr("jkl012"),
		Message: github.Ptr("Update multiple files"),
		HTMLURL: github.Ptr("https://github.com/owner/repo/commit/jkl012"),
	}

	mockUpdatedRef := &github.Reference{
		Ref: github.Ptr("refs/heads/main"),
		Object: &github.GitObject{
			SHA: github.Ptr("jkl012"),
			URL: github.Ptr("https://api.github.com/repos/owner/repo/git/trees/jkl012"),
		},
	}

	// Define test cases
	tests := []struct {
		name           string
		mockedClient   *http.Client
		requestArgs    map[string]any
		expectError    bool
		expectedRef    *github.Reference
		expectedErrMsg string
	}{
		{
			name: "successful push of multiple files",
			mockedClient: NewMockedHTTPClient(
				// Get branch reference
				WithRequestMatch(
					GetReposGitRefByOwnerByRepoByRef,
					mockRef,
				),
				// Get commit
				WithRequestMatch(
					GetReposGitCommitsByOwnerByRepoByCommitSHA,
					mockCommit,
				),
				// Create tree
				WithRequestMatchHandler(
					PostReposGitTreesByOwnerByRepo,
					expectRequestBody(t, map[string]any{
						"base_tree": "def456",
						"tree": []any{
							map[string]any{
								"path":    "README.md",
								"mode":    "100644",
								"type":    "blob",
								"content": "# Updated README\n\nThis is an updated README file.",
							},
							map[string]any{
								"path":    "docs/example.md",
								"mode":    "100644",
								"type":    "blob",
								"content": "# Example\n\nThis is an example file.",
							},
						},
					}).andThen(
						mockResponse(t, http.StatusCreated, mockTree),
					),
				),
				// Create commit
				WithRequestMatchHandler(
					PostReposGitCommitsByOwnerByRepo,
					expectRequestBody(t, map[string]any{
						"message": "Update multiple files",
						"tree":    "ghi789",
						"parents": []any{"abc123"},
					}).andThen(
						mockResponse(t, http.StatusCreated, mockNewCommit),
					),
				),
				// Update reference
				WithRequestMatchHandler(
					PatchReposGitRefsByOwnerByRepoByRef,
					expectRequestBody(t, map[string]any{
						"sha":   "jkl012",
						"force": false,
					}).andThen(
						mockResponse(t, http.StatusOK, mockUpdatedRef),
					),
				),
			),
			requestArgs: map[string]any{
				"owner":  "owner",
				"repo":   "repo",
				"branch": "main",
				"files": []any{
					map[string]any{
						"path":    "README.md",
						"content": "# Updated README\n\nThis is an updated README file.",
					},
					map[string]any{
						"path":    "docs/example.md",
						"content": "# Example\n\nThis is an example file.",
					},
				},
				"message": "Update multiple files",
			},
			expectError: false,
			expectedRef: mockUpdatedRef,
		},
		{
			name:         "fails when files parameter is invalid",
			mockedClient: NewMockedHTTPClient(
			// No requests expected
			),
			requestArgs: map[string]any{
				"owner":   "owner",
				"repo":    "repo",
				"branch":  "main",
				"files":   "invalid-files-parameter", // Not an array
				"message": "Update multiple files",
			},
			expectError:    false, // This returns a tool error, not a Go error
			expectedErrMsg: "files parameter must be an array",
		},
		{
			name: "fails when files contains object without path",
			mockedClient: NewMockedHTTPClient(
				// Get branch reference
				WithRequestMatch(
					GetReposGitRefByOwnerByRepoByRef,
					mockRef,
				),
				// Get commit
				WithRequestMatch(
					GetReposGitCommitsByOwnerByRepoByCommitSHA,
					mockCommit,
				),
			),
			requestArgs: map[string]any{
				"owner":  "owner",
				"repo":   "repo",
				"branch": "main",
				"files": []any{
					map[string]any{
						"content": "# Missing path",
					},
				},
				"message": "Update file",
			},
			expectError:    false, // This returns a tool error, not a Go error
			expectedErrMsg: "each file must have a path",
		},
		{
			name: "fails when files contains object without content",
			mockedClient: NewMockedHTTPClient(
				// Get branch reference
				WithRequestMatch(
					GetReposGitRefByOwnerByRepoByRef,
					mockRef,
				),
				// Get commit
				WithRequestMatch(
					GetReposGitCommitsByOwnerByRepoByCommitSHA,
					mockCommit,
				),
			),
			requestArgs: map[string]any{
				"owner":  "owner",
				"repo":   "repo",
				"branch": "main",
				"files": []any{
					map[string]any{
						"path": "README.md",
						// Missing content
					},
				},
				"message": "Update file",
			},
			expectError:    false, // This returns a tool error, not a Go error
			expectedErrMsg: "each file must have content",
		},
		{
			name: "fails to get branch reference",
			mockedClient: NewMockedHTTPClient(
				WithRequestMatchHandler(
					GetReposGitRefByOwnerByRepoByRef,
					mockResponse(t, http.StatusNotFound, nil),
				),
				// Mock Repositories.Get to fail when trying to create branch from default
				WithRequestMatchHandler(
					GetReposByOwnerByRepo,
					mockResponse(t, http.StatusNotFound, nil),
				),
			),
			requestArgs: map[string]any{
				"owner":  "owner",
				"repo":   "repo",
				"branch": "non-existent-branch",
				"files": []any{
					map[string]any{
						"path":    "README.md",
						"content": "# README",
					},
				},
				"message": "Update file",
			},
			expectError:    false,
			expectedErrMsg: "failed to create branch from default",
		},
		{
			name: "fails to get base commit",
			mockedClient: NewMockedHTTPClient(
				// Get branch reference
				WithRequestMatch(
					GetReposGitRefByOwnerByRepoByRef,
					mockRef,
				),
				// Fail to get commit
				WithRequestMatchHandler(
					GetReposGitCommitsByOwnerByRepoByCommitSHA,
					mockResponse(t, http.StatusNotFound, nil),
				),
			),
			requestArgs: map[string]any{
				"owner":  "owner",
				"repo":   "repo",
				"branch": "main",
				"files": []any{
					map[string]any{
						"path":    "README.md",
						"content": "# README",
					},
				},
				"message": "Update file",
			},
			expectError:    true,
			expectedErrMsg: "failed to get base commit",
		},
		{
			name: "fails to create tree",
			mockedClient: NewMockedHTTPClient(
				// Get branch reference
				WithRequestMatch(
					GetReposGitRefByOwnerByRepoByRef,
					mockRef,
				),
				// Get commit
				WithRequestMatch(
					GetReposGitCommitsByOwnerByRepoByCommitSHA,
					mockCommit,
				),
				// Fail to create tree
				WithRequestMatchHandler(
					PostReposGitTreesByOwnerByRepo,
					mockResponse(t, http.StatusInternalServerError, nil),
				),
			),
			requestArgs: map[string]any{
				"owner":  "owner",
				"repo":   "repo",
				"branch": "main",
				"files": []any{
					map[string]any{
						"path":    "README.md",
						"content": "# README",
					},
				},
				"message": "Update file",
			},
			expectError:    true,
			expectedErrMsg: "failed to create tree",
		},
		{
			name: "successful push to empty repository",
			mockedClient: NewMockedHTTPClient(
				// Get branch reference - first returns 409 for empty repo, second returns success after init
				WithRequestMatchHandler(
					GetReposGitRefByOwnerByRepoByRef,
					func() http.HandlerFunc {
						callCount := 0
						return func(w http.ResponseWriter, _ *http.Request) {
							w.Header().Set("Content-Type", "application/json")
							callCount++
							if callCount == 1 {
								// First call: empty repo
								w.WriteHeader(http.StatusConflict)
								response := map[string]any{
									"message": "Git Repository is empty.",
								}
								_ = json.NewEncoder(w).Encode(response)
							} else {
								// Second call: return the created reference
								w.WriteHeader(http.StatusOK)
								_ = json.NewEncoder(w).Encode(mockRef)
							}
						}
					}(),
				),
				// Mock Repositories.Get to return default branch for initialization
				WithRequestMatch(
					GetReposByOwnerByRepo,
					&github.Repository{
						DefaultBranch: github.Ptr("main"),
					},
				),
				// Create initial file using Contents API
				WithRequestMatchHandler(
					PutReposContentsByOwnerByRepoByPath,
					http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
						var body map[string]any
						err := json.NewDecoder(r.Body).Decode(&body)
						require.NoError(t, err)
						require.Equal(t, "Initial commit", body["message"])
						require.Equal(t, "main", body["branch"])
						w.WriteHeader(http.StatusCreated)
						response := &github.RepositoryContentResponse{
							Commit: github.Commit{SHA: github.Ptr("abc123")},
						}
						b, _ := json.Marshal(response)
						_, _ = w.Write(b)
					}),
				),
				// Get the commit after initialization
				WithRequestMatch(
					GetReposGitCommitsByOwnerByRepoByCommitSHA,
					mockCommit,
				),
				// Create tree
				WithRequestMatch(
					PostReposGitTreesByOwnerByRepo,
					mockTree,
				),
				// Create commit
				WithRequestMatch(
					PostReposGitCommitsByOwnerByRepo,
					mockNewCommit,
				),
				// Update reference
				WithRequestMatch(
					PatchReposGitRefsByOwnerByRepoByRef,
					mockUpdatedRef,
				),
			),
			requestArgs: map[string]any{
				"owner":  "owner",
				"repo":   "repo",
				"branch": "main",
				"files": []any{
					map[string]any{
						"path":    "README.md",
						"content": "# Initial README\n\nFirst commit to empty repository.",
					},
				},
				"message": "Initial commit",
			},
			expectError: false,
			expectedRef: mockUpdatedRef,
		},
		{
			name: "successful push multiple files to empty repository",
			mockedClient: NewMockedHTTPClient(
				// Get branch reference - called twice: first for empty check, second after file creation
				WithRequestMatchHandler(
					GetReposGitRefByOwnerByRepoByRef,
					func() http.HandlerFunc {
						callCount := 0
						return http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
							callCount++
							if callCount == 1 {
								// First call: returns 409 Conflict for empty repo
								w.Header().Set("Content-Type", "application/json")
								w.WriteHeader(http.StatusConflict)
								response := map[string]any{
									"message": "Git Repository is empty.",
								}
								_ = json.NewEncoder(w).Encode(response)
							} else {
								// Second call: returns the updated reference after first file creation
								w.WriteHeader(http.StatusOK)
								b, _ := json.Marshal(&github.Reference{
									Ref:    github.Ptr("refs/heads/main"),
									Object: &github.GitObject{SHA: github.Ptr("init456")},
								})
								_, _ = w.Write(b)
							}
						})
					}(),
				),
				// Mock Repositories.Get to return default branch for initialization
				WithRequestMatch(
					GetReposByOwnerByRepo,
					&github.Repository{
						DefaultBranch: github.Ptr("main"),
					},
				),
				// Create initial empty README.md file using Contents API to initialize repo
				WithRequestMatchHandler(
					PutReposContentsByOwnerByRepoByPath,
					http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
						var body map[string]any
						err := json.NewDecoder(r.Body).Decode(&body)
						require.NoError(t, err)
						require.Equal(t, "Initial commit", body["message"])
						require.Equal(t, "main", body["branch"])
						// Verify it's an empty file
						expectedContent := base64.StdEncoding.EncodeToString([]byte(""))
						require.Equal(t, expectedContent, body["content"])
						w.WriteHeader(http.StatusCreated)
						response := &github.RepositoryContentResponse{
							Content: &github.RepositoryContent{
								SHA: github.Ptr("readme123"),
							},
							Commit: github.Commit{
								SHA: github.Ptr("init456"),
								Tree: &github.Tree{
									SHA: github.Ptr("tree456"),
								},
							},
						}
						b, _ := json.Marshal(response)
						_, _ = w.Write(b)
					}),
				),
				// Get the commit to retrieve parent SHA
				WithRequestMatchHandler(
					GetReposGitCommitsByOwnerByRepoByCommitSHA,
					http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
						w.WriteHeader(http.StatusOK)
						response := &github.Commit{
							SHA: github.Ptr("init456"),
							Tree: &github.Tree{
								SHA: github.Ptr("tree456"),
							},
						}
						b, _ := json.Marshal(response)
						_, _ = w.Write(b)
					}),
				),
				// Create tree with all user files
				WithRequestMatchHandler(
					PostReposGitTreesByOwnerByRepo,
					expectRequestBody(t, map[string]any{
						"base_tree": "tree456",
						"tree": []any{
							map[string]any{
								"path":    "README.md",
								"mode":    "100644",
								"type":    "blob",
								"content": "# Project\n\nProject README",
							},
							map[string]any{
								"path":    ".gitignore",
								"mode":    "100644",
								"type":    "blob",
								"content": "node_modules/\n*.log\n",
							},
							map[string]any{
								"path":    "src/main.js",
								"mode":    "100644",
								"type":    "blob",
								"content": "console.log('Hello World');\n",
							},
						},
					}).andThen(
						mockResponse(t, http.StatusCreated, mockTree),
					),
				),
				// Create commit with all user files
				WithRequestMatchHandler(
					PostReposGitCommitsByOwnerByRepo,
					expectRequestBody(t, map[string]any{
						"message": "Initial project setup",
						"tree":    "ghi789",
						"parents": []any{"init456"},
					}).andThen(
						mockResponse(t, http.StatusCreated, mockNewCommit),
					),
				),
				// Update reference
				WithRequestMatchHandler(
					PatchReposGitRefsByOwnerByRepoByRef,
					expectRequestBody(t, map[string]any{
						"sha":   "jkl012",
						"force": false,
					}).andThen(
						mockResponse(t, http.StatusOK, mockUpdatedRef),
					),
				),
			),
			requestArgs: map[string]any{
				"owner":  "owner",
				"repo":   "repo",
				"branch": "main",
				"files": []any{
					map[string]any{
						"path":    "README.md",
						"content": "# Project\n\nProject README",
					},
					map[string]any{
						"path":    ".gitignore",
						"content": "node_modules/\n*.log\n",
					},
					map[string]any{
						"path":    "src/main.js",
						"content": "console.log('Hello World');\n",
					},
				},
				"message": "Initial project setup",
			},
			expectError: false,
			expectedRef: mockUpdatedRef,
		},
		{
			name: "fails to create initial file in empty repository",
			mockedClient: NewMockedHTTPClient(
				// Get branch reference returns 409 Conflict for empty repo
				WithRequestMatchHandler(
					GetReposGitRefByOwnerByRepoByRef,
					http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
						w.Header().Set("Content-Type", "application/json")
						w.WriteHeader(http.StatusConflict)
						response := map[string]any{
							"message": "Git Repository is empty.",
						}
						_ = json.NewEncoder(w).Encode(response)
					}),
				),
				// Mock Repositories.Get to return default branch
				WithRequestMatch(
					GetReposByOwnerByRepo,
					&github.Repository{
						DefaultBranch: github.Ptr("main"),
					},
				),
				// Fail to create initial file using Contents API
				WithRequestMatchHandler(
					PutReposContentsByOwnerByRepoByPath,
					mockResponse(t, http.StatusInternalServerError, nil),
				),
			),
			requestArgs: map[string]any{
				"owner":  "owner",
				"repo":   "repo",
				"branch": "main",
				"files": []any{
					map[string]any{
						"path":    "README.md",
						"content": "# README",
					},
				},
				"message": "Initial commit",
			},
			expectError:    false,
			expectedErrMsg: "failed to initialize repository",
		},
		{
			name: "fails to get reference after creating initial file in empty repository",
			mockedClient: NewMockedHTTPClient(
				// Get branch reference - called twice
				WithRequestMatchHandler(
					GetReposGitRefByOwnerByRepoByRef,
					func() http.HandlerFunc {
						callCount := 0
						return http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
							callCount++
							if callCount == 1 {
								// First call: returns 409 Conflict for empty repo
								w.Header().Set("Content-Type", "application/json")
								w.WriteHeader(http.StatusConflict)
								response := map[string]any{
									"message": "Git Repository is empty.",
								}
								_ = json.NewEncoder(w).Encode(response)
							} else {
								// Second call: fails
								w.WriteHeader(http.StatusInternalServerError)
							}
						})
					}(),
				),
				// Mock Repositories.Get to return default branch
				WithRequestMatch(
					GetReposByOwnerByRepo,
					&github.Repository{
						DefaultBranch: github.Ptr("main"),
					},
				),
				// Create initial file using Contents API
				WithRequestMatch(
					PutReposContentsByOwnerByRepoByPath,
					&github.RepositoryContentResponse{
						Content: &github.RepositoryContent{SHA: github.Ptr("readme123")},
						Commit:  github.Commit{SHA: github.Ptr("init456")},
					},
				),
			),
			requestArgs: map[string]any{
				"owner":  "owner",
				"repo":   "repo",
				"branch": "main",
				"files": []any{
					map[string]any{
						"path":    "README.md",
						"content": "# README",
					},
				},
				"message": "Initial commit",
			},
			expectError:    false,
			expectedErrMsg: "failed to initialize repository",
		},
		{
			name: "fails to get commit in empty repository with multiple files",
			mockedClient: NewMockedHTTPClient(
				// Get branch reference returns 409 Conflict for empty repo
				WithRequestMatchHandler(
					GetReposGitRefByOwnerByRepoByRef,
					http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
						w.Header().Set("Content-Type", "application/json")
						w.WriteHeader(http.StatusConflict)
						response := map[string]any{
							"message": "Git Repository is empty.",
						}
						_ = json.NewEncoder(w).Encode(response)
					}),
				),
				// Mock Repositories.Get to return default branch
				WithRequestMatch(
					GetReposByOwnerByRepo,
					&github.Repository{
						DefaultBranch: github.Ptr("main"),
					},
				),
				// Create initial file using Contents API
				WithRequestMatch(
					PutReposContentsByOwnerByRepoByPath,
					&github.RepositoryContentResponse{
						Content: &github.RepositoryContent{SHA: github.Ptr("readme123")},
						Commit:  github.Commit{SHA: github.Ptr("init456")},
					},
				),
				// Fail to get commit
				WithRequestMatchHandler(
					GetReposGitCommitsByOwnerByRepoByCommitSHA,
					mockResponse(t, http.StatusInternalServerError, nil),
				),
			),
			requestArgs: map[string]any{
				"owner":  "owner",
				"repo":   "repo",
				"branch": "main",
				"files": []any{
					map[string]any{
						"path":    "README.md",
						"content": "# README",
					},
					map[string]any{
						"path":    "LICENSE",
						"content": "MIT",
					},
				},
				"message": "Initial commit",
			},
			expectError:    false,
			expectedErrMsg: "failed to initialize repository",
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			// Setup client with mock
			client := mustNewGHClient(t, 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)
				require.True(t, result.IsError)
				errorContent := getErrorResult(t, result)
				assert.Contains(t, errorContent.Text, tc.expectedErrMsg)
				return
			}

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

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

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

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

			assert.Equal(t, *tc.expectedRef.Ref, *returnedRef.Ref)
			assert.Equal(t, *tc.expectedRef.Object.SHA, *returnedRef.Object.SHA)
		})
	}
}

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

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

	assert.Equal(t, "list_branches", tool.Name)
	assert.NotEmpty(t, tool.Description)
	assert.Contains(t, schema.Properties, "owner")
	assert.Contains(t, schema.Properties, "repo")
	assert.Contains(t, schema.Properties, "page")
	assert.Contains(t, schema.Properties, "perPage")
	assert.ElementsMatch(t, schema.Required, []string{"owner", "repo"})

	// Setup mock branches for success case
	mockBranches := []*github.Branch{
		{
			Name:   github.Ptr("main"),
			Commit: &github.RepositoryCommit{SHA: github.Ptr("abc123")},
		},
		{
			Name:   github.Ptr("develop"),
			Commit: &github.RepositoryCommit{SHA: github.Ptr("def456")},
		},
	}

	// Test cases
	tests := []struct {
		name          string
		args          map[string]any
		mockResponses []MockBackendOption
		wantErr       bool
		errContains   string
	}{
		{
			name: "success",
			args: map[string]any{
				"owner": "owner",
				"repo":  "repo",
				"page":  float64(2),
			},
			mockResponses: []MockBackendOption{
				WithRequestMatch(
					GetReposBranchesByOwnerByRepo,
					mockBranches,
				),
			},
			wantErr: false,
		},
		{
			name: "missing owner",
			args: map[string]any{
				"repo": "repo",
			},
			mockResponses: []MockBackendOption{},
			wantErr:       false,
			errContains:   "missing required parameter: owner",
		},
		{
			name: "missing repo",
			args: map[string]any{
				"owner": "owner",
			},
			mockResponses: []MockBackendOption{},
			wantErr:       false,
			errContains:   "missing required parameter: repo",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			// Create mock client
			mockClient := mustNewGHClient(t, NewMockedHTTPClient(tt.mockResponses...))
			deps := BaseDeps{
				Client: mockClient,
			}
			handler := serverTool.Handler(deps)

			// Create request
			request := createMCPRequest(tt.args)

			// Call handler
			result, err := handler(ContextWithDeps(context.Background(), deps), &request)
			if tt.wantErr {
				require.NoError(t, err)
				if tt.errContains != "" {
					textContent := getErrorResult(t, result)
					assert.Contains(t, textContent.Text, tt.errContains)
				}
				return
			}

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

			if tt.errContains != "" {
				textContent := getTextResult(t, result)
				assert.Contains(t, textContent.Text, tt.errContains)
				return
			}

			textContent := getTextResult(t, result)
			require.NotEmpty(t, textContent.Text)

			// Verify response
			var branches []*github.Branch
			err = json.Unmarshal([]byte(textContent.Text), &branches)
			require.NoError(t, err)
			assert.Len(t, branches, 2)
			assert.Equal(t, "main", *branches[0].Name)
			assert.Equal(t, "develop", *branches[1].Name)
		})
	}
}

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

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

	assert.Equal(t, "delete_file", tool.Name)
	assert.NotEmpty(t, tool.Description)
	assert.Contains(t, schema.Properties, "owner")
	assert.Contains(t, schema.Properties, "repo")
	assert.Contains(t, schema.Properties, "path")
	assert.Contains(t, schema.Properties, "message")
	assert.Contains(t, schema.Properties, "branch")
	// SHA is no longer required since we're using Git Data API
	assert.ElementsMatch(t, schema.Required, []string{"owner", "repo", "path", "message", "branch"})

	// Setup mock objects for Git Data API
	mockRef := &github.Reference{
		Ref: github.Ptr("refs/heads/main"),
		Object: &github.GitObject{
			SHA: github.Ptr("abc123"),
		},
	}

	mockCommit := &github.Commit{
		SHA: github.Ptr("abc123"),
		Tree: &github.Tree{
			SHA: github.Ptr("def456"),
		},
	}

	mockTree := &github.Tree{
		SHA: github.Ptr("ghi789"),
	}

	mockNewCommit := &github.Commit{
		SHA:     github.Ptr("jkl012"),
		Message: github.Ptr("Delete example file"),
		HTMLURL: github.Ptr("https://github.com/owner/repo/commit/jkl012"),
	}

	tests := []struct {
		name              string
		mockedClient      *http.Client
		requestArgs       map[string]any
		expectError       bool
		expectedCommitSHA string
		expectedErrMsg    string
	}{
		{
			name: "successful file deletion using Git Data API",
			mockedClient: NewMockedHTTPClient(
				// Get branch reference
				WithRequestMatch(
					GetReposGitRefByOwnerByRepoByRef,
					mockRef,
				),
				// Get commit
				WithRequestMatch(
					GetReposGitCommitsByOwnerByRepoByCommitSHA,
					mockCommit,
				),
				// Create tree
				WithRequestMatchHandler(
					PostReposGitTreesByOwnerByRepo,
					expectRequestBody(t, map[string]any{
						"base_tree": "def456",
						"tree": []any{
							map[string]any{
								"path": "docs/example.md",
								"mode": "100644",
								"type": "blob",
								"sha":  nil,
							},
						},
					}).andThen(
						mockResponse(t, http.StatusCreated, mockTree),
					),
				),
				// Create commit
				WithRequestMatchHandler(
					PostReposGitCommitsByOwnerByRepo,
					expectRequestBody(t, map[string]any{
						"message": "Delete example file",
						"tree":    "ghi789",
						"parents": []any{"abc123"},
					}).andThen(
						mockResponse(t, http.StatusCreated, mockNewCommit),
					),
				),
				// Update reference
				WithRequestMatchHandler(
					PatchReposGitRefsByOwnerByRepoByRef,
					expectRequestBody(t, map[string]any{
						"sha":   "jkl012",
						"force": false,
					}).andThen(
						mockResponse(t, http.StatusOK, &github.Reference{
							Ref: github.Ptr("refs/heads/main"),
							Object: &github.GitObject{
								SHA: github.Ptr("jkl012"),
							},
						}),
					),
				),
			),
			requestArgs: map[string]any{
				"owner":   "owner",
				"repo":    "repo",
				"path":    "docs/example.md",
				"message": "Delete example file",
				"branch":  "main",
			},
			expectError:       false,
			expectedCommitSHA: "jkl012",
		},
		{
			name: "file deletion fails - branch not found",
			mockedClient: NewMockedHTTPClient(
				WithRequestMatchHandler(
					GetReposGitRefByOwnerByRepoByRef,
					http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
						w.WriteHeader(http.StatusNotFound)
						_, _ = w.Write([]byte(`{"message": "Reference not found"}`))
					}),
				),
			),
			requestArgs: map[string]any{
				"owner":   "owner",
				"repo":    "repo",
				"path":    "docs/nonexistent.md",
				"message": "Delete nonexistent file",
				"branch":  "nonexistent-branch",
			},
			expectError:    true,
			expectedErrMsg: "failed to get branch reference",
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			// Setup client with mock
			client := mustNewGHClient(t, 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
			}

			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 response map[string]any
			err = json.Unmarshal([]byte(textContent.Text), &response)
			require.NoError(t, err)

			// Verify the response contains the expected commit
			commit, ok := response["commit"].(map[string]any)
			require.True(t, ok)
			commitSHA, ok := commit["sha"].(string)
			require.True(t, ok)
			assert.Equal(t, tc.expectedCommitSHA, commitSHA)
		})
	}
}

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

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

	assert.Equal(t, "list_tags", tool.Name)
	assert.NotEmpty(t, tool.Description)
	assert.Contains(t, schema.Properties, "owner")
	assert.Contains(t, schema.Properties, "repo")
	assert.ElementsMatch(t, schema.Required, []string{"owner", "repo"})

	// Setup mock tags for success case
	mockTags := []*github.RepositoryTag{
		{
			Name: github.Ptr("v1.0.0"),
			Commit: &github.Commit{
				SHA: github.Ptr("v1.0.0-tag-sha"),
				URL: github.Ptr("https://api.github.com/repos/owner/repo/commits/abc123"),
			},
			ZipballURL: github.Ptr("https://github.com/owner/repo/zipball/v1.0.0"),
			TarballURL: github.Ptr("https://github.com/owner/repo/tarball/v1.0.0"),
		},
		{
			Name: github.Ptr("v0.9.0"),
			Commit: &github.Commit{
				SHA: github.Ptr("v0.9.0-tag-sha"),
				URL: github.Ptr("https://api.github.com/repos/owner/repo/commits/def456"),
			},
			ZipballURL: github.Ptr("https://github.com/owner/repo/zipball/v0.9.0"),
			TarballURL: github.Ptr("https://github.com/owner/repo/tarball/v0.9.0"),
		},
	}

	tests := []struct {
		name           string
		mockedClient   *http.Client
		requestArgs    map[string]any
		expectError    bool
		expectedTags   []*github.RepositoryTag
		expectedErrMsg string
	}{
		{
			name: "successful tags list",
			mockedClient: NewMockedHTTPClient(
				WithRequestMatchHandler(
					GetReposTagsByOwnerByRepo,
					expectPath(
						t,
						"/repos/owner/repo/tags",
					).andThen(
						mockResponse(t, http.StatusOK, mockTags),
					),
				),
			),
			requestArgs: map[string]any{
				"owner": "owner",
				"repo":  "repo",
			},
			expectError:  false,
			expectedTags: mockTags,
		},
		{
			name: "list tags fails",
			mockedClient: NewMockedHTTPClient(
				WithRequestMatchHandler(
					GetReposTagsByOwnerByRepo,
					http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
						w.WriteHeader(http.StatusInternalServerError)
						_, _ = w.Write([]byte(`{"message": "Internal Server Error"}`))
					}),
				),
			),
			requestArgs: map[string]any{
				"owner": "owner",
				"repo":  "repo",
			},
			expectError:    true,
			expectedErrMsg: "failed to list tags",
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			// Setup client with mock
			client := mustNewGHClient(t, 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)
				require.True(t, result.IsError)
				errorContent := getErrorResult(t, result)
				assert.Contains(t, errorContent.Text, tc.expectedErrMsg)
				return
			}

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

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

			// Parse and verify the result
			var returnedTags []MinimalTag
			err = json.Unmarshal([]byte(textContent.Text), &returnedTags)
			require.NoError(t, err)

			// Verify each tag
			require.Equal(t, len(tc.expectedTags), len(returnedTags))
			for i, expectedTag := range tc.expectedTags {
				assert.Equal(t, *expectedTag.Name, returnedTags[i].Name)
				assert.Equal(t, *expectedTag.Commit.SHA, returnedTags[i].SHA)
			}
		})
	}
}

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

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

	assert.Equal(t, "get_tag", tool.Name)
	assert.NotEmpty(t, tool.Description)
	assert.Contains(t, schema.Properties, "owner")
	assert.Contains(t, schema.Properties, "repo")
	assert.Contains(t, schema.Properties, "tag")
	assert.ElementsMatch(t, schema.Required, []string{"owner", "repo", "tag"})

	mockAnnotatedTagRef := &github.Reference{
		Ref: github.Ptr("refs/tags/v1.0.0"),
		Object: &github.GitObject{
			Type: github.Ptr("tag"),
			SHA:  github.Ptr("v1.0.0-tag-sha"),
		},
	}

	mockLightweightTagRef := &github.Reference{
		Ref: github.Ptr("refs/tags/v1.0.1"),
		Object: &github.GitObject{
			Type: github.Ptr("commit"),
			SHA:  github.Ptr("abc123"),
		},
	}

	mockTagObj := &github.Tag{
		SHA:     github.Ptr("v1.0.0-tag-sha"),
		Tag:     github.Ptr("v1.0.0"),
		Message: github.Ptr("Release v1.0.0"),
		Object: &github.GitObject{
			Type: github.Ptr("commit"),
			SHA:  github.Ptr("abc123"),
		},
	}

	tests := []struct {
		name           string
		mockedClient   *http.Client
		requestArgs    map[string]any
		expectError    bool
		expectedTag    *github.Tag
		expectedRef    *github.Reference
		expectedErrMsg string
	}{
		{
			name: "successful tag retrieval",
			mockedClient: NewMockedHTTPClient(
				WithRequestMatchHandler(
					GetReposGitRefByOwnerByRepoByRef,
					expectPath(
						t,
						"/repos/owner/repo/git/ref/tags/v1.0.0",
					).andThen(
						mockResponse(t, http.StatusOK, mockAnnotatedTagRef),
					),
				),
				WithRequestMatchHandler(
					GetReposGitTagsByOwnerByRepoByTagSHA,
					expectPath(
						t,
						"/repos/owner/repo/git/tags/v1.0.0-tag-sha",
					).andThen(
						mockResponse(t, http.StatusOK, mockTagObj),
					),
				),
			),
			requestArgs: map[string]any{
				"owner": "owner",
				"repo":  "repo",
				"tag":   "v1.0.0",
			},
			expectError: false,
			expectedTag: mockTagObj,
		},
		{
			name: "tag reference not found",
			mockedClient: NewMockedHTTPClient(
				WithRequestMatchHandler(
					GetReposGitRefByOwnerByRepoByRef,
					http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
						w.WriteHeader(http.StatusNotFound)
						_, _ = w.Write([]byte(`{"message": "Reference does not exist"}`))
					}),
				),
			),
			requestArgs: map[string]any{
				"owner": "owner",
				"repo":  "repo",
				"tag":   "v1.0.0",
			},
			expectError:    true,
			expectedErrMsg: "failed to get tag reference",
		},
		{
			name: "tag object not found",
			mockedClient: NewMockedHTTPClient(
				WithRequestMatch(
					GetReposGitRefByOwnerByRepoByRef,
					mockAnnotatedTagRef,
				),
				WithRequestMatchHandler(
					GetReposGitTagsByOwnerByRepoByTagSHA,
					http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
						w.WriteHeader(http.StatusNotFound)
						_, _ = w.Write([]byte(`{"message": "Tag object does not exist"}`))
					}),
				),
			),
			requestArgs: map[string]any{
				"owner": "owner",
				"repo":  "repo",
				"tag":   "v1.0.0",
			},
			expectError:    true,
			expectedErrMsg: "failed to get tag object",
		},
		{
			name: "successful lightweight tag retrieval",
			mockedClient: NewMockedHTTPClient(
				WithRequestMatchHandler(
					GetReposGitRefByOwnerByRepoByRef,
					expectPath(
						t,
						"/repos/owner/repo/git/ref/tags/v1.0.1",
					).andThen(
						mockResponse(t, http.StatusOK, mockLightweightTagRef),
					),
				),
			),
			requestArgs: map[string]any{
				"owner": "owner",
				"repo":  "repo",
				"tag":   "v1.0.1",
			},
			expectError: false,
			expectedRef: mockLightweightTagRef,
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			// Setup client with mock
			client := mustNewGHClient(t, 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)
				require.True(t, result.IsError)
				errorContent := getErrorResult(t, result)
				assert.Contains(t, errorContent.Text, tc.expectedErrMsg)
				return
			}

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

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

			// Parse and verify the result - annotated tag (full tag object)
			if tc.expectedTag != nil {
				var returnedTag github.Tag
				err = json.Unmarshal([]byte(textContent.Text), &returnedTag)
				require.NoError(t, err)

				assert.Equal(t, tc.expectedTag.GetSHA(), returnedTag.GetSHA())
				assert.Equal(t, tc.expectedTag.GetTag(), returnedTag.GetTag())
				assert.Equal(t, tc.expectedTag.GetMessage(), returnedTag.GetMessage())
				assert.Equal(t, tc.expectedTag.Object.GetType(), returnedTag.Object.GetType())
				assert.Equal(t, tc.expectedTag.Object.GetSHA(), returnedTag.Object.GetSHA())
			}

			// Parse and verify the result - lightweight tag (reference only)
			if tc.expectedRef != nil {
				var returnedRef github.Reference
				err = json.Unmarshal([]byte(textContent.Text), &returnedRef)
				require.NoError(t, err)

				assert.Equal(t, tc.expectedRef.GetRef(), returnedRef.GetRef())
				assert.Equal(t, tc.expectedRef.Object.GetType(), returnedRef.Object.GetType())
				assert.Equal(t, tc.expectedRef.Object.GetSHA(), returnedRef.Object.GetSHA())
			}
		})
	}
}

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

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

	assert.Equal(t, "list_releases", tool.Name)
	assert.NotEmpty(t, tool.Description)
	assert.Contains(t, schema.Properties, "owner")
	assert.Contains(t, schema.Properties, "repo")
	assert.Contains(t, schema.Properties, "fields")
	assert.ElementsMatch(t, schema.Required, []string{"owner", "repo"})

	mockReleases := []*github.RepositoryRelease{
		{
			ID:      1,
			TagName: "v1.0.0",
			Name:    github.Ptr("First Release"),
		},
		{
			ID:      2,
			TagName: "v0.9.0",
			Name:    github.Ptr("Beta Release"),
		},
	}

	tests := []struct {
		name           string
		mockedClient   *http.Client
		requestArgs    map[string]any
		expectError    bool
		expectedResult []*github.RepositoryRelease
		expectedErrMsg string
	}{
		{
			name: "successful releases list",
			mockedClient: NewMockedHTTPClient(
				WithRequestMatch(
					GetReposReleasesByOwnerByRepo,
					mockReleases,
				),
			),
			requestArgs: map[string]any{
				"owner": "owner",
				"repo":  "repo",
			},
			expectError:    false,
			expectedResult: mockReleases,
		},
		{
			name: "releases list fails",
			mockedClient: NewMockedHTTPClient(
				WithRequestMatchHandler(
					GetReposReleasesByOwnerByRepo,
					http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
						w.WriteHeader(http.StatusNotFound)
						_, _ = w.Write([]byte(`{"message": "Not Found"}`))
					}),
				),
			),
			requestArgs: map[string]any{
				"owner": "owner",
				"repo":  "repo",
			},
			expectError:    true,
			expectedErrMsg: "failed to list releases",
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			client := mustNewGHClient(t, tc.mockedClient)
			deps := BaseDeps{
				Client: client,
			}
			handler := serverTool.Handler(deps)
			request := createMCPRequest(tc.requestArgs)
			result, err := handler(ContextWithDeps(context.Background(), deps), &request)

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

			require.NoError(t, err)
			textContent := getTextResult(t, result)
			var returnedReleases []MinimalRelease
			err = json.Unmarshal([]byte(textContent.Text), &returnedReleases)
			require.NoError(t, err)
			assert.Len(t, returnedReleases, len(tc.expectedResult))
			for i := range returnedReleases {
				assert.Equal(t, tc.expectedResult[i].TagName, returnedReleases[i].TagName)
			}
		})
	}
}

func Test_GetLatestRelease(t *testing.T) {
	serverTool := GetLatestRelease(translations.NullTranslationHelper)
	tool := serverTool.Tool
	require.NoError(t, toolsnaps.Test(tool.Name, tool))

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

	assert.Equal(t, "get_latest_release", tool.Name)
	assert.NotEmpty(t, tool.Description)
	assert.Contains(t, schema.Properties, "owner")
	assert.Contains(t, schema.Properties, "repo")
	assert.ElementsMatch(t, schema.Required, []string{"owner", "repo"})

	mockRelease := &github.RepositoryRelease{
		ID:      1,
		TagName: "v1.0.0",
		Name:    github.Ptr("First Release"),
	}

	tests := []struct {
		name           string
		mockedClient   *http.Client
		requestArgs    map[string]any
		expectError    bool
		expectedResult *github.RepositoryRelease
		expectedErrMsg string
	}{
		{
			name: "successful latest release fetch",
			mockedClient: NewMockedHTTPClient(
				WithRequestMatch(
					GetReposReleasesLatestByOwnerByRepo,
					mockRelease,
				),
			),
			requestArgs: map[string]any{
				"owner": "owner",
				"repo":  "repo",
			},
			expectError:    false,
			expectedResult: mockRelease,
		},
		{
			name: "latest release fetch fails",
			mockedClient: NewMockedHTTPClient(
				WithRequestMatchHandler(
					GetReposReleasesLatestByOwnerByRepo,
					http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
						w.WriteHeader(http.StatusNotFound)
						_, _ = w.Write([]byte(`{"message": "Not Found"}`))
					}),
				),
			),
			requestArgs: map[string]any{
				"owner": "owner",
				"repo":  "repo",
			},
			expectError:    true,
			expectedErrMsg: "failed to get latest release",
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			client := mustNewGHClient(t, tc.mockedClient)
			deps := BaseDeps{
				Client: client,
			}
			handler := serverTool.Handler(deps)
			request := createMCPRequest(tc.requestArgs)
			result, err := handler(ContextWithDeps(context.Background(), deps), &request)

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

			require.NoError(t, err)
			textContent := getTextResult(t, result)
			var returnedRelease github.RepositoryRelease
			err = json.Unmarshal([]byte(textContent.Text), &returnedRelease)
			require.NoError(t, err)
			assert.Equal(t, tc.expectedResult.TagName, returnedRelease.TagName)
		})
	}
}

func Test_GetReleaseByTag(t *testing.T) {
	serverTool := GetReleaseByTag(translations.NullTranslationHelper)
	tool := serverTool.Tool
	require.NoError(t, toolsnaps.Test(tool.Name, tool))

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

	assert.Equal(t, "get_release_by_tag", tool.Name)
	assert.NotEmpty(t, tool.Description)
	assert.Contains(t, schema.Properties, "owner")
	assert.Contains(t, schema.Properties, "repo")
	assert.Contains(t, schema.Properties, "tag")
	assert.ElementsMatch(t, schema.Required, []string{"owner", "repo", "tag"})

	mockRelease := &github.RepositoryRelease{
		ID:      1,
		TagName: "v1.0.0",
		Name:    github.Ptr("Release v1.0.0"),
		Body:    github.Ptr("This is the first stable release."),
		Assets: []*github.ReleaseAsset{
			{
				ID:   github.Ptr(int64(1)),
				Name: github.Ptr("release-v1.0.0.tar.gz"),
			},
		},
	}

	tests := []struct {
		name           string
		mockedClient   *http.Client
		requestArgs    map[string]any
		expectError    bool
		expectedResult *github.RepositoryRelease
		expectedErrMsg string
	}{
		{
			name: "successful release by tag fetch",
			mockedClient: NewMockedHTTPClient(
				WithRequestMatch(
					GetReposReleasesTagsByOwnerByRepoByTag,
					mockRelease,
				),
			),
			requestArgs: map[string]any{
				"owner": "owner",
				"repo":  "repo",
				"tag":   "v1.0.0",
			},
			expectError:    false,
			expectedResult: mockRelease,
		},
		{
			name:         "missing owner parameter",
			mockedClient: NewMockedHTTPClient(),
			requestArgs: map[string]any{
				"repo": "repo",
				"tag":  "v1.0.0",
			},
			expectError:    false, // Returns tool error, not Go error
			expectedErrMsg: "missing required parameter: owner",
		},
		{
			name:         "missing repo parameter",
			mockedClient: NewMockedHTTPClient(),
			requestArgs: map[string]any{
				"owner": "owner",
				"tag":   "v1.0.0",
			},
			expectError:    false, // Returns tool error, not Go error
			expectedErrMsg: "missing required parameter: repo",
		},
		{
			name:         "missing tag parameter",
			mockedClient: NewMockedHTTPClient(),
			requestArgs: map[string]any{
				"owner": "owner",
				"repo":  "repo",
			},
			expectError:    false, // Returns tool error, not Go error
			expectedErrMsg: "missing required parameter: tag",
		},
		{
			name: "release by tag not found",
			mockedClient: NewMockedHTTPClient(
				WithRequestMatchHandler(
					GetReposReleasesTagsByOwnerByRepoByTag,
					http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
						w.WriteHeader(http.StatusNotFound)
						_, _ = w.Write([]byte(`{"message": "Not Found"}`))
					}),
				),
			),
			requestArgs: map[string]any{
				"owner": "owner",
				"repo":  "repo",
				"tag":   "v999.0.0",
			},
			expectError:    false, // API errors return tool errors, not Go errors
			expectedErrMsg: "failed to get release by tag: v999.0.0",
		},
		{
			name: "server error",
			mockedClient: NewMockedHTTPClient(
				WithRequestMatchHandler(
					GetReposReleasesTagsByOwnerByRepoByTag,
					http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
						w.WriteHeader(http.StatusInternalServerError)
						_, _ = w.Write([]byte(`{"message": "Internal Server Error"}`))
					}),
				),
			),
			requestArgs: map[string]any{
				"owner": "owner",
				"repo":  "repo",
				"tag":   "v1.0.0",
			},
			expectError:    false, // API errors return tool errors, not Go errors
			expectedErrMsg: "failed to get release by tag: v1.0.0",
		},
	}

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

			request := createMCPRequest(tc.requestArgs)

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

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

			require.NoError(t, err)

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

			require.False(t, result.IsError)

			textContent := getTextResult(t, result)

			var returnedRelease github.RepositoryRelease
			err = json.Unmarshal([]byte(textContent.Text), &returnedRelease)
			require.NoError(t, err)

			assert.Equal(t, tc.expectedResult.ID, returnedRelease.ID)
			assert.Equal(t, tc.expectedResult.TagName, returnedRelease.TagName)
			assert.Equal(t, *tc.expectedResult.Name, *returnedRelease.Name)
			if tc.expectedResult.Body != nil {
				assert.Equal(t, *tc.expectedResult.Body, *returnedRelease.Body)
			}
			if len(tc.expectedResult.Assets) > 0 {
				require.Len(t, returnedRelease.Assets, len(tc.expectedResult.Assets))
				assert.Equal(t, *tc.expectedResult.Assets[0].Name, *returnedRelease.Assets[0].Name)
			}
		})
	}
}

// Test_GetReleaseByTag_IFC_FeatureFlag verifies the IFC label on
// get_release_by_tag. The label is only present when the ifc_labels flag is
// enabled, and confidentiality is public only for a non-draft release on a
// public repo. A draft release is visible only to push-access users, so even
// on a public repo it must be labeled private. Guards against the same
// under-classification fixed for repository security advisories.
func Test_GetReleaseByTag_IFC_FeatureFlag(t *testing.T) {
	t.Parallel()

	serverTool := GetReleaseByTag(translations.NullTranslationHelper)

	makeRelease := func(draft bool) *github.RepositoryRelease {
		return &github.RepositoryRelease{
			ID:      1,
			TagName: "v1.0.0",
			Name:    github.Ptr("v1.0.0"),
			Draft:   draft,
		}
	}

	makeMockClient := func(isPrivate bool, release *github.RepositoryRelease) *http.Client {
		return MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
			GetReposReleasesTagsByOwnerByRepoByTag: mockResponse(t, http.StatusOK, release),
			GetReposByOwnerByRepo: mockResponse(t, http.StatusOK, map[string]any{
				"name":    "repo",
				"private": isPrivate,
			}),
		})
	}

	reqParams := map[string]any{"owner": "owner", "repo": "repo", "tag": "v1.0.0"}

	readIFC := func(t *testing.T, result *mcp.CallToolResult) (map[string]any, bool) {
		t.Helper()
		if result.Meta == nil {
			return nil, false
		}
		label, ok := result.Meta["ifc"]
		if !ok {
			return nil, false
		}
		labelJSON, err := json.Marshal(label)
		require.NoError(t, err)
		var labelMap map[string]any
		require.NoError(t, json.Unmarshal(labelJSON, &labelMap))
		return labelMap, true
	}

	t.Run("feature flag disabled omits ifc label", func(t *testing.T) {
		t.Parallel()
		deps := BaseDeps{Client: mustNewGHClient(t, makeMockClient(false, makeRelease(false)))}
		handler := serverTool.Handler(deps)

		request := createMCPRequest(reqParams)
		result, err := handler(ContextWithDeps(context.Background(), deps), &request)
		require.NoError(t, err)
		require.False(t, result.IsError)
		assert.Nil(t, result.Meta)
	})

	t.Run("public repo with published release is public", func(t *testing.T) {
		t.Parallel()
		deps := BaseDeps{
			Client:         mustNewGHClient(t, makeMockClient(false, makeRelease(false))),
			featureChecker: featureCheckerFor(FeatureFlagIFCLabels),
		}
		handler := serverTool.Handler(deps)

		request := createMCPRequest(reqParams)
		result, err := handler(ContextWithDeps(context.Background(), deps), &request)
		require.NoError(t, err)
		require.False(t, result.IsError)

		label, ok := readIFC(t, result)
		require.True(t, ok)
		assert.Equal(t, "trusted", label["integrity"])
		assert.Equal(t, "public", label["confidentiality"])
	})

	t.Run("public repo with draft release is private", func(t *testing.T) {
		t.Parallel()
		// Reviewer-class scenario: a draft release on a public repo is not
		// world-readable, so the label must not be public.
		deps := BaseDeps{
			Client:         mustNewGHClient(t, makeMockClient(false, makeRelease(true))),
			featureChecker: featureCheckerFor(FeatureFlagIFCLabels),
		}
		handler := serverTool.Handler(deps)

		request := createMCPRequest(reqParams)
		result, err := handler(ContextWithDeps(context.Background(), deps), &request)
		require.NoError(t, err)
		require.False(t, result.IsError)

		label, ok := readIFC(t, result)
		require.True(t, ok)
		assert.Equal(t, "trusted", label["integrity"])
		assert.Equal(t, "private", label["confidentiality"], "draft release on public repo must be private")
	})
}

func Test_looksLikeSHA(t *testing.T) {
	tests := []struct {
		name     string
		input    string
		expected bool
	}{
		{
			name:     "full 40-character SHA",
			input:    "abc123def456abc123def456abc123def456abc1",
			expected: true,
		},
		{
			name:     "too short",
			input:    "abc123def456abc123def45",
			expected: false,
		},
		{
			name:     "too long - 41 characters",
			input:    "abc123def456abc123def456abc123def456abc12",
			expected: false,
		},
		{
			name:     "contains invalid character - space",
			input:    "abc123def456abc123def456 bc123def456abc1",
			expected: false,
		},
		{
			name:     "contains invalid character - dash",
			input:    "abc123def456abc123d-f456abc123def456abc1",
			expected: false,
		},
		{
			name:     "contains invalid character - g",
			input:    "abc123def456gbc123def456abc123def456abc1",
			expected: false,
		},
		{
			name:     "branch name with slash",
			input:    "feature/branch",
			expected: false,
		},
		{
			name:     "empty string",
			input:    "",
			expected: false,
		},
		{
			name:     "all zeros SHA",
			input:    "0000000000000000000000000000000000000000",
			expected: true,
		},
		{
			name:     "all f's SHA",
			input:    "ffffffffffffffffffffffffffffffffffffffff",
			expected: true,
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			result := looksLikeSHA(tc.input)
			assert.Equal(t, tc.expected, result)
		})
	}
}

func Test_filterPaths(t *testing.T) {
	tests := []struct {
		name       string
		tree       []*github.TreeEntry
		path       string
		maxResults int
		expected   []string
	}{
		{
			name: "file name",
			tree: []*github.TreeEntry{
				{Path: github.Ptr("folder/foo.txt"), Type: github.Ptr("blob")},
				{Path: github.Ptr("bar.txt"), Type: github.Ptr("blob")},
				{Path: github.Ptr("nested/folder/foo.txt"), Type: github.Ptr("blob")},
				{Path: github.Ptr("nested/folder/baz.txt"), Type: github.Ptr("blob")},
			},
			path:       "foo.txt",
			maxResults: -1,
			expected:   []string{"folder/foo.txt", "nested/folder/foo.txt"},
		},
		{
			name: "dir name",
			tree: []*github.TreeEntry{
				{Path: github.Ptr("folder"), Type: github.Ptr("tree")},
				{Path: github.Ptr("bar.txt"), Type: github.Ptr("blob")},
				{Path: github.Ptr("nested/folder"), Type: github.Ptr("tree")},
				{Path: github.Ptr("nested/folder/baz.txt"), Type: github.Ptr("blob")},
			},
			path:       "folder/",
			maxResults: -1,
			expected:   []string{"folder/", "nested/folder/"},
		},
		{
			name: "dir and file match",
			tree: []*github.TreeEntry{
				{Path: github.Ptr("name"), Type: github.Ptr("tree")},
				{Path: github.Ptr("name"), Type: github.Ptr("blob")},
			},
			path:       "name", // No trailing slash can match both files and directories
			maxResults: -1,
			expected:   []string{"name/", "name"},
		},
		{
			name: "dir only match",
			tree: []*github.TreeEntry{
				{Path: github.Ptr("name"), Type: github.Ptr("tree")},
				{Path: github.Ptr("name"), Type: github.Ptr("blob")},
			},
			path:       "name/", // Trialing slash ensures only directories are matched
			maxResults: -1,
			expected:   []string{"name/"},
		},
		{
			name: "max results limit 2",
			tree: []*github.TreeEntry{
				{Path: github.Ptr("folder"), Type: github.Ptr("tree")},
				{Path: github.Ptr("nested/folder"), Type: github.Ptr("tree")},
				{Path: github.Ptr("nested/nested/folder"), Type: github.Ptr("tree")},
			},
			path:       "folder/",
			maxResults: 2,
			expected:   []string{"folder/", "nested/folder/"},
		},
		{
			name: "max results limit 1",
			tree: []*github.TreeEntry{
				{Path: github.Ptr("folder"), Type: github.Ptr("tree")},
				{Path: github.Ptr("nested/folder"), Type: github.Ptr("tree")},
				{Path: github.Ptr("nested/nested/folder"), Type: github.Ptr("tree")},
			},
			path:       "folder/",
			maxResults: 1,
			expected:   []string{"folder/"},
		},
		{
			name: "max results limit 0",
			tree: []*github.TreeEntry{
				{Path: github.Ptr("folder"), Type: github.Ptr("tree")},
				{Path: github.Ptr("nested/folder"), Type: github.Ptr("tree")},
				{Path: github.Ptr("nested/nested/folder"), Type: github.Ptr("tree")},
			},
			path:       "folder/",
			maxResults: 0,
			expected:   []string{},
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			result := filterPaths(tc.tree, tc.path, tc.maxResults)
			assert.Equal(t, tc.expected, result)
		})
	}
}

func Test_resolveGitReference(t *testing.T) {
	ctx := context.Background()
	owner := "owner"
	repo := "repo"

	tests := []struct {
		name           string
		ref            string
		sha            string
		mockSetup      func() *http.Client
		expectedOutput *raw.ContentOpts
		expectError    bool
		errorContains  string
	}{
		{
			name: "sha takes precedence over ref",
			ref:  "refs/heads/main",
			sha:  "123sha456",
			mockSetup: func() *http.Client {
				// No API calls should be made when SHA is provided
				return NewMockedHTTPClient()
			},
			expectedOutput: &raw.ContentOpts{
				SHA: "123sha456",
			},
			expectError: false,
		},
		{
			name: "use default branch if ref and sha both empty",
			ref:  "",
			sha:  "",
			mockSetup: func() *http.Client {
				return NewMockedHTTPClient(
					WithRequestMatchHandler(
						GetReposByOwnerByRepo,
						http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
							w.WriteHeader(http.StatusOK)
							_, _ = w.Write([]byte(`{"name": "repo", "default_branch": "main"}`))
						}),
					),
					WithRequestMatchHandler(
						GetReposGitRefByOwnerByRepoByRef,
						http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
							assert.Contains(t, r.URL.Path, "/git/ref/heads/main")
							w.WriteHeader(http.StatusOK)
							_, _ = w.Write([]byte(`{"ref": "refs/heads/main", "object": {"sha": "main-sha"}}`))
						}),
					),
				)
			},
			expectedOutput: &raw.ContentOpts{
				Ref: "refs/heads/main",
				SHA: "main-sha",
			},
			expectError: false,
		},
		{
			name: "fully qualified ref passed through unchanged",
			ref:  "refs/heads/feature-branch",
			sha:  "",
			mockSetup: func() *http.Client {
				return NewMockedHTTPClient(
					WithRequestMatchHandler(
						GetReposGitRefByOwnerByRepoByRef,
						http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
							assert.Contains(t, r.URL.Path, "/git/ref/heads/feature-branch")
							w.WriteHeader(http.StatusOK)
							_, _ = w.Write([]byte(`{"ref": "refs/heads/feature-branch", "object": {"sha": "feature-sha"}}`))
						}),
					),
				)
			},
			expectedOutput: &raw.ContentOpts{
				Ref: "refs/heads/feature-branch",
				SHA: "feature-sha",
			},
			expectError: false,
		},
		{
			name: "short branch name resolves to refs/heads/",
			ref:  "main",
			sha:  "",
			mockSetup: func() *http.Client {
				return NewMockedHTTPClient(
					WithRequestMatchHandler(
						GetReposGitRefByOwnerByRepoByRef,
						http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
							if strings.Contains(r.URL.Path, "/git/ref/heads/main") {
								w.WriteHeader(http.StatusOK)
								_, _ = w.Write([]byte(`{"ref": "refs/heads/main", "object": {"sha": "main-sha"}}`))
							} else {
								t.Errorf("Unexpected path: %s", r.URL.Path)
								w.WriteHeader(http.StatusNotFound)
							}
						}),
					),
				)
			},
			expectedOutput: &raw.ContentOpts{
				Ref: "refs/heads/main",
				SHA: "main-sha",
			},
			expectError: false,
		},
		{
			name: "short tag name falls back to refs/tags/ when branch not found",
			ref:  "v1.0.0",
			sha:  "",
			mockSetup: func() *http.Client {
				return NewMockedHTTPClient(
					WithRequestMatchHandler(
						GetReposGitRefByOwnerByRepoByRef,
						http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
							switch {
							case strings.Contains(r.URL.Path, "/git/ref/heads/v1.0.0"):
								w.WriteHeader(http.StatusNotFound)
								_, _ = w.Write([]byte(`{"message": "Not Found"}`))
							case strings.Contains(r.URL.Path, "/git/ref/tags/v1.0.0"):
								w.WriteHeader(http.StatusOK)
								_, _ = w.Write([]byte(`{"ref": "refs/tags/v1.0.0", "object": {"sha": "tag-sha"}}`))
							default:
								t.Errorf("Unexpected path: %s", r.URL.Path)
								w.WriteHeader(http.StatusNotFound)
							}
						}),
					),
				)
			},
			expectedOutput: &raw.ContentOpts{
				Ref: "refs/tags/v1.0.0",
				SHA: "tag-sha",
			},
			expectError: false,
		},
		{
			name: "heads/ prefix gets refs/ prepended",
			ref:  "heads/feature-branch",
			sha:  "",
			mockSetup: func() *http.Client {
				return NewMockedHTTPClient(
					WithRequestMatchHandler(
						GetReposGitRefByOwnerByRepoByRef,
						http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
							assert.Contains(t, r.URL.Path, "/git/ref/heads/feature-branch")
							w.WriteHeader(http.StatusOK)
							_, _ = w.Write([]byte(`{"ref": "refs/heads/feature-branch", "object": {"sha": "feature-sha"}}`))
						}),
					),
				)
			},
			expectedOutput: &raw.ContentOpts{
				Ref: "refs/heads/feature-branch",
				SHA: "feature-sha",
			},
			expectError: false,
		},
		{
			name: "tags/ prefix gets refs/ prepended",
			ref:  "tags/v1.0.0",
			sha:  "",
			mockSetup: func() *http.Client {
				return NewMockedHTTPClient(
					WithRequestMatchHandler(
						GetReposGitRefByOwnerByRepoByRef,
						http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
							assert.Contains(t, r.URL.Path, "/git/ref/tags/v1.0.0")
							w.WriteHeader(http.StatusOK)
							_, _ = w.Write([]byte(`{"ref": "refs/tags/v1.0.0", "object": {"sha": "tag-sha"}}`))
						}),
					),
				)
			},
			expectedOutput: &raw.ContentOpts{
				Ref: "refs/tags/v1.0.0",
				SHA: "tag-sha",
			},
			expectError: false,
		},
		{
			name: "invalid short name that doesn't exist as branch or tag",
			ref:  "nonexistent",
			sha:  "",
			mockSetup: func() *http.Client {
				return NewMockedHTTPClient(
					WithRequestMatchHandler(
						GetReposGitRefByOwnerByRepoByRef,
						http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
							// Both branch and tag attempts should return 404
							w.WriteHeader(http.StatusNotFound)
							_, _ = w.Write([]byte(`{"message": "Not Found"}`))
						}),
					),
				)
			},
			expectError:   true,
			errorContains: "could not resolve ref \"nonexistent\" as a branch or a tag",
		},
		{
			name: "fully qualified pull request ref",
			ref:  "refs/pull/123/head",
			sha:  "",
			mockSetup: func() *http.Client {
				return NewMockedHTTPClient(
					WithRequestMatchHandler(
						GetReposGitRefByOwnerByRepoByRef,
						http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
							assert.Contains(t, r.URL.Path, "/git/ref/pull/123/head")
							w.WriteHeader(http.StatusOK)
							_, _ = w.Write([]byte(`{"ref": "refs/pull/123/head", "object": {"sha": "pr-sha"}}`))
						}),
					),
				)
			},
			expectedOutput: &raw.ContentOpts{
				Ref: "refs/pull/123/head",
				SHA: "pr-sha",
			},
			expectError: false,
		},
		{
			name: "ref looks like full SHA with empty sha parameter",
			ref:  "abc123def456abc123def456abc123def456abc1",
			sha:  "",
			mockSetup: func() *http.Client {
				// No API calls should be made when ref looks like SHA
				return NewMockedHTTPClient()
			},
			expectedOutput: &raw.ContentOpts{
				SHA: "abc123def456abc123def456abc123def456abc1",
			},
			expectError: false,
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			// Setup client with mock
			client := mustNewGHClient(t, tc.mockSetup())
			opts, _, err := resolveGitReference(ctx, client, owner, repo, tc.ref, tc.sha)

			if tc.expectError {
				require.Error(t, err)
				if tc.errorContains != "" {
					assert.Contains(t, err.Error(), tc.errorContains)
				}
				return
			}

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

			if tc.expectedOutput.SHA != "" {
				assert.Equal(t, tc.expectedOutput.SHA, opts.SHA)
			}
			if tc.expectedOutput.Ref != "" {
				assert.Equal(t, tc.expectedOutput.Ref, opts.Ref)
			}
		})
	}
}

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

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

	assert.Equal(t, "list_starred_repositories", tool.Name)
	assert.NotEmpty(t, tool.Description)
	assert.Contains(t, schema.Properties, "username")
	assert.Contains(t, schema.Properties, "sort")
	assert.Contains(t, schema.Properties, "direction")
	assert.Contains(t, schema.Properties, "page")
	assert.Contains(t, schema.Properties, "perPage")
	assert.Empty(t, schema.Required) // All parameters are optional

	// Setup mock starred repositories
	starredAt := time.Now().Add(-24 * time.Hour)
	updatedAt := time.Now().Add(-2 * time.Hour)
	mockStarredRepos := []*github.StarredRepository{
		{
			StarredAt: &github.Timestamp{Time: starredAt},
			Repository: &github.Repository{
				ID:              github.Ptr(int64(12345)),
				Name:            github.Ptr("awesome-repo"),
				FullName:        github.Ptr("owner/awesome-repo"),
				Description:     github.Ptr("An awesome repository"),
				HTMLURL:         github.Ptr("https://github.com/owner/awesome-repo"),
				Language:        github.Ptr("Go"),
				StargazersCount: github.Ptr(100),
				ForksCount:      github.Ptr(25),
				OpenIssuesCount: github.Ptr(5),
				UpdatedAt:       &github.Timestamp{Time: updatedAt},
				Private:         github.Ptr(false),
				Fork:            github.Ptr(false),
				Archived:        github.Ptr(false),
				DefaultBranch:   github.Ptr("main"),
			},
		},
		{
			StarredAt: &github.Timestamp{Time: starredAt.Add(-12 * time.Hour)},
			Repository: &github.Repository{
				ID:              github.Ptr(int64(67890)),
				Name:            github.Ptr("cool-project"),
				FullName:        github.Ptr("user/cool-project"),
				Description:     github.Ptr("A very cool project"),
				HTMLURL:         github.Ptr("https://github.com/user/cool-project"),
				Language:        github.Ptr("Python"),
				StargazersCount: github.Ptr(500),
				ForksCount:      github.Ptr(75),
				OpenIssuesCount: github.Ptr(10),
				UpdatedAt:       &github.Timestamp{Time: updatedAt.Add(-1 * time.Hour)},
				Private:         github.Ptr(false),
				Fork:            github.Ptr(true),
				Archived:        github.Ptr(false),
				DefaultBranch:   github.Ptr("master"),
			},
		},
	}

	tests := []struct {
		name           string
		mockedClient   *http.Client
		requestArgs    map[string]any
		expectError    bool
		expectedErrMsg string
		expectedCount  int
	}{
		{
			name: "successful list for authenticated user",
			mockedClient: NewMockedHTTPClient(
				WithRequestMatchHandler(
					GetUserStarred,
					http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
						w.WriteHeader(http.StatusOK)
						_, _ = w.Write(MustMarshal(mockStarredRepos))
					}),
				),
			),
			requestArgs:   map[string]any{},
			expectError:   false,
			expectedCount: 2,
		},
		{
			name: "successful list for specific user",
			mockedClient: NewMockedHTTPClient(
				WithRequestMatchHandler(
					GetUsersStarredByUsername,
					http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
						w.WriteHeader(http.StatusOK)
						_, _ = w.Write(MustMarshal(mockStarredRepos))
					}),
				),
			),
			requestArgs: map[string]any{
				"username": "testuser",
			},
			expectError:   false,
			expectedCount: 2,
		},
		{
			name: "list fails",
			mockedClient: NewMockedHTTPClient(
				WithRequestMatchHandler(
					GetUserStarred,
					http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
						w.WriteHeader(http.StatusNotFound)
						_, _ = w.Write([]byte(`{"message": "Not Found"}`))
					}),
				),
			),
			requestArgs:    map[string]any{},
			expectError:    true,
			expectedErrMsg: "failed to list starred repositories",
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			// Setup client with mock
			client := mustNewGHClient(t, 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.NotNil(t, result)
				textResult, ok := result.Content[0].(*mcp.TextContent)
				require.True(t, ok, "Expected text content")
				assert.Contains(t, textResult.Text, tc.expectedErrMsg)
			} else {
				require.NoError(t, err)
				require.NotNil(t, result)

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

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

				assert.Len(t, returnedRepos, tc.expectedCount)
				if tc.expectedCount > 0 {
					assert.Equal(t, "awesome-repo", returnedRepos[0].Name)
					assert.Equal(t, "owner/awesome-repo", returnedRepos[0].FullName)
				}
			}
		})
	}
}

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

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

	assert.Equal(t, "star_repository", tool.Name)
	assert.NotEmpty(t, tool.Description)
	assert.Contains(t, schema.Properties, "owner")
	assert.Contains(t, schema.Properties, "repo")
	assert.ElementsMatch(t, schema.Required, []string{"owner", "repo"})

	tests := []struct {
		name           string
		mockedClient   *http.Client
		requestArgs    map[string]any
		expectError    bool
		expectedErrMsg string
	}{
		{
			name: "successful star",
			mockedClient: NewMockedHTTPClient(
				WithRequestMatchHandler(
					PutUserStarredByOwnerByRepo,
					http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
						w.WriteHeader(http.StatusNoContent)
					}),
				),
			),
			requestArgs: map[string]any{
				"owner": "testowner",
				"repo":  "testrepo",
			},
			expectError: false,
		},
		{
			name: "star fails",
			mockedClient: NewMockedHTTPClient(
				WithRequestMatchHandler(
					PutUserStarredByOwnerByRepo,
					http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
						w.WriteHeader(http.StatusNotFound)
						_, _ = w.Write([]byte(`{"message": "Not Found"}`))
					}),
				),
			),
			requestArgs: map[string]any{
				"owner": "testowner",
				"repo":  "nonexistent",
			},
			expectError:    true,
			expectedErrMsg: "failed to star repository",
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			// Setup client with mock
			client := mustNewGHClient(t, 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.NotNil(t, result)
				textResult, ok := result.Content[0].(*mcp.TextContent)
				require.True(t, ok, "Expected text content")
				assert.Contains(t, textResult.Text, tc.expectedErrMsg)
			} else {
				require.NoError(t, err)
				require.NotNil(t, result)

				// Parse the result and get the text content
				textContent := getTextResult(t, result)
				assert.Contains(t, textContent.Text, "Successfully starred repository")
			}
		})
	}
}

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

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

	assert.Equal(t, "unstar_repository", tool.Name)
	assert.NotEmpty(t, tool.Description)
	assert.Contains(t, schema.Properties, "owner")
	assert.Contains(t, schema.Properties, "repo")
	assert.ElementsMatch(t, schema.Required, []string{"owner", "repo"})

	tests := []struct {
		name           string
		mockedClient   *http.Client
		requestArgs    map[string]any
		expectError    bool
		expectedErrMsg string
	}{
		{
			name: "successful unstar",
			mockedClient: NewMockedHTTPClient(
				WithRequestMatchHandler(
					DeleteUserStarredByOwnerByRepo,
					http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
						w.WriteHeader(http.StatusNoContent)
					}),
				),
			),
			requestArgs: map[string]any{
				"owner": "testowner",
				"repo":  "testrepo",
			},
			expectError: false,
		},
		{
			name: "unstar fails",
			mockedClient: NewMockedHTTPClient(
				WithRequestMatchHandler(
					DeleteUserStarredByOwnerByRepo,
					http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
						w.WriteHeader(http.StatusNotFound)
						_, _ = w.Write([]byte(`{"message": "Not Found"}`))
					}),
				),
			),
			requestArgs: map[string]any{
				"owner": "testowner",
				"repo":  "nonexistent",
			},
			expectError:    true,
			expectedErrMsg: "failed to unstar repository",
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			// Setup client with mock
			client := mustNewGHClient(t, 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.NotNil(t, result)
				textResult, ok := result.Content[0].(*mcp.TextContent)
				require.True(t, ok, "Expected text content")
				assert.Contains(t, textResult.Text, tc.expectedErrMsg)
			} else {
				require.NoError(t, err)
				require.NotNil(t, result)

				// Parse the result and get the text content
				textContent := getTextResult(t, result)
				assert.Contains(t, textContent.Text, "Successfully unstarred repository")
			}
		})
	}
}
func Test_GetFileBlame(t *testing.T) {
	// Verify tool definition once
	serverTool := GetFileBlame(translations.NullTranslationHelper)
	tool := serverTool.Tool
	require.NoError(t, toolsnaps.Test(tool.Name, tool))

	// get_file_blame is gated so it is not advertised unless the feature flag
	// (or insiders mode) opts it in.
	assert.Equal(t, FeatureFlagFileBlame, serverTool.FeatureFlagEnable, "get_file_blame must be gated behind the file_blame feature flag")

	schema, ok := tool.InputSchema.(*jsonschema.Schema)
	require.True(t, ok, "InputSchema should be *jsonschema.Schema")
	assert.Equal(t, "get_file_blame", tool.Name)
	assert.NotEmpty(t, tool.Description)
	for _, key := range []string{"owner", "repo", "path", "ref", "start_line", "end_line", "perPage", "after"} {
		assert.Contains(t, schema.Properties, key, "schema missing property %q", key)
	}
	assert.NotContains(t, schema.Properties, "page")
	assert.ElementsMatch(t, schema.Required, []string{"owner", "repo", "path"})
	require.NotNil(t, tool.Annotations)
	assert.True(t, tool.Annotations.ReadOnlyHint, "blame is read-only")

	// blameQueryShape is the GraphQL query shape used by all
	// network-touching subtests below. Defined once so changes to the wire
	// schema are made in a single place.
	type blameQueryShape = struct {
		Repository struct {
			DefaultBranchRef struct {
				Name githubv4.String
			}
			Object struct {
				Typename githubv4.String     `graphql:"__typename"`
				Commit   blameCommitFragment `graphql:"... on Commit"`
				Tag      struct {
					Target struct {
						Typename githubv4.String     `graphql:"__typename"`
						Commit   blameCommitFragment `graphql:"... on Commit"`
					}
				} `graphql:"... on Tag"`
			} `graphql:"object(expression: $ref)"`
		} `graphql:"repository(owner: $owner, name: $repo)"`
	}

	makeBlameVars := func(owner, repo, ref, path string) map[string]any {
		return map[string]any{
			"owner": githubv4.String(owner),
			"repo":  githubv4.String(repo),
			"ref":   githubv4.String(ref),
			"path":  githubv4.String(path),
		}
	}

	tests := []struct {
		name             string
		mockedClient     *http.Client
		requestArgs      map[string]any
		expectError      bool
		expectedErrMsg   string
		validateResponse func(t *testing.T, result string)
	}{
		{
			name: "successful blame using default branch (HEAD)",
			mockedClient: githubv4mock.NewMockedHTTPClient(
				githubv4mock.NewQueryMatcher(
					blameQueryShape{},
					makeBlameVars("testowner", "testrepo", "HEAD", "README.md"),
					githubv4mock.DataResponse(map[string]any{
						"repository": map[string]any{
							"defaultBranchRef": map[string]any{"name": "main"},
							"object": map[string]any{
								"__typename": "Commit",
								"blame": map[string]any{
									"ranges": []map[string]any{
										{
											"startingLine": 1, "endingLine": 5, "age": 2,
											"commit": map[string]any{
												"oid":           "abc123def456",
												"message":       "Initial commit\n\nLong body that should not appear in the response.",
												"committedDate": "2024-01-01T12:00:00Z",
												"author": map[string]any{
													"name": "John Doe", "email": "john@example.com",
													"user": map[string]any{"login": "johndoe", "url": "https://github.com/johndoe"},
												},
											},
										},
										{
											// Same commit as the first range -> must be deduplicated.
											"startingLine": 6, "endingLine": 7, "age": 2,
											"commit": map[string]any{
												"oid":           "abc123def456",
												"message":       "Initial commit\n\nLong body that should not appear in the response.",
												"committedDate": "2024-01-01T12:00:00Z",
												"author": map[string]any{
													"name": "John Doe", "email": "john@example.com",
													"user": map[string]any{"login": "johndoe", "url": "https://github.com/johndoe"},
												},
											},
										},
										{
											"startingLine": 8, "endingLine": 10, "age": 1,
											"commit": map[string]any{
												"oid":           "def456ghi789",
												"message":       "Update README",
												"committedDate": "2024-01-02T15:30:00Z",
												"author": map[string]any{
													"name": "Jane Smith", "email": "jane@example.com",
													"user": map[string]any{"login": "janesmith", "url": "https://github.com/janesmith"},
												},
											},
										},
									},
								},
							},
						},
					}),
				),
			),
			requestArgs: map[string]any{
				"owner": "testowner",
				"repo":  "testrepo",
				"path":  "README.md",
			},
			validateResponse: func(t *testing.T, result string) {
				var br BlameResult
				require.NoError(t, json.Unmarshal([]byte(result), &br))
				assert.Equal(t, "testowner/testrepo", br.Repository)
				assert.Equal(t, "README.md", br.Path)
				assert.Equal(t, "main", br.Ref, "ref should resolve to default branch name")
				assert.False(t, br.Truncated)
				assert.Equal(t, 3, br.TotalRanges)
				assert.False(t, br.PageInfo.HasNextPage)
				assert.False(t, br.PageInfo.HasPreviousPage)
				assert.NotEmpty(t, br.PageInfo.StartCursor)
				assert.NotEmpty(t, br.PageInfo.EndCursor)
				require.Len(t, br.Ranges, 3)
				// Commits map is deduplicated.
				require.Len(t, br.Commits, 2)
				require.Contains(t, br.Commits, "abc123def456")
				require.Contains(t, br.Commits, "def456ghi789")
				// Multi-line message must be reduced to its headline.
				assert.Equal(t, "Initial commit", br.Commits["abc123def456"].MessageHeadline)
				assert.NotContains(t, result, "Long body that should not appear")
				// Login/URL pointers populated.
				require.NotNil(t, br.Commits["abc123def456"].Author.Login)
				assert.Equal(t, "johndoe", *br.Commits["abc123def456"].Author.Login)
			},
		},
		{
			name: "successful blame with explicit ref",
			mockedClient: githubv4mock.NewMockedHTTPClient(
				githubv4mock.NewQueryMatcher(
					blameQueryShape{},
					makeBlameVars("testowner", "testrepo", "feature-branch", "src/main.go"),
					githubv4mock.DataResponse(map[string]any{
						"repository": map[string]any{
							"defaultBranchRef": map[string]any{"name": "main"},
							"object": map[string]any{
								"__typename": "Commit",
								"blame": map[string]any{
									"ranges": []map[string]any{
										{
											"startingLine": 1, "endingLine": 3, "age": 1,
											"commit": map[string]any{
												"oid":           "xyz789abc123",
												"message":       "Add main function",
												"committedDate": "2024-01-03T10:00:00Z",
												"author": map[string]any{
													"name": "Bob Developer", "email": "bob@example.com",
													"user": nil,
												},
											},
										},
									},
								},
							},
						},
					}),
				),
			),
			requestArgs: map[string]any{
				"owner": "testowner",
				"repo":  "testrepo",
				"path":  "src/main.go",
				"ref":   "feature-branch",
			},
			validateResponse: func(t *testing.T, result string) {
				var br BlameResult
				require.NoError(t, json.Unmarshal([]byte(result), &br))
				assert.Equal(t, "feature-branch", br.Ref, "explicit ref echoed back")
				require.Len(t, br.Ranges, 1)
				require.Contains(t, br.Commits, "xyz789abc123")
				assert.Nil(t, br.Commits["xyz789abc123"].Author.Login, "anonymous author has no login")
			},
		},
		{
			name: "successful blame with annotated tag ref",
			mockedClient: githubv4mock.NewMockedHTTPClient(
				githubv4mock.NewQueryMatcher(
					blameQueryShape{},
					makeBlameVars("testowner", "testrepo", "v1.0.0", "src/tagged.go"),
					githubv4mock.DataResponse(map[string]any{
						"repository": map[string]any{
							"defaultBranchRef": map[string]any{"name": "main"},
							"object": map[string]any{
								"__typename": "Tag",
								"target": map[string]any{
									"__typename": "Commit",
									"blame": map[string]any{
										"ranges": []map[string]any{
											{
												"startingLine": 1, "endingLine": 2, "age": 1,
												"commit": map[string]any{
													"oid":           "taggedcommit123",
													"message":       "Tagged release commit",
													"committedDate": "2024-01-04T10:00:00Z",
													"author":        map[string]any{"name": "Tag Author", "email": "tag@example.com", "user": nil},
												},
											},
										},
									},
								},
							},
						},
					}),
				),
			),
			requestArgs: map[string]any{
				"owner": "testowner",
				"repo":  "testrepo",
				"path":  "src/tagged.go",
				"ref":   "v1.0.0",
			},
			validateResponse: func(t *testing.T, result string) {
				var br BlameResult
				require.NoError(t, json.Unmarshal([]byte(result), &br))
				assert.Equal(t, "v1.0.0", br.Ref, "explicit annotated tag ref echoed back")
				require.Len(t, br.Ranges, 1)
				assert.Equal(t, "taggedcommit123", br.Ranges[0].CommitSHA)
				require.Contains(t, br.Commits, "taggedcommit123")
				assert.Equal(t, "Tagged release commit", br.Commits["taggedcommit123"].MessageHeadline,
					"commit metadata threads through the Tag.Target.Commit path")
				assert.Equal(t, "Tag Author", br.Commits["taggedcommit123"].Author.Name)
			},
		},
		{
			name: "empty blame ranges",
			mockedClient: githubv4mock.NewMockedHTTPClient(
				githubv4mock.NewQueryMatcher(
					blameQueryShape{},
					makeBlameVars("testowner", "testrepo", "HEAD", "EMPTY.md"),
					githubv4mock.DataResponse(map[string]any{
						"repository": map[string]any{
							"defaultBranchRef": map[string]any{"name": "main"},
							"object": map[string]any{
								"__typename": "Commit",
								"blame":      map[string]any{"ranges": []map[string]any{}},
							},
						},
					}),
				),
			),
			requestArgs: map[string]any{
				"owner": "testowner",
				"repo":  "testrepo",
				"path":  "EMPTY.md",
			},
			validateResponse: func(t *testing.T, result string) {
				var br BlameResult
				require.NoError(t, json.Unmarshal([]byte(result), &br))
				assert.Equal(t, 0, br.TotalRanges)
				assert.Empty(t, br.Ranges)
				assert.Empty(t, br.Commits)
				assert.False(t, br.PageInfo.HasNextPage)
				assert.False(t, br.PageInfo.HasPreviousPage)
				assert.False(t, br.Truncated)
				// Ranges should marshal as an empty array, not null.
				assert.Contains(t, result, `"ranges":[]`)
			},
		},
		{
			name: "ref resolves to non-commit object",
			mockedClient: githubv4mock.NewMockedHTTPClient(
				githubv4mock.NewQueryMatcher(
					blameQueryShape{},
					makeBlameVars("testowner", "testrepo", "main", "docs"),
					githubv4mock.DataResponse(map[string]any{
						"repository": map[string]any{
							"defaultBranchRef": map[string]any{"name": "main"},
							"object": map[string]any{
								"__typename": "Tree",
								"blame":      map[string]any{"ranges": []map[string]any{}},
							},
						},
					}),
				),
			),
			requestArgs: map[string]any{
				"owner": "testowner",
				"repo":  "testrepo",
				"path":  "docs",
				"ref":   "main",
			},
			expectError:    true,
			expectedErrMsg: "did not resolve to a commit",
		},
		{
			name: "ref not found",
			mockedClient: githubv4mock.NewMockedHTTPClient(
				githubv4mock.NewQueryMatcher(
					blameQueryShape{},
					makeBlameVars("testowner", "testrepo", "no-such-ref", "README.md"),
					githubv4mock.DataResponse(map[string]any{
						"repository": map[string]any{
							"defaultBranchRef": map[string]any{"name": "main"},
							"object":           nil,
						},
					}),
				),
			),
			requestArgs: map[string]any{
				"owner": "testowner",
				"repo":  "testrepo",
				"path":  "README.md",
				"ref":   "no-such-ref",
			},
			expectError:    true,
			expectedErrMsg: "was not found",
		},
		{
			name: "annotated tag target is not commit",
			mockedClient: githubv4mock.NewMockedHTTPClient(
				githubv4mock.NewQueryMatcher(
					blameQueryShape{},
					makeBlameVars("testowner", "testrepo", "tree-tag", "README.md"),
					githubv4mock.DataResponse(map[string]any{
						"repository": map[string]any{
							"defaultBranchRef": map[string]any{"name": "main"},
							"object": map[string]any{
								"__typename": "Tag",
								"target":     map[string]any{"__typename": "Tree"},
							},
						},
					}),
				),
			),
			requestArgs: map[string]any{
				"owner": "testowner",
				"repo":  "testrepo",
				"path":  "README.md",
				"ref":   "tree-tag",
			},
			expectError:    true,
			expectedErrMsg: "tag target did not resolve to a commit",
		},
		{
			name: "line-range filter clamps and drops ranges",
			mockedClient: githubv4mock.NewMockedHTTPClient(
				githubv4mock.NewQueryMatcher(
					blameQueryShape{},
					makeBlameVars("testowner", "testrepo", "HEAD", "src/big.go"),
					githubv4mock.DataResponse(map[string]any{
						"repository": map[string]any{
							"defaultBranchRef": map[string]any{"name": "main"},
							"object": map[string]any{
								"__typename": "Commit",
								"blame": map[string]any{
									"ranges": []map[string]any{
										{
											"startingLine": 1, "endingLine": 5, "age": 1,
											"commit": map[string]any{
												"oid": "sha-A", "message": "A", "committedDate": "2024-01-01T00:00:00Z",
												"author": map[string]any{"name": "a", "email": "a@x", "user": nil},
											},
										},
										{
											"startingLine": 6, "endingLine": 12, "age": 1,
											"commit": map[string]any{
												"oid": "sha-B", "message": "B", "committedDate": "2024-01-01T00:00:00Z",
												"author": map[string]any{"name": "b", "email": "b@x", "user": nil},
											},
										},
										{
											"startingLine": 13, "endingLine": 20, "age": 1,
											"commit": map[string]any{
												"oid": "sha-C", "message": "C", "committedDate": "2024-01-01T00:00:00Z",
												"author": map[string]any{"name": "c", "email": "c@x", "user": nil},
											},
										},
									},
								},
							},
						},
					}),
				),
			),
			requestArgs: map[string]any{
				"owner":      "testowner",
				"repo":       "testrepo",
				"path":       "src/big.go",
				"start_line": float64(8),
				"end_line":   float64(15),
			},
			validateResponse: func(t *testing.T, result string) {
				var br BlameResult
				require.NoError(t, json.Unmarshal([]byte(result), &br))
				// First range (1-5) is dropped; middle clamped to 8-12;
				// last clamped to 13-15.
				require.Len(t, br.Ranges, 2)
				assert.Equal(t, 8, br.Ranges[0].StartingLine)
				assert.Equal(t, 12, br.Ranges[0].EndingLine)
				assert.Equal(t, "sha-B", br.Ranges[0].CommitSHA)
				assert.Equal(t, 13, br.Ranges[1].StartingLine)
				assert.Equal(t, 15, br.Ranges[1].EndingLine)
				assert.Equal(t, "sha-C", br.Ranges[1].CommitSHA)
				assert.NotContains(t, br.Commits, "sha-A", "filtered-out commit must not appear")
			},
		},
		{
			name: "cursor pagination returns requested page",
			mockedClient: githubv4mock.NewMockedHTTPClient(
				githubv4mock.NewQueryMatcher(
					blameQueryShape{},
					makeBlameVars("testowner", "testrepo", "HEAD", "src/paged.go"),
					githubv4mock.DataResponse(map[string]any{
						"repository": map[string]any{
							"defaultBranchRef": map[string]any{"name": "main"},
							"object": map[string]any{
								"__typename": "Commit",
								"blame": map[string]any{
									"ranges": []map[string]any{
										{
											"startingLine": 1, "endingLine": 1, "age": 1,
											"commit": map[string]any{
												"oid": "sha-A", "message": "A", "committedDate": "2024-01-01T00:00:00Z",
												"author": map[string]any{"name": "a", "email": "a@x", "user": nil},
											},
										},
										{
											"startingLine": 2, "endingLine": 2, "age": 1,
											"commit": map[string]any{
												"oid": "sha-B", "message": "B", "committedDate": "2024-01-01T00:00:00Z",
												"author": map[string]any{"name": "b", "email": "b@x", "user": nil},
											},
										},
										{
											"startingLine": 3, "endingLine": 3, "age": 1,
											"commit": map[string]any{
												"oid": "sha-C", "message": "C", "committedDate": "2024-01-01T00:00:00Z",
												"author": map[string]any{"name": "c", "email": "c@x", "user": nil},
											},
										},
									},
								},
							},
						},
					}),
				),
			),
			requestArgs: map[string]any{
				"owner":   "testowner",
				"repo":    "testrepo",
				"path":    "src/paged.go",
				"perPage": float64(1),
				"after":   encodeBlameCursor(1),
			},
			validateResponse: func(t *testing.T, result string) {
				var br BlameResult
				require.NoError(t, json.Unmarshal([]byte(result), &br))
				assert.Equal(t, 3, br.TotalRanges)
				require.Len(t, br.Ranges, 1)
				assert.Equal(t, "sha-B", br.Ranges[0].CommitSHA)
				require.Len(t, br.Commits, 1)
				require.Contains(t, br.Commits, "sha-B")
				assert.True(t, br.PageInfo.HasNextPage)
				assert.True(t, br.PageInfo.HasPreviousPage)
				assert.Equal(t, encodeBlameCursor(1), br.PageInfo.StartCursor)
				assert.Equal(t, encodeBlameCursor(2), br.PageInfo.EndCursor)
			},
		},
		{
			name: "GraphQL error is surfaced",
			mockedClient: githubv4mock.NewMockedHTTPClient(
				githubv4mock.NewQueryMatcher(
					blameQueryShape{},
					makeBlameVars("testowner", "testrepo", "main", "nonexistent.txt"),
					githubv4mock.ErrorResponse("file not found"),
				),
			),
			requestArgs: map[string]any{
				"owner": "testowner",
				"repo":  "testrepo",
				"path":  "nonexistent.txt",
				"ref":   "main",
			},
			expectError:    true,
			expectedErrMsg: "file not found",
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			client := githubv4.NewClient(tc.mockedClient)
			deps := BaseDeps{GQLClient: client}
			handler := serverTool.Handler(deps)

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

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

			require.NoError(t, err)
			require.False(t, result.IsError)
			textContent := getTextResult(t, result)
			if tc.validateResponse != nil {
				tc.validateResponse(t, textContent.Text)
			}
		})
	}

	// Path validation must short-circuit before any network call. We supply
	// a client with no matchers so any HTTP attempt would fail loudly.
	t.Run("path validation rejects bad inputs", func(t *testing.T) {
		client := githubv4.NewClient(githubv4mock.NewMockedHTTPClient())
		deps := BaseDeps{GQLClient: client}
		handler := serverTool.Handler(deps)

		cases := []struct {
			name string
			path string
			want string
		}{
			{"empty", "   ", "must not be empty"},
			{"absolute", "/etc/passwd", "must be relative"},
			{"traversal", "src/../../../etc/passwd", "must not contain '..'"},
			{"control char", "src/\x00bad.go", "control characters"},
		}
		for _, c := range cases {
			t.Run(c.name, func(t *testing.T) {
				req := createMCPRequest(map[string]any{
					"owner": "o", "repo": "r", "path": c.path,
				})
				result, err := handler(ContextWithDeps(context.Background(), deps), &req)
				require.NoError(t, err)
				require.True(t, result.IsError, "expected validation error for %q", c.path)
				assert.Contains(t, getErrorResult(t, result).Text, c.want)
			})
		}
	})

	// Line-window and cursor pagination validation also short-circuits.
	t.Run("line-range argument validation", func(t *testing.T) {
		client := githubv4.NewClient(githubv4mock.NewMockedHTTPClient())
		deps := BaseDeps{GQLClient: client}
		handler := serverTool.Handler(deps)

		cases := []struct {
			name string
			args map[string]any
			want string
		}{
			{
				"end before start",
				map[string]any{"owner": "o", "repo": "r", "path": "f.go", "start_line": float64(10), "end_line": float64(5)},
				"end_line must be >= start_line when both are provided",
			},
			{
				"start line zero",
				map[string]any{"owner": "o", "repo": "r", "path": "f.go", "start_line": float64(0)},
				"start_line must be omitted or >= 1",
			},
			{
				"end line zero",
				map[string]any{"owner": "o", "repo": "r", "path": "f.go", "end_line": float64(0)},
				"end_line must be omitted or >= 1",
			},
			{
				"page not supported",
				map[string]any{"owner": "o", "repo": "r", "path": "f.go", "page": float64(1)},
				"cursor-based pagination",
			},
			{
				"invalid after cursor",
				map[string]any{"owner": "o", "repo": "r", "path": "f.go", "after": "not-a-cursor"},
				"after cursor is invalid",
			},
			{
				"perPage too large",
				map[string]any{"owner": "o", "repo": "r", "path": "f.go", "perPage": float64(101)},
				"perPage must be between 1 and 100 when provided",
			},
			{
				"perPage zero",
				map[string]any{"owner": "o", "repo": "r", "path": "f.go", "perPage": float64(0)},
				"perPage must be between 1 and 100 when provided",
			},
		}
		for _, c := range cases {
			t.Run(c.name, func(t *testing.T) {
				req := createMCPRequest(c.args)
				result, err := handler(ContextWithDeps(context.Background(), deps), &req)
				require.NoError(t, err)
				require.True(t, result.IsError)
				assert.Contains(t, getErrorResult(t, result).Text, c.want)
			})
		}
	})

	// Truncation: hand-build a response with > maxBlameRanges to verify
	// the cap is applied and surfaced.
	t.Run("truncation at maxBlameRanges", func(t *testing.T) {
		ranges := make([]map[string]any, 0, maxBlameRanges+5)
		for i := range maxBlameRanges + 5 {
			ranges = append(ranges, map[string]any{
				"startingLine": i + 1, "endingLine": i + 1, "age": 0,
				"commit": map[string]any{
					"oid":           "sha-shared",
					"message":       "shared",
					"committedDate": "2024-01-01T00:00:00Z",
					"author":        map[string]any{"name": "n", "email": "e@x", "user": nil},
				},
			})
		}
		mocked := githubv4mock.NewMockedHTTPClient(
			githubv4mock.NewQueryMatcher(
				blameQueryShape{},
				makeBlameVars("o", "r", "HEAD", "huge.txt"),
				githubv4mock.DataResponse(map[string]any{
					"repository": map[string]any{
						"defaultBranchRef": map[string]any{"name": "main"},
						"object": map[string]any{
							"__typename": "Commit",
							"blame":      map[string]any{"ranges": ranges},
						},
					},
				}),
			),
		)
		// Use a large perPage so the truncated set is observable on a
		// single page.
		req := createMCPRequest(map[string]any{
			"owner": "o", "repo": "r", "path": "huge.txt", "perPage": float64(100),
		})
		client := githubv4.NewClient(mocked)
		deps := BaseDeps{GQLClient: client}
		handler := serverTool.Handler(deps)
		result, err := handler(ContextWithDeps(context.Background(), deps), &req)
		require.NoError(t, err)
		require.False(t, result.IsError)

		var br BlameResult
		require.NoError(t, json.Unmarshal([]byte(getTextResult(t, result).Text), &br))
		assert.True(t, br.Truncated, "truncation flag must be set")
		assert.Equal(t, maxBlameRanges+5, br.TotalRanges)
		assert.Len(t, br.Ranges, 100, "perPage limits the page size")
		assert.True(t, br.PageInfo.HasNextPage)
		assert.NotEmpty(t, br.PageInfo.EndCursor)
	})
}

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

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

	assert.Equal(t, "list_repository_collaborators", tool.Name)
	assert.NotEmpty(t, tool.Description)
	assert.True(t, tool.Annotations.ReadOnlyHint)
	assert.Contains(t, schema.Properties, "owner")
	assert.Contains(t, schema.Properties, "repo")
	assert.Contains(t, schema.Properties, "affiliation")
	assert.Contains(t, schema.Properties, "page")
	assert.Contains(t, schema.Properties, "perPage")
	assert.ElementsMatch(t, schema.Required, []string{"owner", "repo"})

	mockCollaborators := []*github.User{
		{
			Login:    github.Ptr("user1"),
			ID:       github.Ptr(int64(101)),
			RoleName: github.Ptr("admin"),
		},
		{
			Login:    github.Ptr("user2"),
			ID:       github.Ptr(int64(102)),
			RoleName: github.Ptr("write"),
		},
	}

	tests := []struct {
		name          string
		args          map[string]any
		mockResponses []MockBackendOption
		errContains   string
	}{
		{
			name: "success",
			args: map[string]any{
				"owner": "owner",
				"repo":  "repo",
			},
			mockResponses: []MockBackendOption{
				WithRequestMatch(
					ListCollaborators,
					mockCollaborators,
				),
			},
		},
		{
			name: "success with affiliation filter",
			args: map[string]any{
				"owner":       "owner",
				"repo":        "repo",
				"affiliation": "direct",
			},
			mockResponses: []MockBackendOption{
				WithRequestMatch(
					ListCollaborators,
					mockCollaborators,
				),
			},
		},
		{
			name: "missing owner",
			args: map[string]any{
				"repo": "repo",
			},
			mockResponses: []MockBackendOption{},
			errContains:   "missing required parameter: owner",
		},
		{
			name: "missing repo",
			args: map[string]any{
				"owner": "owner",
			},
			mockResponses: []MockBackendOption{},
			errContains:   "missing required parameter: repo",
		},
		{
			name: "empty collaborators returns empty array",
			args: map[string]any{
				"owner": "owner",
				"repo":  "repo",
			},
			mockResponses: []MockBackendOption{
				WithRequestMatch(
					ListCollaborators,
					[]*github.User{},
				),
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			mockClient := mustNewGHClient(t, NewMockedHTTPClient(tt.mockResponses...))
			deps := BaseDeps{
				Client: mockClient,
			}
			handler := serverTool.Handler(deps)

			request := createMCPRequest(tt.args)
			result, err := handler(ContextWithDeps(context.Background(), deps), &request)
			require.NoError(t, err)
			require.NotNil(t, result)

			if tt.errContains != "" {
				textContent := getTextResult(t, result)
				assert.Contains(t, textContent.Text, tt.errContains)
				return
			}

			textContent := getTextResult(t, result)
			require.NotEmpty(t, textContent.Text)

			var response struct {
				Items     []MinimalCollaborator `json:"items"`
				NextPage  int                   `json:"nextPage"`
				PrevPage  int                   `json:"prevPage"`
				FirstPage int                   `json:"firstPage"`
				LastPage  int                   `json:"lastPage"`
			}
			err = json.Unmarshal([]byte(textContent.Text), &response)
			require.NoError(t, err)

			if tt.name == "empty collaborators returns empty array" {
				assert.Empty(t, response.Items)
				return
			}

			collaborators := response.Items
			assert.Len(t, collaborators, 2)
			assert.Equal(t, "user1", collaborators[0].Login)
			assert.Equal(t, int64(101), collaborators[0].ID)
			assert.Equal(t, "admin", collaborators[0].RoleName)
			assert.Equal(t, "user2", collaborators[1].Login)
			assert.Equal(t, int64(102), collaborators[1].ID)
			assert.Equal(t, "write", collaborators[1].RoleName)
		})
	}
}
