package app

import (
	"errors"
	"strings"
	"testing"
	"time"

	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/internal/extension/providers/searchengine/runtime"
	"github.com/tidbcloud/AgentTeam/internal/store/app/registry"
	"github.com/tidbcloud/AgentTeam/internal/store/serviceactor"
)

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

	tests := []struct {
		name        string
		provider    string
		params      UpsertSearchEngineConnectionParams
		wantKey     string
		wantBase    string
		wantLang    string
		wantErr     bool
		keyProvided bool
	}{
		{
			name:     "SearXNG config",
			provider: runtime.ProviderSearXNG,
			params: UpsertSearchEngineConnectionParams{
				Config: map[string]any{
					"baseUrl":  " https://search.example.com/base ",
					"language": " zh-CN ",
				},
				Credentials: map[string]any{},
			},
			wantBase: "https://search.example.com/base",
			wantLang: "zh-CN",
		},
		{
			name:     "Brave api key",
			provider: runtime.ProviderBrave,
			params: UpsertSearchEngineConnectionParams{
				Config:      map[string]any{},
				Credentials: map[string]any{"apiKey": " secret "},
			},
			wantKey:     "secret",
			keyProvided: true,
		},
		{
			name:     "omitted key preserves existing",
			provider: runtime.ProviderTavily,
			params: UpsertSearchEngineConnectionParams{
				Config:      map[string]any{},
				Credentials: map[string]any{},
			},
		},
		{
			name:     "SearXNG rejects credential",
			provider: runtime.ProviderSearXNG,
			params: UpsertSearchEngineConnectionParams{
				Config:      map[string]any{"baseUrl": "https://search.example.com"},
				Credentials: map[string]any{"apiKey": "not-allowed"},
			},
			wantErr: true,
		},
		{
			name:     "Brave rejects config",
			provider: runtime.ProviderBrave,
			params: UpsertSearchEngineConnectionParams{
				Config:      map[string]any{"baseUrl": "https://example.com"},
				Credentials: map[string]any{"apiKey": "secret"},
			},
			wantErr: true,
		},
		{
			name:     "empty key",
			provider: runtime.ProviderSerper,
			params: UpsertSearchEngineConnectionParams{
				Config:      map[string]any{},
				Credentials: map[string]any{"apiKey": "  "},
			},
			wantErr: true,
		},
		{
			name:     "SearXNG rejects URL credential",
			provider: runtime.ProviderSearXNG,
			params: UpsertSearchEngineConnectionParams{
				Config:      map[string]any{"baseUrl": "https://user:pass@example.com"},
				Credentials: map[string]any{},
			},
			wantErr: true,
		},
		{
			name:     "API key byte limit",
			provider: runtime.ProviderBrave,
			params: UpsertSearchEngineConnectionParams{
				Config: map[string]any{},
				Credentials: map[string]any{
					"apiKey": strings.Repeat("x", maxSearchEngineAPIKeyBytes+1),
				},
			},
			wantErr: true,
		},
		{
			name:     "SearXNG URL byte limit",
			provider: runtime.ProviderSearXNG,
			params: UpsertSearchEngineConnectionParams{
				Config: map[string]any{
					"baseUrl": "https://example.com/" +
						strings.Repeat("x", maxSearXNGBaseURLBytes),
				},
				Credentials: map[string]any{},
			},
			wantErr: true,
		},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			t.Parallel()
			product := searchEngineProductsByProvider[test.provider]
			prepared, err := prepareSearchEngineConnection(product, test.params)
			if test.wantErr {
				require.Error(t, err)
				require.True(t, errors.Is(err, ErrBadRequest))
				return
			}
			require.NoError(t, err)
			require.Equal(t, test.wantKey, prepared.APIKey)
			require.Equal(t, test.keyProvided, prepared.APIKeyProvided)
			require.Equal(t, test.wantBase, prepared.Config.BaseURL)
			require.Equal(t, test.wantLang, prepared.Config.Language)
		})
	}
}

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

	store := New(nil, Options{AppCredentialStorageKey: testAppCredentialStorageKey()})
	credentials, err := store.marshalAppCredentialsObject(map[string]any{
		"apiKey": "server-only-secret",
	})
	require.NoError(t, err)
	require.NotContains(t, string(credentials), "server-only-secret")
	now := time.Date(2026, 8, 15, 1, 30, 0, 0, time.UTC)
	product := searchEngineProductsByProvider[runtime.ProviderBrave]
	installation := serviceactor.Installation{
		ID:                  "svcinst_brave",
		WorkspaceID:         "wsp_brave",
		ServiceDefinitionID: product.AppID,
		ServiceKind:         serviceactor.KindApp,
		Status:              serviceactor.StatusActive,
	}
	connection := serviceactor.Connection{
		ID:                    "appconn_brave",
		WorkspaceID:           installation.WorkspaceID,
		ServiceDefinitionID:   product.AppID,
		ServiceInstallationID: installation.ID,
		ServiceKind:           serviceactor.KindApp,
		ExternalSubjectRef:    searchEngineConnectionExternalSubjectRef,
		Credentials:           credentials,
		Config:                []byte(`{}`),
		Status:                serviceactor.StatusActive,
		UpdatedAt:             now,
	}

	view, err := store.searchEngineConnectionView(
		installation,
		product,
		[]serviceactor.Connection{connection},
	)
	require.NoError(t, err)
	require.True(t, view.Active)
	require.True(t, view.APIKeyConfigured)
	require.NotContains(t, strings.Join([]string{
		view.Provider,
		view.Config.BaseURL,
		view.Config.Language,
	}, " "), "server-only-secret")
	apiKey, err := store.searchEngineAPIKey(connection)
	require.NoError(t, err)
	require.Equal(t, "server-only-secret", apiKey)
}

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

	product, ok := searchEngineProductForDefinition(
		registry.BraveSearchAppID,
		registry.BraveSearchAppSlug,
	)
	require.True(t, ok)
	require.Equal(t, runtime.ProviderBrave, product.Provider)

	_, ok = searchEngineProductForDefinition(
		registry.BraveSearchAppID,
		registry.SearXNGAppSlug,
	)
	require.False(t, ok)
	_, ok = searchEngineProductForDefinition(
		"app_spoof",
		registry.BraveSearchAppSlug,
	)
	require.False(t, ok)
}

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

	for _, slug := range []string{
		registry.SearXNGAppSlug,
		registry.BraveSearchAppSlug,
		registry.TavilyAppSlug,
		registry.SerperAppSlug,
	} {
		slug := slug
		t.Run(slug, func(t *testing.T) {
			t.Parallel()
			_, err := prepareWorkspaceAppDefinition(
				RequestCustomAppInstallationParams{Slug: slug},
			)
			require.ErrorIs(t, err, ErrBadRequest)
		})
	}
}

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

	product := searchEngineProductsByProvider[runtime.ProviderBrave]
	installation := serviceactor.Installation{
		ID:                  "svcinst_1",
		WorkspaceID:         "wsp_1",
		ServiceDefinitionID: product.AppID,
		ServiceKind:         serviceactor.KindApp,
		Status:              serviceactor.StatusActive,
	}
	active := serviceactor.Connection{
		ID:                    "appconn_1",
		WorkspaceID:           installation.WorkspaceID,
		ServiceDefinitionID:   product.AppID,
		ServiceInstallationID: installation.ID,
		ServiceKind:           serviceactor.KindApp,
		ExternalSubjectRef:    searchEngineConnectionExternalSubjectRef,
		Status:                serviceactor.StatusActive,
	}
	connection, err := requireAuthoritativeSearchEngineConnection(
		installation,
		product,
		[]serviceactor.Connection{active},
	)
	require.NoError(t, err)
	require.Equal(t, active.ID, connection.ID)

	_, err = requireAuthoritativeSearchEngineConnection(
		installation,
		product,
		[]serviceactor.Connection{
			active,
			{
				ID:                 "appconn_2",
				ExternalSubjectRef: "other",
				Status:             serviceactor.StatusActive,
			},
		},
	)
	require.Error(t, err)
	require.True(t, errors.Is(err, ErrConflict))
}

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

	product := searchEngineProductsByProvider[runtime.ProviderBrave]
	installation := serviceactor.Installation{
		ID:                  "svcinst_1",
		WorkspaceID:         "wsp_1",
		ServiceDefinitionID: product.AppID,
		ServiceKind:         serviceactor.KindApp,
		Status:              serviceactor.StatusActive,
	}
	base := serviceactor.Connection{
		ID:                    "appconn_1",
		WorkspaceID:           installation.WorkspaceID,
		ServiceDefinitionID:   product.AppID,
		ServiceInstallationID: installation.ID,
		ServiceKind:           serviceactor.KindApp,
		ExternalSubjectRef:    searchEngineConnectionExternalSubjectRef,
		Status:                serviceactor.StatusActive,
	}
	tests := map[string]func(*serviceactor.Connection){
		"workspace":    func(value *serviceactor.Connection) { value.WorkspaceID = "wsp_other" },
		"definition":   func(value *serviceactor.Connection) { value.ServiceDefinitionID = registry.SearXNGAppID },
		"installation": func(value *serviceactor.Connection) { value.ServiceInstallationID = "svcinst_other" },
		"kind":         func(value *serviceactor.Connection) { value.ServiceKind = "agent" },
	}
	for name, mutate := range tests {
		name := name
		mutate := mutate
		t.Run(name, func(t *testing.T) {
			t.Parallel()
			connection := base
			mutate(&connection)
			_, err := requireAuthoritativeSearchEngineConnection(
				installation,
				product,
				[]serviceactor.Connection{connection},
			)
			require.ErrorIs(t, err, ErrConflict)
		})
	}
}
