package server

import (
	"context"
	"errors"
	"testing"

	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/internal/api/auth"
	workspaceapi "github.com/tidbcloud/AgentTeam/internal/api/workspace"
	"github.com/tidbcloud/AgentTeam/internal/core/authidentity"
	"github.com/tidbcloud/AgentTeam/internal/service/authflow"
	"github.com/tidbcloud/AgentTeam/internal/store/identity"
	"github.com/tidbcloud/AgentTeam/internal/store/organization"
	"github.com/tidbcloud/AgentTeam/internal/store/workspace"
)

type recordedWorkspaceSummaryInvalidation struct {
	userID      string
	workspaceID string
}

type fakeWorkspaceSummaryInvalidator struct {
	precise    []recordedWorkspaceSummaryInvalidation
	workspaces []string
	users      []string
	orgs       []string
	clears     int
}

type fakeWorkspaceMembershipRealtime struct {
	workspaceIDs []string
}

func (realtime *fakeWorkspaceMembershipRealtime) EmitWorkspaceMembersUpdated(workspaceID string) {
	realtime.workspaceIDs = append(realtime.workspaceIDs, workspaceID)
}

func (invalidator *fakeWorkspaceSummaryInvalidator) Invalidate(userID string, workspaceID string) {
	invalidator.precise = append(invalidator.precise, recordedWorkspaceSummaryInvalidation{userID: userID, workspaceID: workspaceID})
}

func (invalidator *fakeWorkspaceSummaryInvalidator) InvalidateWorkspace(workspaceID string) {
	invalidator.workspaces = append(invalidator.workspaces, workspaceID)
}

func (invalidator *fakeWorkspaceSummaryInvalidator) InvalidateUser(userID string) {
	invalidator.users = append(invalidator.users, userID)
}

func (invalidator *fakeWorkspaceSummaryInvalidator) InvalidateOrganization(organizationID string) {
	invalidator.orgs = append(invalidator.orgs, organizationID)
}

func (invalidator *fakeWorkspaceSummaryInvalidator) Clear() {
	invalidator.clears++
}

func TestWorkspaceSummaryInvalidatingWorkspaceServiceInvalidatesSuccessfulMutations(t *testing.T) {
	next := &fakeInvalidatingWorkspaceService{}
	invalidator := &fakeWorkspaceSummaryInvalidator{}
	service := workspaceSummaryInvalidatingWorkspaceService{WorkspaceService: next, invalidator: invalidator}

	summary, err := service.UpdateWorkspaceNameForUser(context.Background(), "actor", "wsp_old", "Renamed")
	require.NoError(t, err)
	require.Equal(t, "wsp_returned", summary.WorkspaceID)
	require.NoError(t, service.UpdateWorkspaceMemberRoleForUser(context.Background(), "actor", "wsp_role", "target_role", "admin"))
	_, err = service.RemoveWorkspaceMemberForUser(context.Background(), "actor", "wsp_remove", "target_remove")
	require.NoError(t, err)

	require.Equal(t, []string{"wsp_returned"}, invalidator.workspaces)
	require.Equal(t, []recordedWorkspaceSummaryInvalidation{
		{userID: "target_role", workspaceID: "wsp_role"},
		{userID: "target_remove", workspaceID: "wsp_remove"},
	}, invalidator.precise)
}

func TestWorkspaceSummaryInvalidatingWorkspaceServiceDoesNotInvalidateFailedMutations(t *testing.T) {
	boom := errors.New("boom")
	next := &fakeInvalidatingWorkspaceService{nameErr: boom, roleErr: boom, removeErr: boom}
	invalidator := &fakeWorkspaceSummaryInvalidator{}
	service := workspaceSummaryInvalidatingWorkspaceService{WorkspaceService: next, invalidator: invalidator}

	_, err := service.UpdateWorkspaceNameForUser(context.Background(), "actor", "wsp", "Renamed")
	require.ErrorIs(t, err, boom)
	require.ErrorIs(t, service.UpdateWorkspaceMemberRoleForUser(context.Background(), "actor", "wsp", "target", "admin"), boom)
	_, err = service.RemoveWorkspaceMemberForUser(context.Background(), "actor", "wsp", "target")
	require.ErrorIs(t, err, boom)

	require.Empty(t, invalidator.workspaces)
	require.Empty(t, invalidator.precise)
}

