package github

import (
	"context"
	"errors"
	"fmt"
	"testing"

	"github.com/google/go-github/v89/github"
	"github.com/modelcontextprotocol/go-sdk/mcp"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestRepositoryResourceCompletionHandler(t *testing.T) {
	tests := []struct {
		name     string
		request  *mcp.CompleteRequest
		expected *mcp.CompleteResult
		wantErr  bool
	}{
		{
			name: "non-resource completion request",
			request: &mcp.CompleteRequest{
				Params: &mcp.CompleteParams{
					Ref: &mcp.CompleteReference{
						Type: "something-else",
					},
				},
			},
			expected: nil,
			wantErr:  false,
		},
		{
			name: "invalid ref type",
			request: &mcp.CompleteRequest{
				Params: &mcp.CompleteParams{
					Ref: &mcp.CompleteReference{
						Type: "invalid-ref",
					},
				},
			},
			expected: nil,
			wantErr:  false,
		},
		{
			name: "unknown argument",
			request: &mcp.CompleteRequest{
				Params: &mcp.CompleteParams{
					Ref: &mcp.CompleteReference{
						Type: "ref/resource",
					},
					Context: &mcp.CompleteContext{},
					Argument: mcp.CompleteParamsArgument{
						Name:  "unknown_arg",
						Value: "test",
					},
				},
			},
			expected: nil,
			wantErr:  true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			getClient := func(_ context.Context) (*github.Client, error) {
				return &github.Client{}, nil
			}

			handler := RepositoryResourceCompletionHandler(getClient)
			result, err := handler(t.Context(), tt.request)

			if tt.wantErr {
				require.Error(t, err)
			} else {
				require.NoError(t, err)
			}
			assert.Equal(t, tt.expected, result)
		})
	}
}

func TestRepositoryResourceCompletionHandler_GetClientError(t *testing.T) {
	getClient := func(_ context.Context) (*github.Client, error) {
		return nil, errors.New("client error")
	}

	handler := RepositoryResourceCompletionHandler(getClient)
	request := &mcp.CompleteRequest{
		Params: &mcp.CompleteParams{
			Ref: &mcp.CompleteReference{
				Type: "ref/resource",
			},
			Context: &mcp.CompleteContext{
				Arguments: map[string]string{
					"owner": "test",
				},
			},
			Argument: mcp.CompleteParamsArgument{
				Name:  "owner",
				Value: "test",
			},
		},
	}

	result, err := handler(t.Context(), request)
	require.Error(t, err)
	assert.Nil(t, result)
	assert.Contains(t, err.Error(), "client error")
}

// Test the logical behavior of complete functions with missing dependencies
func TestCompleteRepo_MissingOwner(t *testing.T) {
	ctx := t.Context()
	resolved := map[string]string{} // No owner
	argValue := "test"

	result, err := completeRepo(ctx, nil, resolved, argValue)
	require.Error(t, err)
	assert.Nil(t, result) // Should return nil slice when owner is missing
}

func TestCompleteBranch_MissingDependencies(t *testing.T) {
	ctx := t.Context()

	// Test missing owner
	resolved := map[string]string{"repo": "testrepo"}
	result, err := completeBranch(ctx, nil, resolved, "main")
	require.Error(t, err)
	assert.Nil(t, result) // Returns nil slice when dependencies are missing

	// Test missing repo
	resolved = map[string]string{"owner": "testowner"}
	result, err = completeBranch(ctx, nil, resolved, "main")
	require.Error(t, err)
	assert.Nil(t, result) // Returns nil slice when dependencies are missing
}

func TestCompleteSHA_MissingDependencies(t *testing.T) {
	ctx := t.Context()

	// Test missing owner
	resolved := map[string]string{"repo": "testrepo"}
	result, err := completeSHA(ctx, nil, resolved, "abc123")
	require.Error(t, err)
	assert.Nil(t, result) // Returns nil slice when dependencies are missing

	// Test missing repo
	resolved = map[string]string{"owner": "testowner"}
	result, err = completeSHA(ctx, nil, resolved, "abc123")
	require.Error(t, err)
	assert.Nil(t, result) // Returns nil slice when dependencies are missing
}

func TestCompleteTag_MissingDependencies(t *testing.T) {
	ctx := t.Context()

	// Test missing owner
	resolved := map[string]string{"repo": "testrepo"}
	result, err := completeTag(ctx, nil, resolved, "v1.0")
	require.Error(t, err)
	assert.Nil(t, result) // completeTag returns nil for missing dependencies

	// Test missing repo
	resolved = map[string]string{"owner": "testowner"}
	result, err = completeTag(ctx, nil, resolved, "v1.0")
	require.Error(t, err)
	assert.Nil(t, result)
}

func TestCompletePRNumber_MissingDependencies(t *testing.T) {
	ctx := t.Context()

	// Test missing owner
	resolved := map[string]string{"repo": "testrepo"}
	result, err := completePRNumber(ctx, nil, resolved, "1")
	require.Error(t, err)
	assert.Nil(t, result) // Returns nil slice when dependencies are missing

	// Test missing repo
	resolved = map[string]string{"owner": "testowner"}
	result, err = completePRNumber(ctx, nil, resolved, "1")
	require.Error(t, err)
	assert.Nil(t, result) // Returns nil slice when dependencies are missing
}

func TestCompletePath_MissingDependencies(t *testing.T) {
	ctx := t.Context()

	// Test missing owner
	resolved := map[string]string{"repo": "testrepo"}
	result, err := completePath(ctx, nil, resolved, "src/")
	require.Error(t, err)
	assert.Nil(t, result) // completePath returns nil for missing dependencies

	// Test missing repo
	resolved = map[string]string{"owner": "testowner"}
	result, err = completePath(ctx, nil, resolved, "src/")
	require.Error(t, err)
	assert.Nil(t, result)
}

