package server

import (
	"context"
	"errors"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/internal/config"
	"github.com/tidbcloud/AgentTeam/internal/extension/providers"
	"github.com/tidbcloud/AgentTeam/internal/extension/providers/mail/mailruntime"
	"github.com/tidbcloud/AgentTeam/internal/extension/providers/searchengine/runtime"
	"github.com/tidbcloud/AgentTeam/internal/extension/runner"
	"github.com/tidbcloud/AgentTeam/internal/router"
	"github.com/tidbcloud/AgentTeam/internal/service/prwatch"
	"github.com/tidbcloud/AgentTeam/internal/workspace/owner"
)

// TestRouterProfileForConfig_IncludesExtension asserts the config string maps to
// the typed extension profile, and unknown profiles fail closed.
func TestRouterProfileForConfig_IncludesExtension(t *testing.T) {
	t.Parallel()
	got, err := routerProfileForConfig(config.ServerProfileExtension)
	require.NoError(t, err)
	require.Equal(t, router.ProfileExtension, got)

	for _, profile := range []string{config.ServerProfileAll, config.ServerProfileCentral, config.ServerProfileWorkspace} {
		_, err := routerProfileForConfig(profile)
		require.NoError(t, err, "existing profile %q must still map", profile)
	}
	_, err = routerProfileForConfig("nope")
	require.Error(t, err, "unknown profile must fail closed")
}

// TestProfileRunsWorkspaceBackgroundLoops_ExcludesExtension asserts the
// extension profile does NOT inherit the workspace data-plane background loops
// (spec §6.1). Only all and workspace run them.
func TestProfileRunsWorkspaceBackgroundLoops_ExcludesExtension(t *testing.T) {
	t.Parallel()
	require.True(t, (&Server{profile: router.ProfileAll}).runsWorkspaceBackgroundLoops())
	require.True(t, (&Server{profile: router.ProfileWorkspace}).runsWorkspaceBackgroundLoops())
	require.False(t, (&Server{profile: router.ProfileCentral}).runsWorkspaceBackgroundLoops())
	require.False(t, (&Server{profile: router.ProfileExtension}).runsWorkspaceBackgroundLoops())
}

// TestProfileRunsPlatformServiceAccounts_ExcludesExtension asserts the extension
// profile runs no platform workers.
func TestProfileRunsPlatformServiceAccounts_ExcludesExtension(t *testing.T) {
	t.Parallel()
	require.True(t, profileRunsPlatformServiceAccounts(router.ProfileAll))
	require.True(t, profileRunsPlatformServiceAccounts(router.ProfileCentral))
	require.True(t, profileRunsPlatformServiceAccounts(router.ProfileWorkspace))
	require.False(t, profileRunsPlatformServiceAccounts(router.ProfileExtension))
}

// TestProfileComposesMeshX_ExcludesExtension asserts the extension profile does
// not embed the MeshX runtime in-process (spec §6.1).
func TestProfileComposesMeshX_ExcludesExtension(t *testing.T) {
	t.Parallel()
	require.True(t, profileComposesMeshX(router.ProfileAll, true))
	require.True(t, profileComposesMeshX(router.ProfileWorkspace, true))
	require.False(t, profileComposesMeshX(router.ProfileCentral, true))
	require.False(t, profileComposesMeshX(router.ProfileExtension, true))
	// Disabled config wins for every profile.
	require.False(t, profileComposesMeshX(router.ProfileWorkspace, false))
}

// TestManagedWorkspaceSetForProfile_ExtensionIsEmpty asserts the extension
// profile owns no workspaces (it is a binding/Catalog runtime, not a workspace
// data-plane).
func TestManagedWorkspaceSetForProfile_ExtensionIsEmpty(t *testing.T) {
	t.Parallel()
	set, err := managedWorkspaceSetForProfile(context.Background(), router.ProfileExtension, "", nil)
	require.NoError(t, err)
	require.NotNil(t, set)
	// An empty set rejects every workspace ID (fail-closed ownership).
	require.Error(t, owner.Require(set, "ws_any"))
}

// TestProfileOwnsWorkspaceDataPlane_ExcludesExtension asserts the wire-layer
// helper excludes central AND extension, so all workspace-sharded worker
// constructors short-circuit for both.
func TestProfileOwnsWorkspaceDataPlane_ExcludesExtension(t *testing.T) {
	t.Parallel()
	for _, profile := range []string{config.ServerProfileAll, config.ServerProfileWorkspace} {
		require.True(t, profileOwnsWorkspaceDataPlane(config.ServerConfig{Profile: profile}), profile)
	}
	for _, profile := range []string{config.ServerProfileCentral, config.ServerProfileExtension} {
		require.False(t, profileOwnsWorkspaceDataPlane(config.ServerConfig{Profile: profile}), profile)
	}
}