func TestWorkspaceSummaryInvalidatingAuthStoreInvalidatesDisplayNameChanges(t *testing.T) {
	invalidator := &fakeWorkspaceSummaryInvalidator{}
	store := workspaceSummaryInvalidatingAuthStore{Store: &fakeInvalidatingAuthStore{}, invalidator: invalidator}

	user, err := store.UpdateUserDisplayName(context.Background(), "usr_profile", "Ada")
	require.NoError(t, err)
	require.Equal(t, "usr_profile", user.ID)
	require.Equal(t, []string{"usr_profile"}, invalidator.users)
	require.Equal(t, 1, invalidator.clears)
}

func TestWorkspaceSummaryInvalidatingAuthFlowInvalidatesInviteMembershipChanges(t *testing.T) {
	invalidator := &fakeWorkspaceSummaryInvalidator{}
	realtime := &fakeWorkspaceMembershipRealtime{}
	organizations := &fakeInvalidatingOrganizationProvider{}
	flow := workspaceSummaryInvalidatingAuthFlow{AuthFlow: &fakeInvalidatingAuthFlow{
		acceptance: authflow.InviteAcceptance{Kind: authflow.InviteKindWorkspace, Workspace: workspace.Summary{WorkspaceID: "wsp_invite"}},
		found:      true,
	}, invalidator: invalidator, organizations: organizations, realtime: realtime}

	_, found, err := flow.AcceptInviteByToken(context.Background(), identity.User{ID: "usr_invite"}, "token")
	require.NoError(t, err)
	require.True(t, found)
	require.Equal(t, []recordedWorkspaceSummaryInvalidation{{userID: "usr_invite", workspaceID: "wsp_invite"}}, invalidator.precise)

	flow.AuthFlow = &fakeInvalidatingAuthFlow{
		acceptance: authflow.InviteAcceptance{Kind: authflow.InviteKindOrganization, Organization: organization.OrganizationInviteAcceptance{OrganizationID: "org_invite"}},
		found:      true,
	}
	_, found, err = flow.AcceptPendingInviteByID(context.Background(), identity.User{ID: "usr_org_invite"}, "invite")
	require.NoError(t, err)
	require.True(t, found)
	require.Equal(t, []string{"usr_org_invite"}, invalidator.users)
	require.Equal(t, []string{"wsp_org_a", "wsp_org_b"}, realtime.workspaceIDs)

	flow.AuthFlow = &fakeInvalidatingAuthFlow{onAutoJoin: func() {
		organizations.visibleOrganizations = []organization.OrganizationSummary{{ID: "org_autojoin"}}
	}}
	require.NoError(t, flow.AutoJoinOrganizationsAfterVerifiedLogin(context.Background(), identity.User{ID: "usr_autojoin"}, "email"))
	require.Equal(t, []string{"usr_org_invite", "usr_autojoin"}, invalidator.users)
	require.Equal(t, []string{
		"wsp_org_a", "wsp_org_b", // explicit organization invite acceptance
		"wsp_org_a", "wsp_org_b", // newly joined organization after login
	}, realtime.workspaceIDs)
}