func TestCompletePath_RefSelection(t *testing.T) {
	// Test the logic for selecting the ref (branch, sha, tag, or HEAD)
	// We test this by verifying the function handles different ref combinations
	// without making API calls (since we can't mock them easily)

	ctx := t.Context()

	// Test that the function returns nil when dependencies are missing
	resolved := map[string]string{
		"owner": "",
		"repo":  "",
	}
	result, err := completePath(ctx, nil, resolved, "src/")
	require.Error(t, err)
	assert.Nil(t, result)

	// When owner is present but repo is missing
	resolved = map[string]string{
		"owner": "testowner",
		"repo":  "",
	}
	result, err = completePath(ctx, nil, resolved, "src/")
	require.Error(t, err)
	assert.Nil(t, result)
}

func TestRepositoryResourceArgumentResolvers_Existence(t *testing.T) {
	// Test that all expected resolvers are present
	expectedResolvers := []string{
		"owner", "repo", "branch", "sha", "tag", "prNumber", "path",
	}

	for _, resolver := range expectedResolvers {
		t.Run(fmt.Sprintf("resolver_%s_exists", resolver), func(t *testing.T) {
			_, exists := RepositoryResourceArgumentResolvers[resolver]
			assert.True(t, exists, "Resolver %s should exist", resolver)
		})
	}

	// Verify the total count
	assert.Len(t, RepositoryResourceArgumentResolvers, len(expectedResolvers))
}

func TestRepositoryResourceCompletionHandler_MaxResults(t *testing.T) {
	// Test that results are limited to 100 items
	getClient := func(_ context.Context) (*github.Client, error) {
		return &github.Client{}, nil
	}

	handler := RepositoryResourceCompletionHandler(getClient)

	// Mock a resolver that returns more than 100 results
	originalResolver := RepositoryResourceArgumentResolvers["owner"]
	RepositoryResourceArgumentResolvers["owner"] = func(_ context.Context, _ *github.Client, _ map[string]string, _ string) ([]string, error) {
		// Return 150 results
		results := make([]string, 150)
		for i := range 150 {
			results[i] = fmt.Sprintf("user%d", i)
		}
		return results, nil
	}

	request := &mcp.CompleteRequest{
		Params: &mcp.CompleteParams{
			Ref: &mcp.CompleteReference{
				Type: "ref/resource",
			},
			Context: &mcp.CompleteContext{
				Arguments: map[string]string{
					"owner": "test",
				},
			},
			Argument: mcp.CompleteParamsArgument{
				Name:  "owner",
				Value: "test",
			},
		},
	}

	result, err := handler(t.Context(), request)
	require.NoError(t, err)
	assert.NotNil(t, result)
	assert.LessOrEqual(t, len(result.Completion.Values), 100)

	// Restore original resolver
	RepositoryResourceArgumentResolvers["owner"] = originalResolver
}

func TestRepositoryResourceCompletionHandler_WithContext(t *testing.T) {
	// Test that the handler properly passes resolved context arguments
	getClient := func(_ context.Context) (*github.Client, error) {
		return &github.Client{}, nil
	}

	handler := RepositoryResourceCompletionHandler(getClient)

	// Mock a resolver that just returns the resolved arguments for testing
	originalResolver := RepositoryResourceArgumentResolvers["repo"]
	RepositoryResourceArgumentResolvers["repo"] = func(_ context.Context, _ *github.Client, resolved map[string]string, _ string) ([]string, error) {
		if owner, exists := resolved["owner"]; exists {
			return []string{fmt.Sprintf("repo-for-%s", owner)}, nil
		}
		return []string{}, nil
	}

	request := &mcp.CompleteRequest{
		Params: &mcp.CompleteParams{
			Ref: &mcp.CompleteReference{
				Type: "ref/resource",
			},
			Argument: mcp.CompleteParamsArgument{
				Name:  "repo",
				Value: "test",
			},
			Context: &mcp.CompleteContext{
				Arguments: map[string]string{
					"owner": "testowner",
				},
			},
		},
	}

	result, err := handler(t.Context(), request)
	require.NoError(t, err)
	assert.NotNil(t, result)
	assert.Contains(t, result.Completion.Values, "repo-for-testowner")

	// Restore original resolver
	RepositoryResourceArgumentResolvers["repo"] = originalResolver
}

func TestRepositoryResourceCompletionHandler_NilContext(t *testing.T) {
	// Test that the handler handles nil context gracefully
	getClient := func(_ context.Context) (*github.Client, error) {
		return &github.Client{}, nil
	}

	handler := RepositoryResourceCompletionHandler(getClient)

	// Mock a resolver that checks for empty resolved map
	originalResolver := RepositoryResourceArgumentResolvers["repo"]
	RepositoryResourceArgumentResolvers["repo"] = func(_ context.Context, _ *github.Client, resolved map[string]string, _ string) ([]string, error) {
		assert.NotNil(t, resolved, "Resolved map should never be nil")
		return []string{"test-repo"}, nil
	}

	request := &mcp.CompleteRequest{
		Params: &mcp.CompleteParams{
			Ref: &mcp.CompleteReference{
				Type: "ref/resource",
			},
			Argument: mcp.CompleteParamsArgument{
				Name:  "repo",
				Value: "test",
			},
			// Context is not set, so it should default to empty map
			Context: &mcp.CompleteContext{
				Arguments: map[string]string{},
			},
		},
	}

	result, err := handler(t.Context(), request)
	require.NoError(t, err)
	assert.NotNil(t, result)

	// Restore original resolver
	RepositoryResourceArgumentResolvers["repo"] = originalResolver
}