// TestWorkspaceWorkers_NotConstructedForExtension asserts the workspace
// data-plane workers are NOT constructed under the extension profile (they are
// nil), so Server.Run cannot start them.
func TestWorkspaceWorkers_NotConstructedForExtension(t *testing.T) {
	t.Parallel()
	cfg := config.Config{Server: config.ServerConfig{Profile: config.ServerProfileExtension}}

	unread, err := newUnreadRuntimeForConfig(cfg, nil, nil)
	require.NoError(t, err)
	assert.Nil(t, unread, "unread runtime must not be constructed for extension")

	invocationSchedule, err := newWorkflowInvocationScheduleWorkerForConfig(cfg, nil, nil)
	require.NoError(t, err)
	assert.Nil(t, invocationSchedule, "workflow invocation schedule worker must not be constructed for extension")

	runWorker, err := newWorkflowRunWorkerForConfig(cfg, nil, nil, nil, nil, nil, nil, nil, nil)
	require.NoError(t, err)
	assert.Nil(t, runWorker, "workflow run worker must not be constructed for extension")

	assetWorker, err := newMeshXAssetAccessWorkerForConfig(cfg, "https://meshx.example.com", nil, nil, nil, nil)
	require.NoError(t, err)
	assert.Nil(t, assetWorker, "meshx asset access worker must not be constructed for extension")
}

// TestRoleManagementWorkspaceIDs_NotConstructedForExtension asserts role
// management workspace reconciliation is not constructed under extension.
func TestRoleManagementWorkspaceIDs_NotConstructedForExtension(t *testing.T) {
	t.Parallel()
	cfg := config.Config{
		Server: config.ServerConfig{Profile: config.ServerProfileExtension},
		MeshX:  config.MeshXConfig{Enabled: true},
	}
	source, err := roleManagementWorkspaceIDsForConfig(cfg, nil, nil)
	require.NoError(t, err)
	require.Nil(t, source, "role management must not run for extension")
}

// TestExtensionRegistryForProfile_SkipsBootstrapForCentralWorkspace is the
// adversarial insulation assertion (spec §6.1): central and workspace never
// register Extension routes, so they skip the Extension Registry bootstrap
// entirely and return a nil Registry — they cannot be affected by a broken
// embedded Extension package. all and extension still bootstrap it.
func TestExtensionRegistryForProfile_SkipsBootstrapForCentralWorkspace(t *testing.T) {
	t.Parallel()
	for _, profile := range []router.Profile{router.ProfileCentral, router.ProfileWorkspace} {
		reg, err := extensionRegistryForProfile(profile, providers.Deps{})
		require.NoErrorf(t, err, "%s profile must skip the registry bootstrap without error", profile)
		require.Nilf(t, reg, "%s profile must not bootstrap the extension registry", profile)
	}
	for _, profile := range []router.Profile{router.ProfileAll, router.ProfileExtension} {
		reg, err := extensionRegistryForProfile(
			profile,
			providers.Deps{
				WatchService:  prwatch.New(nil),
				SearchService: runtime.New(nil),
				Credentials:   stubProviderCredentialSource{},
				Connections:   stubProviderCredentialSource{},
				MailService:   mailruntime.New(nil, nil, mailruntime.Options{}),
			},
		)
		require.NoErrorf(t, err, "%s profile must bootstrap the registry successfully", profile)
		require.NotNilf(t, reg, "%s profile must bootstrap the extension registry", profile)
		for _, domain := range []string{
			"github",
			"gitea",
			"mail",
			"searxng",
			"brave",
			"tavily",
			"serper",
			"arxiv",
			"jenkins",
			"todoist",
			"notion",
			"hackernews",
			"ones",
			"hotnews",
			"feishucustombot",
			"dingtalkbot",
			"wecombot",
			"tianyancha",
			"qichacha",
			"tushare",
			"alpha-vantage",
			"financial-modeling-prep",
			"jin10",
			"pkulaw",
			"aifinmarket",
		} {
			registered, ok := reg.LookupByIdentity(domain, 1)
			require.Truef(t, ok, "%s profile must register %s", profile, domain)
			require.Equal(t, domain, registered.Definition.Domain)
		}
		_, hasSharedAlias := reg.LookupByIdentity("web-search", 1)
		require.False(t, hasSharedAlias)
	}
}

// stubProviderCredentialSource satisfies the runtime credential seam for
// profile bootstrap tests; no provider action is invoked there.
type stubProviderCredentialSource struct{}

func (stubProviderCredentialSource) ResolveProviderCredentials(
	context.Context, string, string,
) (runner.Credentials, error) {
	return nil, errors.New("stub credential source is not invocable in profile tests")
}

func (stubProviderCredentialSource) ResolveProviderConnection(
	context.Context,
	string,
	string,
	runner.ConnectionConfigContract,
) (runner.ResolvedConnection, error) {
	return runner.ResolvedConnection{},
		errors.New("stub connection source is not invocable in profile tests")
}