func TestWorkspaceSummaryInvalidatingOrganizationProviderInvalidatesMembershipAndGrantMutations(t *testing.T) {
	invalidator := &fakeWorkspaceSummaryInvalidator{}
	realtime := &fakeWorkspaceMembershipRealtime{}
	provider := workspaceSummaryInvalidatingOrganizationProvider{
		workspaceSummaryOrganizationProvider: &fakeInvalidatingOrganizationProvider{},
		invalidator:                          invalidator,
		realtime:                             realtime,
	}

	_, err := provider.CreateOrganizationMemberForUser(context.Background(), "admin", "org_1", "team", "ada@example.com", "member")
	require.NoError(t, err)
	_, err = provider.GrantOrganizationMemberWorkspaceAccessForUser(context.Background(), "admin", "org_1", "wsp_direct", "orgm_direct", "admin")
	require.NoError(t, err)
	require.NoError(t, provider.RevokeOrganizationMemberWorkspaceAccessForUser(context.Background(), "admin", "org_1", "wsp_direct", "orgm_direct"))
	_, err = provider.GrantOrganizationTeamWorkspaceAccessForUser(context.Background(), "admin", "org_1", "wsp_team", "team", "member")
	require.NoError(t, err)
	require.NoError(t, provider.RevokeOrganizationTeamWorkspaceAccessForUser(context.Background(), "admin", "org_1", "wsp_team", "team"))
	_, err = provider.AddOrganizationTeamMemberForUser(context.Background(), "admin", "org_1", "team", organization.AddOrganizationTeamMemberParams{MemberID: "orgm_team"})
	require.NoError(t, err)
	require.NoError(t, provider.RemoveOrganizationTeamMemberForUser(context.Background(), "admin", "org_1", "team", "orgm_team"))
	_, err = provider.AddOrganizationUnitMemberForUser(context.Background(), "admin", "org_1", "unit", organization.AddOrganizationUnitMemberParams{MemberID: "orgm_unit"})
	require.NoError(t, err)
	require.NoError(t, provider.RemoveOrganizationUnitMemberForUser(context.Background(), "admin", "org_1", "unit", "orgm_unit"))
	_, err = provider.SwitchOrganizationMemberPrimaryTeamForUser(context.Background(), "admin", "org_1", "orgm_switch", "team_next")
	require.NoError(t, err)
	_, err = provider.RemoveOrganizationMemberForUser(context.Background(), "admin", "org_1", "orgm_removed")
	require.NoError(t, err)
	_, err = provider.SyncOrganizationFeishuDirectoryForUser(context.Background(), "admin", "org_1")
	require.NoError(t, err)

	require.Equal(t, []string{"usr_created", "usr_team", "usr_unit", "usr_switch"}, invalidator.users)
	require.Equal(t, []recordedWorkspaceSummaryInvalidation{{userID: "usr_direct", workspaceID: "wsp_direct"}}, invalidator.precise)
	require.Equal(t, []string{"wsp_direct", "wsp_team", "wsp_team"}, invalidator.workspaces)
	require.Equal(t, []string{"org_1", "org_1", "org_1", "org_1"}, invalidator.orgs)
	require.Equal(t, []string{
		"wsp_org_a", "wsp_org_b", // member create
		"wsp_direct", "wsp_direct", // direct grant/revoke
		"wsp_team", "wsp_team", // team grant/revoke
		"wsp_org_a", "wsp_org_b", // team member add
		"wsp_org_a", "wsp_org_b", // team member remove
		"wsp_org_a", "wsp_org_b", // unit member add
		"wsp_org_a", "wsp_org_b", // unit member remove
		"wsp_org_a", "wsp_org_b", // primary team switch
		"wsp_org_a", "wsp_org_b", // organization member remove
		"wsp_org_a", "wsp_org_b", // Feishu directory sync
	}, realtime.workspaceIDs)
}

func TestWorkspaceSummaryInvalidatingOrganizationProviderDoesNotEmitFailedMembershipMutations(t *testing.T) {
	boom := errors.New("boom")
	invalidator := &fakeWorkspaceSummaryInvalidator{}
	realtime := &fakeWorkspaceMembershipRealtime{}
	provider := workspaceSummaryInvalidatingOrganizationProvider{
		workspaceSummaryOrganizationProvider: &fakeInvalidatingOrganizationProvider{
			memberGrantErr: boom,
			teamMemberErr:  boom,
		},
		invalidator: invalidator,
		realtime:    realtime,
	}

	_, err := provider.GrantOrganizationMemberWorkspaceAccessForUser(context.Background(), "admin", "org_1", "wsp_direct", "orgm_direct", "admin")
	require.ErrorIs(t, err, boom)
	_, err = provider.AddOrganizationTeamMemberForUser(context.Background(), "admin", "org_1", "team", organization.AddOrganizationTeamMemberParams{MemberID: "orgm_team"})
	require.ErrorIs(t, err, boom)

	require.Empty(t, realtime.workspaceIDs)
	require.Empty(t, invalidator.precise)
	require.Empty(t, invalidator.users)
}

type fakeInvalidatingWorkspaceService struct {
	workspaceapi.WorkspaceService
	nameErr   error
	roleErr   error
	removeErr error
}

func (service *fakeInvalidatingWorkspaceService) UpdateWorkspaceNameForUser(context.Context, string, string, string) (workspace.Summary, error) {
	return workspace.Summary{WorkspaceID: "wsp_returned"}, service.nameErr
}

func (service *fakeInvalidatingWorkspaceService) UpdateWorkspaceMemberRoleForUser(context.Context, string, string, string, string) error {
	return service.roleErr
}

func (service *fakeInvalidatingWorkspaceService) RemoveWorkspaceMemberForUser(context.Context, string, string, string) ([]string, error) {
	return []string{"machine"}, service.removeErr
}

type fakeInvalidatingAuthStore struct {
	auth.Store
	err error
}

func (store *fakeInvalidatingAuthStore) UpdateUserDisplayName(_ context.Context, userID string, displayName string) (identity.User, error) {
	return identity.User{ID: userID, DisplayName: displayName}, store.err
}

type fakeInvalidatingAuthFlow struct {
	auth.AuthFlow
	acceptance authflow.InviteAcceptance
	found      bool
	err        error
	autoErr    error
	onAutoJoin func()
}

func (flow *fakeInvalidatingAuthFlow) AcceptInviteByToken(context.Context, identity.User, string) (authflow.InviteAcceptance, bool, error) {
	return flow.acceptance, flow.found, flow.err
}

