package v0_test

import (
	"bytes"
	"context"
	"crypto/ed25519"
	"crypto/rand"
	"encoding/hex"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/danielgtaylor/huma/v2"
	"github.com/danielgtaylor/huma/v2/adapters/humago"
	v0 "github.com/modelcontextprotocol/registry/internal/api/handlers/v0"
	"github.com/modelcontextprotocol/registry/internal/auth"
	"github.com/modelcontextprotocol/registry/internal/config"
	"github.com/modelcontextprotocol/registry/internal/database"
	"github.com/modelcontextprotocol/registry/internal/service"
	apiv0 "github.com/modelcontextprotocol/registry/pkg/api/v0"
	"github.com/modelcontextprotocol/registry/pkg/model"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

// Helper function to generate a valid JWT token for testing
func generateTestJWTToken(cfg *config.Config, claims auth.JWTClaims) (string, error) {
	jwtManager := auth.NewJWTManager(cfg)
	ctx := context.Background()
	tokenResponse, err := jwtManager.GenerateTokenResponse(ctx, claims)
	if err != nil {
		return "", err
	}
	return tokenResponse.RegistryToken, nil
}

func TestPublishEndpoint(t *testing.T) {
	testSeed := make([]byte, ed25519.SeedSize)
	_, err := rand.Read(testSeed)
	require.NoError(t, err)
	testConfig := &config.Config{
		JWTPrivateKey:            hex.EncodeToString(testSeed),
		EnableRegistryValidation: false, // Disable for unit tests
	}

	testCases := []struct {
		name                 string
		requestBody          interface{}
		tokenClaims          *auth.JWTClaims
		authHeader           string
		setupRegistryService func(service.RegistryService)
		expectedStatus       int
		expectedError        string
	}{
		{
			name: "successful publish with GitHub auth",
			requestBody: apiv0.ServerJSON{
				Schema:      model.CurrentSchemaURL,
				Name:        "io.github.example/test-server",
				Description: "A test server",
				Repository: &model.Repository{
					URL:    "https://github.com/example/test-server",
					Source: "github",
					ID:     "example/test-server",
				},
				Version: "1.0.0",
			},
			tokenClaims: &auth.JWTClaims{
				AuthMethod:        auth.MethodGitHubAT,
				AuthMethodSubject: "example",
				Permissions: []auth.Permission{
					{Action: auth.PermissionActionPublish, ResourcePattern: "io.github.example/*"},
				},
			},
			setupRegistryService: func(_ service.RegistryService) {
				// Empty registry - no setup needed
			},
			expectedStatus: http.StatusOK,
		},
		{
			name: "successful publish with no auth (AuthMethodNone)",
			requestBody: apiv0.ServerJSON{
				Schema:      model.CurrentSchemaURL,
				Name:        "example/test-server",
				Description: "A test server without auth",
				Repository: &model.Repository{
					URL:    "https://github.com/example/test-server",
					Source: "github",
					ID:     "example/test-server",
				},
				Version: "1.0.0",
			},
			tokenClaims: &auth.JWTClaims{
				AuthMethod: auth.MethodNone,
				Permissions: []auth.Permission{
					{Action: auth.PermissionActionPublish, ResourcePattern: "example/*"},
				},
			},
			setupRegistryService: func(_ service.RegistryService) {
				// Empty registry - no setup needed
			},
			expectedStatus: http.StatusOK,
		},
		{
			name:        "missing authorization header",
			requestBody: apiv0.ServerJSON{},
			authHeader:  "", // Empty auth header
			setupRegistryService: func(_ service.RegistryService) {
				// Empty registry - no setup needed
			},
			expectedStatus: http.StatusUnprocessableEntity,
			expectedError:  "required header parameter is missing",
		},
		{
			name: "invalid authorization header format",
			requestBody: apiv0.ServerJSON{
				Schema:      model.CurrentSchemaURL,
				Name:        "io.github.domdomegg/test-server",
				Description: "Test server",
				Version:     "1.0.0",
			},
			authHeader: "InvalidFormat",
			setupRegistryService: func(_ service.RegistryService) {
				// Empty registry - no setup needed
			},
			expectedStatus: http.StatusUnauthorized,
			expectedError:  "Invalid Authorization header format",
		},
		{
			name: "invalid token",
			requestBody: apiv0.ServerJSON{
				Schema:      model.CurrentSchemaURL,
				Name:        "example/test-server",
				Description: "A test server",
				Version:     "1.0.0",
			},
			authHeader: "Bearer invalidToken",
			setupRegistryService: func(_ service.RegistryService) {
				// Empty registry - no setup needed
			},
			expectedStatus: http.StatusUnauthorized,
			expectedError:  "Invalid or expired Registry JWT token",
		},
		{
			name: "permission denied",
			requestBody: apiv0.ServerJSON{
				Schema:      model.CurrentSchemaURL,
				Name:        "io.github.other/test-server",
				Description: "A test server",
				Version:     "1.0.0",
				Repository: &model.Repository{
					URL:    "https://github.com/example/test-server",
					Source: "github",
					ID:     "example/test-server",
				},
			},
			tokenClaims: &auth.JWTClaims{
				AuthMethod: auth.MethodGitHubAT,
				Permissions: []auth.Permission{
					{Action: auth.PermissionActionPublish, ResourcePattern: "io.github.example/*"},
				},
			},
			setupRegistryService: func(_ service.RegistryService) {
				// Empty registry - no setup needed
			},
			expectedStatus: http.StatusForbidden,
			expectedError:  "You do not have permission to publish this server",
		},
		{
			name: "registry service error",
			requestBody: apiv0.ServerJSON{
				Schema:      model.CurrentSchemaURL,
				Name:        "example/test-server",
				Description: "A test server",
				Version:     "1.0.0",
				Repository: &model.Repository{
					URL:    "https://github.com/example/test-server",
					Source: "github",
					ID:     "example/test-server",
				},
			},
			tokenClaims: &auth.JWTClaims{
				AuthMethod: auth.MethodNone,
				Permissions: []auth.Permission{
					{Action: auth.PermissionActionPublish, ResourcePattern: "*"},
				},
			},
			setupRegistryService: func(registry service.RegistryService) {
				// Pre-publish the same server to cause duplicate version error
				existingServer := apiv0.ServerJSON{
					Schema:      model.CurrentSchemaURL,
					Name:        "example/test-server",
					Description: "Existing test server",
					Version:     "1.0.0",
					Repository: &model.Repository{
						URL:    "https://github.com/example/test-server-existing",
						Source: "github",
						ID:     "example/test-server-existing",
					},
				}
				_, _ = registry.CreateServer(context.Background(), &existingServer)
			},
			expectedStatus: http.StatusBadRequest,
			expectedError:  "invalid version: cannot publish duplicate version",
		},
		{
			name: "package validation success - MCPB package",
			requestBody: apiv0.ServerJSON{
				Schema:      model.CurrentSchemaURL,
				Name:        "com.example/test-server-mcpb",
				Description: "A test server with MCPB package",
				Version:     "1.0.0",
				Packages: []model.Package{
					{
						RegistryType: model.RegistryTypeMCPB,
						Identifier:   "https://github.com/example/server/releases/download/v1.0.0/server.tar.gz",
						Version:      "1.0.0",
						FileSHA256:   "fe333e598595000ae021bd27117db32ec69af6987f507ba7a63c90638ff633ce",
						Transport: model.Transport{
							Type: model.TransportTypeStdio,
						},
					},
				},
			},
			tokenClaims: &auth.JWTClaims{
				AuthMethod: auth.MethodNone,
				Permissions: []auth.Permission{
					{Action: auth.PermissionActionPublish, ResourcePattern: "*"},
				},
			},
			setupRegistryService: func(_ service.RegistryService) {},
			expectedStatus:       http.StatusOK,
		},
		{
			name: "invalid server name - multiple slashes (two slashes)",
			requestBody: apiv0.ServerJSON{
				Schema:      model.CurrentSchemaURL,
				Name:        "com.example/server/path",
				Description: "Server with multiple slashes in name",
				Version:     "1.0.0",
				Repository: &model.Repository{
					URL:    "https://github.com/example/test-server",
					Source: "github",
					ID:     "example/test-server",
				},
			},
			tokenClaims: &auth.JWTClaims{
				AuthMethod: auth.MethodNone,
				Permissions: []auth.Permission{
					{Action: auth.PermissionActionPublish, ResourcePattern: "*"},
				},
			},
			setupRegistryService: func(_ service.RegistryService) {},
			expectedStatus:       http.StatusUnprocessableEntity,
			expectedError:        "expected string to match pattern",
		},
		{
			name: "invalid server name - multiple slashes (three slashes)",
			requestBody: apiv0.ServerJSON{
				Schema:      model.CurrentSchemaURL,
				Name:        "org.company/dept/team/project",
				Description: "Server with three slashes in name",
				Version:     "1.0.0",
			},
			tokenClaims: &auth.JWTClaims{
				AuthMethod: auth.MethodNone,
				Permissions: []auth.Permission{
					{Action: auth.PermissionActionPublish, ResourcePattern: "*"},
				},
			},
			setupRegistryService: func(_ service.RegistryService) {},
			expectedStatus:       http.StatusUnprocessableEntity,
			expectedError:        "expected string to match pattern",
		},
		{
			name: "invalid server name - consecutive slashes",
			requestBody: apiv0.ServerJSON{
				Schema:      model.CurrentSchemaURL,
				Name:        "com.example//double-slash",
				Description: "Server with consecutive slashes",
				Version:     "1.0.0",
			},
			tokenClaims: &auth.JWTClaims{
				AuthMethod: auth.MethodNone,
				Permissions: []auth.Permission{
					{Action: auth.PermissionActionPublish, ResourcePattern: "*"},
				},
			},
			setupRegistryService: func(_ service.RegistryService) {},
			expectedStatus:       http.StatusUnprocessableEntity,
			expectedError:        "expected string to match pattern",
		},
		{
			name: "invalid server name - URL-like path",
			requestBody: apiv0.ServerJSON{
				Schema:      model.CurrentSchemaURL,
				Name:        "com.example/servers/v1/api",
				Description: "Server with URL-like path structure",
				Version:     "1.0.0",
			},
			tokenClaims: &auth.JWTClaims{
				AuthMethod: auth.MethodNone,
				Permissions: []auth.Permission{
					{Action: auth.PermissionActionPublish, ResourcePattern: "*"},
				},
			},
			setupRegistryService: func(_ service.RegistryService) {},
			expectedStatus:       http.StatusUnprocessableEntity,
			expectedError:        "expected string to match pattern",
		},
		{
			name: "invalid server name - many slashes",
			requestBody: apiv0.ServerJSON{
				Schema:      model.CurrentSchemaURL,
				Name:        "a/b/c/d/e/f",
				Description: "Server with many slashes",
				Version:     "1.0.0",
			},
			tokenClaims: &auth.JWTClaims{
				AuthMethod: auth.MethodNone,
				Permissions: []auth.Permission{
					{Action: auth.PermissionActionPublish, ResourcePattern: "*"},
				},
			},
			setupRegistryService: func(_ service.RegistryService) {},
			expectedStatus:       http.StatusUnprocessableEntity,
			expectedError:        "expected string to match pattern",
		},
		{
			name: "invalid server name - with packages and remotes",
			requestBody: apiv0.ServerJSON{
				Schema:      model.CurrentSchemaURL,
				Name:        "com.example/test/server/v2",
				Description: "Complex server with invalid name",
				Version:     "2.0.0",
				Repository: &model.Repository{
					URL:    "https://github.com/example/test-server",
					Source: "github",
					ID:     "example/test-server",
				},
				Packages: []model.Package{
					{
						RegistryType: model.RegistryTypeNPM,
						Identifier:   "test-package",
						Version:      "2.0.0",
						Transport: model.Transport{
							Type: model.TransportTypeStdio,
						},
					},
				},
				Remotes: []model.Transport{
					{
						Type: model.TransportTypeStreamableHTTP,
						URL:  "https://example.com/api",
					},
				},
			},
			tokenClaims: &auth.JWTClaims{
				AuthMethod: auth.MethodNone,
				Permissions: []auth.Permission{
					{Action: auth.PermissionActionPublish, ResourcePattern: "*"},
				},
			},
			setupRegistryService: func(_ service.RegistryService) {},
			expectedStatus:       http.StatusUnprocessableEntity,
			expectedError:        "expected string to match pattern",
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			// Create registry service
			registryService := service.NewRegistryService(database.NewTestDB(t), testConfig)

			// Setup registry service
			tc.setupRegistryService(registryService)

			// Create a new ServeMux and Huma API
			mux := http.NewServeMux()
			api := humago.New(mux, huma.DefaultConfig("Test API", "1.0.0"))

			// Register the endpoint with test config
			v0.RegisterPublishEndpoint(api, "/v0", registryService, testConfig)

			// Prepare request body
			var requestBody []byte
			if tc.requestBody != nil {
				var err error
				requestBody, err = json.Marshal(tc.requestBody)
				assert.NoError(t, err)
			}

			// Create request
			req, err := http.NewRequestWithContext(context.Background(), http.MethodPost, "/v0/publish", bytes.NewBuffer(requestBody))
			assert.NoError(t, err)
			req.Header.Set("Content-Type", "application/json")

			// Set auth header
			if tc.authHeader != "" {
				req.Header.Set("Authorization", tc.authHeader)
			} else if tc.tokenClaims != nil {
				// Generate a valid JWT token
				token, err := generateTestJWTToken(testConfig, *tc.tokenClaims)
				assert.NoError(t, err)
				req.Header.Set("Authorization", "Bearer "+token)
			}

			// Perform request
			rr := httptest.NewRecorder()
			mux.ServeHTTP(rr, req)

			// Assertions
			assert.Equal(t, tc.expectedStatus, rr.Code, "status code mismatch")

			if tc.expectedError != "" {
				assert.Contains(t, rr.Body.String(), tc.expectedError)
			}

			// No mock expectations to verify
		})
	}
}

