// SPDX-FileCopyrightText: Copyright 2025 Stacklok, Inc.
// SPDX-License-Identifier: Apache-2.0

package server

import (
	"context"
	"testing"

	"github.com/mark3labs/mcp-go/mcp"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	regtypes "github.com/stacklok/toolhive-core/registry/types"
	"github.com/stacklok/toolhive/pkg/runner"
)

func TestParseRunServerArgs(t *testing.T) {
	t.Parallel()
	tests := []struct {
		name     string
		request  mcp.CallToolRequest
		expected *runServerArgs
		wantErr  bool
	}{
		{
			name: "valid args with all fields",
			request: mcp.CallToolRequest{
				Params: mcp.CallToolParams{
					Arguments: map[string]interface{}{
						"server": "test-server",
						"name":   "custom-name",
						"host":   "192.168.1.1",
						"env": map[string]interface{}{
							"KEY1": "value1",
							"KEY2": "value2",
						},
						"secrets": []interface{}{
							map[string]interface{}{
								"name":   "github-token",
								"target": "GITHUB_TOKEN",
							},
							map[string]interface{}{
								"name":   "api-key",
								"target": "API_KEY",
							},
						},
					},
				},
			},
			expected: &runServerArgs{
				Server: "test-server",
				Name:   "custom-name",
				Host:   "192.168.1.1",
				Env: map[string]string{
					"KEY1": "value1",
					"KEY2": "value2",
				},
				Secrets: []SecretMapping{
					{Name: "github-token", Target: "GITHUB_TOKEN"},
					{Name: "api-key", Target: "API_KEY"},
				},
			},
			wantErr: false,
		},
		{
			name: "minimal args - server only",
			request: mcp.CallToolRequest{
				Params: mcp.CallToolParams{
					Arguments: map[string]interface{}{
						"server": "test-server",
					},
				},
			},
			expected: &runServerArgs{
				Server:  "test-server",
				Name:    "test-server", // Should default to server name
				Host:    "127.0.0.1",   // Should default to 127.0.0.1
				Env:     nil,
				Secrets: nil,
			},
			wantErr: false,
		},
		{
			name: "empty name defaults to server name",
			request: mcp.CallToolRequest{
				Params: mcp.CallToolParams{
					Arguments: map[string]interface{}{
						"server": "my-server",
						"name":   "",
					},
				},
			},
			expected: &runServerArgs{
				Server:  "my-server",
				Name:    "my-server",
				Host:    "127.0.0.1",
				Env:     nil,
				Secrets: nil,
			},
			wantErr: false,
		},
		{
			name: "empty host defaults to 127.0.0.1",
			request: mcp.CallToolRequest{
				Params: mcp.CallToolParams{
					Arguments: map[string]interface{}{
						"server": "test-server",
						"host":   "",
					},
				},
			},
			expected: &runServerArgs{
				Server:  "test-server",
				Name:    "test-server",
				Host:    "127.0.0.1",
				Env:     nil,
				Secrets: nil,
			},
			wantErr: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()
			result, err := parseRunServerArgs(tt.request)

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

func TestConfigureTransport(t *testing.T) {
	t.Parallel()
	tests := []struct {
		name              string
		imageMetadata     *regtypes.ImageMetadata
		expectedTransport string
	}{
		{
			name:              "nil metadata returns SSE",
			imageMetadata:     nil,
			expectedTransport: "sse",
		},
		{
			name: "metadata with empty transport returns SSE",
			imageMetadata: &regtypes.ImageMetadata{
				BaseServerMetadata: regtypes.BaseServerMetadata{
					Transport: "",
				},
			},
			expectedTransport: "sse",
		},
		{
			name: "metadata with stdio transport",
			imageMetadata: &regtypes.ImageMetadata{
				BaseServerMetadata: regtypes.BaseServerMetadata{
					Transport: "stdio",
				},
			},
			expectedTransport: "stdio",
		},
		{
			name: "metadata with streamable-http transport",
			imageMetadata: &regtypes.ImageMetadata{
				BaseServerMetadata: regtypes.BaseServerMetadata{
					Transport: "streamable-http",
				},
			},
			expectedTransport: "streamable-http",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()
			opts := []runner.RunConfigBuilderOption{}
			transport := configureTransport(&opts, tt.imageMetadata)

			assert.Equal(t, tt.expectedTransport, transport)
		})
	}
}

func TestPrepareEnvironmentVariables(t *testing.T) {
	t.Parallel()
	tests := []struct {
		name          string
		imageMetadata *regtypes.ImageMetadata
		userEnv       map[string]string
		expected      map[string]string
	}{
		{
			name:          "nil metadata and nil user env",
			imageMetadata: nil,
			userEnv:       nil,
			expected:      map[string]string{},
		},
		{
			name: "metadata with defaults, no user env",
			imageMetadata: &regtypes.ImageMetadata{
				EnvVars: []*regtypes.EnvVar{
					{Name: "VAR1", Default: "default1"},
					{Name: "VAR2", Default: "default2"},
				},
			},
			userEnv: nil,
			expected: map[string]string{
				"VAR1": "default1",
				"VAR2": "default2",
			},
		},
		{
			name: "metadata with defaults, user overrides",
			imageMetadata: &regtypes.ImageMetadata{
				EnvVars: []*regtypes.EnvVar{
					{Name: "VAR1", Default: "default1"},
					{Name: "VAR2", Default: "default2"},
				},
			},
			userEnv: map[string]string{
				"VAR1": "user1",
				"VAR3": "user3",
			},
			expected: map[string]string{
				"VAR1": "user1",
				"VAR2": "default2",
				"VAR3": "user3",
			},
		},
		{
			name:          "no metadata, only user env",
			imageMetadata: nil,
			userEnv: map[string]string{
				"USER_VAR": "user_value",
			},
			expected: map[string]string{
				"USER_VAR": "user_value",
			},
		},
		{
			name: "metadata with empty defaults ignored",
			imageMetadata: &regtypes.ImageMetadata{
				EnvVars: []*regtypes.EnvVar{
					{Name: "VAR1", Default: ""},
					{Name: "VAR2", Default: "value2"},
				},
			},
			userEnv: nil,
			expected: map[string]string{
				"VAR2": "value2",
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()
			result := prepareEnvironmentVariables(tt.imageMetadata, tt.userEnv)

			// Compare maps directly
			assert.Equal(t, tt.expected, result)
		})
	}
}

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

	ctx := context.Background()
	args := &runServerArgs{
		Server: "test-server",
		Name:   "test-name",
		Host:   "127.0.0.1",
		Env:    map[string]string{"TEST_VAR": "test_value"},
	}

	tests := []struct {
		name          string
		imageURL      string
		imageMetadata *regtypes.ImageMetadata
		extraOpts     []runner.RunConfigBuilderOption
		expectError   bool
		checkConfig   func(t *testing.T, rc *runner.RunConfig)
	}{
		{
			name:          "valid config with nil metadata",
			imageURL:      "test/image:latest",
			imageMetadata: nil,
			expectError:   false, // Actually succeeds because container runtime creation works
		},
		{
			name:     "valid config with metadata",
			imageURL: "test/image:latest",
			imageMetadata: &regtypes.ImageMetadata{
				BaseServerMetadata: regtypes.BaseServerMetadata{
					Transport: "stdio",
				},
				Image: "test/image:latest",
				Args:  []string{"--test"},
				EnvVars: []*regtypes.EnvVar{
					{Name: "DEFAULT_VAR", Default: "default_value"},
				},
			},
			expectError: false, // Actually succeeds and tests the type assertion line
		},
		{
			name:     "registry source URLs and server name are recorded on config",
			imageURL: "test/image:latest",
			imageMetadata: &regtypes.ImageMetadata{
				BaseServerMetadata: regtypes.BaseServerMetadata{
					Transport: "stdio",
				},
				Image: "test/image:latest",
			},
			extraOpts: []runner.RunConfigBuilderOption{
				runner.WithRegistrySourceURLs("https://api.example.com", "https://registry.example.com"),
				runner.WithRegistryServerName("fetch"),
			},
			expectError: false,
			checkConfig: func(t *testing.T, rc *runner.RunConfig) {
				t.Helper()
				assert.Equal(t, "https://api.example.com", rc.RegistryAPIURL)
				assert.Equal(t, "https://registry.example.com", rc.RegistryURL)
				assert.Equal(t, "fetch", rc.RegistryServerName)
			},
		},
	}

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

			runConfig, err := buildServerConfig(ctx, args, tt.imageURL, tt.imageMetadata, tt.extraOpts...)

			if tt.expectError {
				require.Error(t, err)
				require.Nil(t, runConfig)
			} else {
				require.NoError(t, err)
				require.NotNil(t, runConfig)
				if tt.checkConfig != nil {
					tt.checkConfig(t, runConfig)
				}
			}
		})
	}
}

func TestPrepareSecrets(t *testing.T) {
	t.Parallel()
	tests := []struct {
		name     string
		secrets  []SecretMapping
		expected []string
	}{
		{
			name:     "nil secrets",
			secrets:  nil,
			expected: nil,
		},
		{
			name:     "empty secrets",
			secrets:  []SecretMapping{},
			expected: nil,
		},
		{
			name: "single secret",
			secrets: []SecretMapping{
				{Name: "github-token", Target: "GITHUB_TOKEN"},
			},
			expected: []string{"github-token,target=GITHUB_TOKEN"},
		},
		{
			name: "multiple secrets",
			secrets: []SecretMapping{
				{Name: "github-token", Target: "GITHUB_TOKEN"},
				{Name: "api-key", Target: "API_KEY"},
				{Name: "db-password", Target: "DATABASE_PASSWORD"},
			},
			expected: []string{
				"github-token,target=GITHUB_TOKEN",
				"api-key,target=API_KEY",
				"db-password,target=DATABASE_PASSWORD",
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()
			result := prepareSecrets(tt.secrets)
			assert.Equal(t, tt.expected, result)
		})
	}
}