func (flow *fakeInvalidatingAuthFlow) AcceptPendingInviteByID(context.Context, identity.User, string) (authflow.InviteAcceptance, bool, error) {
	return flow.acceptance, flow.found, flow.err
}

func (flow *fakeInvalidatingAuthFlow) AutoJoinOrganizationsAfterVerifiedLogin(context.Context, identity.User, authidentity.Kind) error {
	if flow.onAutoJoin != nil {
		flow.onAutoJoin()
	}
	return flow.autoErr
}

type fakeInvalidatingOrganizationProvider struct {
	workspaceSummaryOrganizationProvider
	memberGrantErr       error
	teamMemberErr        error
	visibleOrganizations []organization.OrganizationSummary
}

func (provider *fakeInvalidatingOrganizationProvider) ListOrganizationsForUser(context.Context, string) ([]organization.OrganizationSummary, error) {
	return provider.visibleOrganizations, nil
}

func (provider *fakeInvalidatingOrganizationProvider) ListOrganizationWorkspacesForUser(context.Context, string, string) ([]organization.OrganizationWorkspaceView, error) {
	return []organization.OrganizationWorkspaceView{
		{ID: "wsp_org_a"},
		{ID: "wsp_org_b"},
		{ID: "wsp_org_a"},
		{ID: "  "},
	}, nil
}

func (provider *fakeInvalidatingOrganizationProvider) CreateOrganizationMemberForUser(context.Context, string, string, string, string, string) (organization.OrganizationMemberView, error) {
	return organization.OrganizationMemberView{UserID: "usr_created"}, nil
}

func (provider *fakeInvalidatingOrganizationProvider) GrantOrganizationMemberWorkspaceAccessForUser(_ context.Context, _ string, _ string, workspaceID string, _ string, _ string) (organization.OrganizationWorkspaceGrantView, error) {
	return organization.OrganizationWorkspaceGrantView{WorkspaceID: workspaceID, UserID: "usr_direct"}, provider.memberGrantErr
}

func (provider *fakeInvalidatingOrganizationProvider) RevokeOrganizationMemberWorkspaceAccessForUser(context.Context, string, string, string, string) error {
	return nil
}

func (provider *fakeInvalidatingOrganizationProvider) GrantOrganizationTeamWorkspaceAccessForUser(_ context.Context, _ string, _ string, workspaceID string, _ string, _ string) (organization.OrganizationWorkspaceTeamGrantView, error) {
	return organization.OrganizationWorkspaceTeamGrantView{WorkspaceID: workspaceID}, nil
}

func (provider *fakeInvalidatingOrganizationProvider) RevokeOrganizationTeamWorkspaceAccessForUser(context.Context, string, string, string, string) error {
	return nil
}

func (provider *fakeInvalidatingOrganizationProvider) AddOrganizationTeamMemberForUser(context.Context, string, string, string, organization.AddOrganizationTeamMemberParams) (organization.OrganizationTeamMemberView, error) {
	return organization.OrganizationTeamMemberView{UserID: "usr_team"}, provider.teamMemberErr
}

func (provider *fakeInvalidatingOrganizationProvider) RemoveOrganizationTeamMemberForUser(context.Context, string, string, string, string) error {
	return nil
}

func (provider *fakeInvalidatingOrganizationProvider) AddOrganizationUnitMemberForUser(context.Context, string, string, string, organization.AddOrganizationUnitMemberParams) (organization.OrganizationUnitMemberView, error) {
	return organization.OrganizationUnitMemberView{UserID: "usr_unit"}, nil
}

func (provider *fakeInvalidatingOrganizationProvider) RemoveOrganizationUnitMemberForUser(context.Context, string, string, string, string) error {
	return nil
}

func (provider *fakeInvalidatingOrganizationProvider) SwitchOrganizationMemberPrimaryTeamForUser(context.Context, string, string, string, string) (organization.OrganizationMemberView, error) {
	return organization.OrganizationMemberView{UserID: "usr_switch"}, nil
}

func (provider *fakeInvalidatingOrganizationProvider) RemoveOrganizationMemberForUser(context.Context, string, string, string) (organization.OrganizationMemberRemovalResult, error) {
	return organization.OrganizationMemberRemovalResult{MemberID: "orgm_removed", Status: "inactive"}, nil
}

func (provider *fakeInvalidatingOrganizationProvider) SyncOrganizationFeishuDirectoryForUser(context.Context, string, string) (organization.OrganizationFeishuDirectoryImportResult, error) {
	return organization.OrganizationFeishuDirectoryImportResult{}, nil
}