// TestPublishEndpoint_MultipleSlashesEdgeCases tests additional edge cases for multi-slash validation
func TestPublishEndpoint_MultipleSlashesEdgeCases(t *testing.T) {
	testSeed := make([]byte, ed25519.SeedSize)
	_, err := rand.Read(testSeed)
	require.NoError(t, err)
	testConfig := &config.Config{
		JWTPrivateKey:            hex.EncodeToString(testSeed),
		EnableRegistryValidation: false,
	}

	testCases := []struct {
		name           string
		serverName     string
		expectedStatus int
		description    string
	}{
		{
			name:           "valid - single slash",
			serverName:     "com.example/server",
			expectedStatus: http.StatusOK,
			description:    "Valid server name with single slash should succeed",
		},
		{
			name:           "invalid - trailing slash after valid name",
			serverName:     "com.example/server/",
			expectedStatus: http.StatusUnprocessableEntity,
			description:    "Trailing slash creates multiple slashes",
		},
		{
			name:           "invalid - leading and middle slash",
			serverName:     "/com.example/server",
			expectedStatus: http.StatusUnprocessableEntity,
			description:    "Leading slash with middle slash",
		},
		{
			name:           "invalid - file system style path",
			serverName:     "usr/local/bin/server",
			expectedStatus: http.StatusUnprocessableEntity,
			description:    "File system style paths should be rejected",
		},
		{
			name:           "invalid - version-like suffix",
			serverName:     "com.example/server/v1.0.0",
			expectedStatus: http.StatusUnprocessableEntity,
			description:    "Version suffixes with slash should be rejected",
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			// Create registry service
			registryService := service.NewRegistryService(database.NewTestDB(t), testConfig)

			// Create a new ServeMux and Huma API
			mux := http.NewServeMux()
			api := humago.New(mux, huma.DefaultConfig("Test API", "1.0.0"))

			// Register the endpoint
			v0.RegisterPublishEndpoint(api, "/v0", registryService, testConfig)

			// Create request body
			requestBody := apiv0.ServerJSON{
				Schema:      model.CurrentSchemaURL,
				Name:        tc.serverName,
				Description: "Test server",
				Version:     "1.0.0",
			}

			bodyBytes, err := json.Marshal(requestBody)
			require.NoError(t, err)

			// Create request
			req, err := http.NewRequestWithContext(context.Background(), http.MethodPost, "/v0/publish", bytes.NewBuffer(bodyBytes))
			require.NoError(t, err)
			req.Header.Set("Content-Type", "application/json")

			// Set auth header with permissions
			tokenClaims := auth.JWTClaims{
				AuthMethod: auth.MethodNone,
				Permissions: []auth.Permission{
					{Action: auth.PermissionActionPublish, ResourcePattern: "*"},
				},
			}
			token, err := generateTestJWTToken(testConfig, tokenClaims)
			require.NoError(t, err)
			req.Header.Set("Authorization", "Bearer "+token)

			// Perform request
			rr := httptest.NewRecorder()
			mux.ServeHTTP(rr, req)

			// Assertions
			assert.Equal(t, tc.expectedStatus, rr.Code,
				"%s: expected status %d, got %d", tc.description, tc.expectedStatus, rr.Code)

			if tc.expectedStatus == http.StatusUnprocessableEntity {
				assert.Contains(t, rr.Body.String(), "expected string to match pattern",
					"%s: should contain pattern validation error", tc.description)
			}
		})
	}
}
