package organization

import (
	"net/url"
	"strings"
	"testing"
	"time"

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

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

	feishu := newFeishuHTTPTestServer(t)
	defer feishu.Close()

	runtime := startOrganizationServiceRuntimeWithOptions(t, ServiceOptions{
		FeishuBaseURL:    feishu.URL(),
		FeishuHTTPClient: feishu.Client(),
		FeishuAppID:      feishu.AppID(),
		FeishuAppSecret:  feishu.AppSecret(),
	})
	owner := bootstrapOrganizationAdmin(t, runtime, "owner@example.com", "owner")

	organization, err := runtime.service.CreateOrganizationForUser(runtime.ctx, owner, "Feishu Auth Org", "feishu-auth-org")
	require.NoError(t, err)
	configureFeishuProviderAutoForTests(t, runtime, owner.ID, organization.ID)

	_, err = runtime.service.SyncOrganizationFeishuDirectoryForUser(runtime.ctx, owner.ID, organization.ID)
	require.NoError(t, err)

	redirectURI := "https://agentteam.test/login"
	authorizeURL, err := runtime.service.BuildFeishuLoginURL(runtime.ctx, organization.Slug, redirectURI)
	require.NoError(t, err)

	parsedAuthorizeURL, err := url.Parse(authorizeURL)
	require.NoError(t, err)
	require.Equal(t, feishuAuthRequiredScopeUserID, strings.TrimSpace(parsedAuthorizeURL.Query().Get("scope")))
	state := strings.TrimSpace(parsedAuthorizeURL.Query().Get("state"))
	require.NotEmpty(t, state)

	user, err := runtime.service.AuthenticateFeishuLogin(runtime.ctx, feishu.AuthCode(), state, redirectURI)
	require.NoError(t, err)
	require.NotEmpty(t, user.ID)
	require.True(t, strings.HasSuffix(user.Email, "@directory.invalid"))

	accounts, err := runtime.service.ListOrganizationMembersForUser(runtime.ctx, owner.ID, organization.ID)
	require.NoError(t, err)
	carolAccount := requireAccountByExternalID(t, accounts, "ou-carol")
	require.Equal(t, carolAccount.UserID, user.ID)

	bindings, err := runtime.service.ListOrganizationMemberAuthBindings(runtime.ctx, organization.ID, carolAccount.ID)
	require.NoError(t, err)
	require.Len(t, bindings, 2)
	require.Equal(t, organizationAuthSubjectKeyOpenID, bindings[0].SubjectKey)
	require.Equal(t, "ou-open-carol", bindings[0].SubjectValue)
	require.Equal(t, "tenant-key-1", bindings[0].ProviderTenantID)
	require.Equal(t, organizationAuthSubjectKeyUnionID, bindings[1].SubjectKey)
	require.Equal(t, "on-union-carol", bindings[1].SubjectValue)
	require.Equal(t, "tenant-key-1", bindings[1].ProviderTenantID)

	settings, err := runtime.service.GetOrganizationSettingsForUser(runtime.ctx, owner.ID, organization.ID)
	require.NoError(t, err)
	require.Equal(t, "tenant-key-1", settings.Feishu.TenantKey)
	require.Empty(t, settings.Feishu.RootDepartmentID)
	require.NotNil(t, settings.Feishu.LastSync)
	require.Equal(t, organizationProviderFeishu, settings.Feishu.LastSync.ExternalProvider)
	require.Equal(t, organizationDirectorySyncRunStatusCompleted, settings.Feishu.LastSync.Status)
	require.NotNil(t, settings.Feishu.LastSync.StartedAt)
	require.NotNil(t, settings.Feishu.LastSync.CompletedAt)
	require.Nil(t, settings.Feishu.LastAutoJoin)
}

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

	feishu := newFeishuHTTPTestServer(t)
	defer feishu.Close()

	runtime := startOrganizationServiceRuntimeWithOptions(t, ServiceOptions{
		FeishuBaseURL:    feishu.URL(),
		FeishuHTTPClient: feishu.Client(),
		FeishuAppID:      feishu.AppID(),
		FeishuAppSecret:  feishu.AppSecret(),
	})
	owner := bootstrapOrganizationAdmin(t, runtime, "owner@example.com", "owner")

	organization, err := runtime.service.CreateOrganizationForUser(runtime.ctx, owner, "Feishu Auto Join Org", "feishu-auto-join-org")
	require.NoError(t, err)
	configureFeishuProviderAutoForTests(t, runtime, owner.ID, organization.ID)

	_, err = runtime.service.SyncOrganizationFeishuDirectoryForUser(runtime.ctx, owner.ID, organization.ID)
	require.NoError(t, err)

	accounts, err := runtime.service.ListOrganizationMembersForUser(runtime.ctx, owner.ID, organization.ID)
	require.NoError(t, err)
	carolBefore := requireAccountByExternalID(t, accounts, "ou-carol")

	user := createVerifiedOrganizationUser(t, runtime, "carol@example.com", "carol")

	orgsBefore, err := runtime.service.ListOrganizationsForUser(runtime.ctx, user.ID)
	require.NoError(t, err)
	require.Empty(t, orgsBefore)

	err = runtime.service.AutoJoinOrganizationsForVerifiedEmail(runtime.ctx, user)
	require.NoError(t, err)

	orgsAfter, err := runtime.service.ListOrganizationsForUser(runtime.ctx, user.ID)
	require.NoError(t, err)
	require.Len(t, orgsAfter, 1)
	require.Equal(t, organization.ID, orgsAfter[0].ID)
	defaultWorkspace := requireDefaultOrganizationWorkspace(t, runtime, owner.ID, organization.ID)
	requireUserHasWorkspace(t, runtime, user.ID, defaultWorkspace.ID)

	accounts, err = runtime.service.ListOrganizationMembersForUser(runtime.ctx, owner.ID, organization.ID)
	require.NoError(t, err)
	carolAfter := requireAccountByExternalID(t, accounts, "ou-carol")
	require.NotEqual(t, user.ID, carolBefore.UserID)
	require.Equal(t, user.ID, carolAfter.UserID)

	settings, err := runtime.service.GetOrganizationSettingsForUser(runtime.ctx, owner.ID, organization.ID)
	require.NoError(t, err)
	require.NotNil(t, settings.Feishu.LastSync)
	require.NotNil(t, settings.Feishu.LastAutoJoin)
	require.Equal(t, feishuAutoJoinStatusSuccess, settings.Feishu.LastAutoJoin.Status)
	require.Equal(t, feishuAutoJoinSubjectTypeEmail, settings.Feishu.LastAutoJoin.SubjectType)
	require.Equal(t, "ca***@example.com", settings.Feishu.LastAutoJoin.SubjectHint)
	require.NotNil(t, settings.Feishu.LastAutoJoin.AttemptedAt)
	require.Contains(t, settings.Feishu.LastAutoJoin.Message, "joined")
}

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

	feishu := newFeishuHTTPTestServer(t)
	defer feishu.Close()

	runtime := startOrganizationServiceRuntimeWithOptions(t, ServiceOptions{
		FeishuBaseURL:    feishu.URL(),
		FeishuHTTPClient: feishu.Client(),
		FeishuAppID:      feishu.AppID(),
		FeishuAppSecret:  feishu.AppSecret(),
	})
	owner := bootstrapOrganizationAdmin(t, runtime, "owner@example.com", "owner")

	organization, err := runtime.service.CreateOrganizationForUser(runtime.ctx, owner, "Feishu Mobile Auto Join Org", "feishu-mobile-auto-join-org")
	require.NoError(t, err)
	configureFeishuProviderAutoForTests(t, runtime, owner.ID, organization.ID)

	_, err = runtime.service.SyncOrganizationFeishuDirectoryForUser(runtime.ctx, owner.ID, organization.ID)
	require.NoError(t, err)

	accounts, err := runtime.service.ListOrganizationMembersForUser(runtime.ctx, owner.ID, organization.ID)
	require.NoError(t, err)
	carolBefore := requireAccountByExternalID(t, accounts, "ou-carol")

	user := createMobileVerifiedOrganizationUser(t, runtime, "carol-mobile@example.com", "carol-mobile", "+8613800000000")

	orgsBefore, err := runtime.service.ListOrganizationsForUser(runtime.ctx, user.ID)
	require.NoError(t, err)
	require.Empty(t, orgsBefore)

	err = runtime.service.AutoJoinOrganizationsForVerifiedMobile(runtime.ctx, user)
	require.NoError(t, err)

	orgsAfter, err := runtime.service.ListOrganizationsForUser(runtime.ctx, user.ID)
	require.NoError(t, err)
	require.Len(t, orgsAfter, 1)
	require.Equal(t, organization.ID, orgsAfter[0].ID)
	defaultWorkspace := requireDefaultOrganizationWorkspace(t, runtime, owner.ID, organization.ID)
	requireUserHasWorkspace(t, runtime, user.ID, defaultWorkspace.ID)

	accounts, err = runtime.service.ListOrganizationMembersForUser(runtime.ctx, owner.ID, organization.ID)
	require.NoError(t, err)
	carolAfter := requireAccountByExternalID(t, accounts, "ou-carol")
	require.NotEqual(t, user.ID, carolBefore.UserID)
	require.Equal(t, user.ID, carolAfter.UserID)

	settings, err := runtime.service.GetOrganizationSettingsForUser(runtime.ctx, owner.ID, organization.ID)
	require.NoError(t, err)
	require.NotNil(t, settings.Feishu.LastAutoJoin)
	require.Equal(t, feishuAutoJoinStatusSuccess, settings.Feishu.LastAutoJoin.Status)
	require.Equal(t, feishuAutoJoinSubjectTypeMobile, settings.Feishu.LastAutoJoin.SubjectType)
	require.Equal(t, "+8613*******00", settings.Feishu.LastAutoJoin.SubjectHint)
	require.NotNil(t, settings.Feishu.LastAutoJoin.AttemptedAt)
	require.Contains(t, settings.Feishu.LastAutoJoin.Message, "joined")
}

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

	feishu := newFeishuHTTPTestServer(t)
	defer feishu.Close()

	runtime := startOrganizationServiceRuntimeWithOptions(t, ServiceOptions{
		FeishuBaseURL:    feishu.URL(),
		FeishuHTTPClient: feishu.Client(),
		FeishuAppID:      feishu.AppID(),
		FeishuAppSecret:  feishu.AppSecret(),
	})
	owner := bootstrapOrganizationAdmin(t, runtime, "owner@example.com", "owner")

	firstOrg, err := runtime.service.CreateOrganizationForUser(runtime.ctx, owner, "Feishu Auto Join Org A", "feishu-auto-join-org-a")
	require.NoError(t, err)
	configureFeishuProviderAutoForTests(t, runtime, owner.ID, firstOrg.ID)
	_, err = runtime.service.SyncOrganizationFeishuDirectoryForUser(runtime.ctx, owner.ID, firstOrg.ID)
	require.NoError(t, err)

	secondOrg, err := runtime.service.CreateOrganizationForUser(runtime.ctx, owner, "Feishu Auto Join Org B", "feishu-auto-join-org-b")
	require.NoError(t, err)
	configureFeishuProviderAutoForTests(t, runtime, owner.ID, secondOrg.ID)
	_, err = runtime.service.SyncOrganizationFeishuDirectoryForUser(runtime.ctx, owner.ID, secondOrg.ID)
	require.NoError(t, err)

	user := createVerifiedOrganizationUser(t, runtime, "carol@example.com", "carol")

	err = runtime.service.AutoJoinOrganizationsForVerifiedEmail(runtime.ctx, user)
	require.NoError(t, err)

	orgs, err := runtime.service.ListOrganizationsForUser(runtime.ctx, user.ID)
	require.NoError(t, err)
	require.Empty(t, orgs)
}

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

	feishu := newFeishuHTTPTestServer(t)
	defer feishu.Close()

	runtime := startOrganizationServiceRuntimeWithOptions(t, ServiceOptions{
		FeishuBaseURL:    feishu.URL(),
		FeishuHTTPClient: feishu.Client(),
		FeishuAppID:      feishu.AppID(),
		FeishuAppSecret:  feishu.AppSecret(),
	})
	owner := bootstrapOrganizationAdmin(t, runtime, "owner@example.com", "owner")

	organization, err := runtime.service.CreateOrganizationForUser(runtime.ctx, owner, "Feishu Auto Join Org", "feishu-auto-join-unverified")
	require.NoError(t, err)
	configureFeishuProviderAutoForTests(t, runtime, owner.ID, organization.ID)

	_, err = runtime.service.SyncOrganizationFeishuDirectoryForUser(runtime.ctx, owner.ID, organization.ID)
	require.NoError(t, err)

	user, err := runtime.store.Identity.RegisterUser(runtime.ctx, "carol@example.com", "carol", "carol", "hash", "verification-token-hash", time.Now().UTC().Add(time.Hour))
	require.NoError(t, err)

	err = runtime.service.AutoJoinOrganizationsForVerifiedEmail(runtime.ctx, user)
	require.NoError(t, err)

	orgs, err := runtime.service.ListOrganizationsForUser(runtime.ctx, user.ID)
	require.NoError(t, err)
	require.Empty(t, orgs)
}

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

	feishu := newFeishuHTTPTestServer(t)
	defer feishu.Close()

	runtime := startOrganizationServiceRuntimeWithOptions(t, ServiceOptions{
		FeishuBaseURL:    feishu.URL(),
		FeishuHTTPClient: feishu.Client(),
		FeishuAppID:      feishu.AppID(),
		FeishuAppSecret:  feishu.AppSecret(),
	})
	owner := bootstrapOrganizationAdmin(t, runtime, "owner@example.com", "owner")

	organization, err := runtime.service.CreateOrganizationForUser(runtime.ctx, owner, "Feishu Explain Org", "feishu-explain-org")
	require.NoError(t, err)
	configureFeishuProviderAutoForTests(t, runtime, owner.ID, organization.ID)

	user := createVerifiedOrganizationUser(t, runtime, "carol@example.com", "carol")

	err = runtime.service.AutoJoinOrganizationsForVerifiedEmail(runtime.ctx, user)
	require.NoError(t, err)

	settings, err := runtime.service.GetOrganizationSettingsForUser(runtime.ctx, owner.ID, organization.ID)
	require.NoError(t, err)
	require.Nil(t, settings.Feishu.LastSync)
	require.NotNil(t, settings.Feishu.LastAutoJoin)
	require.Equal(t, feishuAutoJoinStatusDirectoryUserNotImported, settings.Feishu.LastAutoJoin.Status)
	require.Equal(t, feishuAutoJoinSubjectTypeEmail, settings.Feishu.LastAutoJoin.SubjectType)
	require.Equal(t, "ca***@example.com", settings.Feishu.LastAutoJoin.SubjectHint)
	require.NotNil(t, settings.Feishu.LastAutoJoin.AttemptedAt)
	require.Contains(t, settings.Feishu.LastAutoJoin.Message, "Run Feishu sync first")
}
