package server

import (
	"context"
	"strings"

	"github.com/tidbcloud/AgentTeam/internal/api/auth"
	"github.com/tidbcloud/AgentTeam/internal/api/org"
	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 workspaceSummaryCacheInvalidator interface {
	Invalidate(userID string, workspaceID string)
	InvalidateWorkspace(workspaceID string)
	InvalidateUser(userID string)
	InvalidateOrganization(organizationID string)
	Clear()
}

type workspaceMembershipRealtime interface {
	EmitWorkspaceMembersUpdated(workspaceID string)
}

func withWorkspaceSummaryInvalidatingWorkspaceService(next workspaceapi.WorkspaceService, invalidator workspaceSummaryCacheInvalidator) workspaceapi.WorkspaceService {
	if next == nil || invalidator == nil {
		return next
	}
	return workspaceSummaryInvalidatingWorkspaceService{WorkspaceService: next, invalidator: invalidator}
}

func withWorkspaceSummaryInvalidatingAuthStore(next auth.Store, invalidator workspaceSummaryCacheInvalidator) auth.Store {
	if next == nil || invalidator == nil {
		return next
	}
	return workspaceSummaryInvalidatingAuthStore{Store: next, invalidator: invalidator}
}

type workspaceMembershipOrganizationReader interface {
	ListOrganizationsForUser(ctx context.Context, userID string) ([]organization.OrganizationSummary, error)
	ListOrganizationWorkspacesForUser(ctx context.Context, userID string, organizationID string) ([]organization.OrganizationWorkspaceView, error)
}

func withWorkspaceSummaryInvalidatingAuthFlow(next auth.AuthFlow, invalidator workspaceSummaryCacheInvalidator, organizations workspaceMembershipOrganizationReader, realtime workspaceMembershipRealtime) auth.AuthFlow {
	if next == nil || invalidator == nil {
		return next
	}
	return workspaceSummaryInvalidatingAuthFlow{
		AuthFlow:      next,
		invalidator:   invalidator,
		organizations: organizations,
		realtime:      realtime,
	}
}

type workspaceSummaryOrganizationProvider interface {
	org.OrganizationProvider
	workspaceapi.OrganizationProvider
}

func withWorkspaceSummaryInvalidatingOrganizationProvider(next workspaceSummaryOrganizationProvider, invalidator workspaceSummaryCacheInvalidator, realtime workspaceMembershipRealtime) workspaceSummaryOrganizationProvider {
	if next == nil || invalidator == nil {
		return next
	}
	return workspaceSummaryInvalidatingOrganizationProvider{
		workspaceSummaryOrganizationProvider: next,
		invalidator:                          invalidator,
		realtime:                             realtime,
	}
}

func trimmedOrFallback(value string, fallback string) string {
	trimmed := strings.TrimSpace(value)
	if trimmed != "" {
		return trimmed
	}
	return fallback
}

type workspaceSummaryInvalidatingWorkspaceService struct {
	workspaceapi.WorkspaceService
	invalidator workspaceSummaryCacheInvalidator
}

func (service workspaceSummaryInvalidatingWorkspaceService) UpdateWorkspaceNameForUser(ctx context.Context, userID string, workspaceID string, name string) (workspace.Summary, error) {
	summary, err := service.WorkspaceService.UpdateWorkspaceNameForUser(ctx, userID, workspaceID, name)
	if err == nil {
		service.invalidator.InvalidateWorkspace(trimmedOrFallback(summary.WorkspaceID, workspaceID))
	}
	return summary, err
}

func (service workspaceSummaryInvalidatingWorkspaceService) UpdateWorkspaceMemberRoleForUser(ctx context.Context, actorUserID string, workspaceID string, targetUserID string, role string) error {
	err := service.WorkspaceService.UpdateWorkspaceMemberRoleForUser(ctx, actorUserID, workspaceID, targetUserID, role)
	if err == nil {
		service.invalidator.Invalidate(targetUserID, workspaceID)
	}
	return err
}

func (service workspaceSummaryInvalidatingWorkspaceService) RemoveWorkspaceMemberForUser(ctx context.Context, actorUserID string, workspaceID string, targetUserID string) ([]string, error) {
	machineIDs, err := service.WorkspaceService.RemoveWorkspaceMemberForUser(ctx, actorUserID, workspaceID, targetUserID)
	if err == nil {
		service.invalidator.Invalidate(targetUserID, workspaceID)
	}
	return machineIDs, err
}

type workspaceSummaryInvalidatingAuthStore struct {
	auth.Store
	invalidator workspaceSummaryCacheInvalidator
}

func (store workspaceSummaryInvalidatingAuthStore) UpdateUserDisplayName(ctx context.Context, userID string, displayName string) (identity.User, error) {
	user, err := store.Store.UpdateUserDisplayName(ctx, userID, displayName)
	if err == nil {
		store.invalidator.InvalidateUser(trimmedOrFallback(user.ID, userID))
		// Updating a user's display name can also rename their default personal
		// workspace in the identity store. That side effect does not return a
		// workspace ID, so clear the short-lived cache to avoid serving a stale
		// workspace name to any other cached member of that workspace.
		store.invalidator.Clear()
	}
	return user, err
}

type workspaceSummaryInvalidatingAuthFlow struct {
	auth.AuthFlow
	invalidator   workspaceSummaryCacheInvalidator
	organizations workspaceMembershipOrganizationReader
	realtime      workspaceMembershipRealtime
}

func (flow workspaceSummaryInvalidatingAuthFlow) EnsureDefaultWorkspace(ctx context.Context, user identity.User, options authflow.DefaultWorkspaceOptions) error {
	err := flow.AuthFlow.EnsureDefaultWorkspace(ctx, user, options)
	if err == nil {
		flow.invalidator.InvalidateUser(user.ID)
	}
	return err
}

func (flow workspaceSummaryInvalidatingAuthFlow) AcceptInviteByToken(ctx context.Context, user identity.User, token string) (authflow.InviteAcceptance, bool, error) {
	acceptance, found, err := flow.AuthFlow.AcceptInviteByToken(ctx, user, token)
	if err == nil && found {
		flow.invalidateInviteAcceptance(ctx, user.ID, acceptance)
	}
	return acceptance, found, err
}

func (flow workspaceSummaryInvalidatingAuthFlow) AcceptPendingInviteByID(ctx context.Context, user identity.User, inviteID string) (authflow.InviteAcceptance, bool, error) {
	acceptance, found, err := flow.AuthFlow.AcceptPendingInviteByID(ctx, user, inviteID)
	if err == nil && found {
		flow.invalidateInviteAcceptance(ctx, user.ID, acceptance)
	}
	return acceptance, found, err
}

func (flow workspaceSummaryInvalidatingAuthFlow) AutoJoinOrganizationsAfterVerifiedLogin(ctx context.Context, user identity.User, loginIdentifierKind authidentity.Kind) error {
	beforeOrganizationIDs, beforeErr := flow.listOrganizationIDsForUser(ctx, user.ID)
	err := flow.AuthFlow.AutoJoinOrganizationsAfterVerifiedLogin(ctx, user, loginIdentifierKind)
	if err == nil {
		flow.invalidator.InvalidateUser(user.ID)
		afterOrganizationIDs, afterErr := flow.listOrganizationIDsForUser(ctx, user.ID)
		if afterErr == nil {
			for organizationID := range afterOrganizationIDs {
				if beforeErr == nil {
					if _, existed := beforeOrganizationIDs[organizationID]; existed {
						continue
					}
				}
				emitOrganizationWorkspaceMembersUpdated(ctx, flow.organizations, flow.realtime, user.ID, organizationID)
			}
		}
	}
	return err
}

func (flow workspaceSummaryInvalidatingAuthFlow) listOrganizationIDsForUser(ctx context.Context, userID string) (map[string]struct{}, error) {
	if flow.organizations == nil {
		return nil, nil
	}
	organizations, err := flow.organizations.ListOrganizationsForUser(ctx, userID)
	if err != nil {
		return nil, err
	}
	organizationIDs := make(map[string]struct{}, len(organizations))
	for _, organizationView := range organizations {
		organizationID := strings.TrimSpace(organizationView.ID)
		if organizationID != "" {
			organizationIDs[organizationID] = struct{}{}
		}
	}
	return organizationIDs, nil
}

func (flow workspaceSummaryInvalidatingAuthFlow) invalidateInviteAcceptance(ctx context.Context, userID string, acceptance authflow.InviteAcceptance) {
	switch acceptance.Kind {
	case authflow.InviteKindWorkspace:
		flow.invalidator.Invalidate(userID, acceptance.Workspace.WorkspaceID)
	case authflow.InviteKindOrganization:
		flow.invalidator.InvalidateUser(userID)
		emitOrganizationWorkspaceMembersUpdated(ctx, flow.organizations, flow.realtime, userID, acceptance.Organization.OrganizationID)
	}
}

type workspaceSummaryInvalidatingOrganizationProvider struct {
	workspaceSummaryOrganizationProvider
	invalidator workspaceSummaryCacheInvalidator
	realtime    workspaceMembershipRealtime
}

func (provider workspaceSummaryInvalidatingOrganizationProvider) emitWorkspaceMembersUpdated(workspaceID string) {
	if provider.realtime == nil {
		return
	}
	workspaceID = strings.TrimSpace(workspaceID)
	if workspaceID == "" {
		return
	}
	provider.realtime.EmitWorkspaceMembersUpdated(workspaceID)
}

// emitOrganizationWorkspaceMembersUpdated is the bounded fan-out used after
// organization mutations (team/unit membership and directory sync) that may
// change effective roles in more than one workspace. The mutation has already
// committed before this best-effort live notification runs; clients also
// reconcile authoritatively whenever their workspace stream reconnects.
func (provider workspaceSummaryInvalidatingOrganizationProvider) emitOrganizationWorkspaceMembersUpdated(ctx context.Context, userID string, organizationID string) {
	emitOrganizationWorkspaceMembersUpdated(ctx, provider.workspaceSummaryOrganizationProvider, provider.realtime, userID, organizationID)
}

func emitOrganizationWorkspaceMembersUpdated(ctx context.Context, organizations workspaceMembershipOrganizationReader, realtime workspaceMembershipRealtime, userID string, organizationID string) {
	if organizations == nil || realtime == nil {
		return
	}
	workspaces, err := organizations.ListOrganizationWorkspacesForUser(ctx, userID, organizationID)
	if err != nil {
		return
	}
	emitted := make(map[string]struct{}, len(workspaces))
	for _, workspace := range workspaces {
		workspaceID := strings.TrimSpace(workspace.ID)
		if workspaceID == "" {
			continue
		}
		if _, ok := emitted[workspaceID]; ok {
			continue
		}
		emitted[workspaceID] = struct{}{}
		realtime.EmitWorkspaceMembersUpdated(workspaceID)
	}
}

func (provider workspaceSummaryInvalidatingOrganizationProvider) CreateOrganizationMemberForUser(ctx context.Context, userID string, organizationID string, teamID string, email string, defaultWorkspaceRole string) (organization.OrganizationMemberView, error) {
	view, err := provider.workspaceSummaryOrganizationProvider.CreateOrganizationMemberForUser(ctx, userID, organizationID, teamID, email, defaultWorkspaceRole)
	if err == nil {
		provider.invalidator.InvalidateUser(view.UserID)
		provider.emitOrganizationWorkspaceMembersUpdated(ctx, userID, organizationID)
	}
	return view, err
}

func (provider workspaceSummaryInvalidatingOrganizationProvider) UpdateOrganizationMemberRoleForUser(ctx context.Context, userID string, organizationID string, memberID string, role string) (organization.OrganizationMemberView, error) {
	view, err := provider.workspaceSummaryOrganizationProvider.UpdateOrganizationMemberRoleForUser(ctx, userID, organizationID, memberID, role)
	if err == nil {
		provider.invalidator.InvalidateUser(view.UserID)
	}
	return view, err
}

func (provider workspaceSummaryInvalidatingOrganizationProvider) RemoveOrganizationMemberForUser(ctx context.Context, userID string, organizationID string, memberID string) (organization.OrganizationMemberRemovalResult, error) {
	result, err := provider.workspaceSummaryOrganizationProvider.RemoveOrganizationMemberForUser(ctx, userID, organizationID, memberID)
	if err == nil {
		provider.invalidator.InvalidateOrganization(organizationID)
		provider.emitOrganizationWorkspaceMembersUpdated(ctx, userID, organizationID)
	}
	return result, err
}

func (provider workspaceSummaryInvalidatingOrganizationProvider) GrantOrganizationMemberWorkspaceAccessForUser(ctx context.Context, userID string, organizationID string, workspaceID string, memberID string, role string) (organization.OrganizationWorkspaceGrantView, error) {
	view, err := provider.workspaceSummaryOrganizationProvider.GrantOrganizationMemberWorkspaceAccessForUser(ctx, userID, organizationID, workspaceID, memberID, role)
	if err == nil {
		resolvedWorkspaceID := trimmedOrFallback(view.WorkspaceID, workspaceID)
		provider.invalidator.Invalidate(view.UserID, resolvedWorkspaceID)
		provider.emitWorkspaceMembersUpdated(resolvedWorkspaceID)
	}
	return view, err
}

func (provider workspaceSummaryInvalidatingOrganizationProvider) RevokeOrganizationMemberWorkspaceAccessForUser(ctx context.Context, userID string, organizationID string, workspaceID string, memberID string) error {
	err := provider.workspaceSummaryOrganizationProvider.RevokeOrganizationMemberWorkspaceAccessForUser(ctx, userID, organizationID, workspaceID, memberID)
	if err == nil {
		provider.invalidator.InvalidateWorkspace(workspaceID)
		provider.emitWorkspaceMembersUpdated(workspaceID)
	}
	return err
}

func (provider workspaceSummaryInvalidatingOrganizationProvider) GrantOrganizationTeamWorkspaceAccessForUser(ctx context.Context, userID string, organizationID string, workspaceID string, teamID string, role string) (organization.OrganizationWorkspaceTeamGrantView, error) {
	view, err := provider.workspaceSummaryOrganizationProvider.GrantOrganizationTeamWorkspaceAccessForUser(ctx, userID, organizationID, workspaceID, teamID, role)
	if err == nil {
		resolvedWorkspaceID := trimmedOrFallback(view.WorkspaceID, workspaceID)
		provider.invalidator.InvalidateWorkspace(resolvedWorkspaceID)
		provider.emitWorkspaceMembersUpdated(resolvedWorkspaceID)
	}
	return view, err
}

func (provider workspaceSummaryInvalidatingOrganizationProvider) RevokeOrganizationTeamWorkspaceAccessForUser(ctx context.Context, userID string, organizationID string, workspaceID string, teamID string) error {
	err := provider.workspaceSummaryOrganizationProvider.RevokeOrganizationTeamWorkspaceAccessForUser(ctx, userID, organizationID, workspaceID, teamID)
	if err == nil {
		provider.invalidator.InvalidateWorkspace(workspaceID)
		provider.emitWorkspaceMembersUpdated(workspaceID)
	}
	return err
}

func (provider workspaceSummaryInvalidatingOrganizationProvider) AddOrganizationTeamMemberForUser(ctx context.Context, userID string, organizationID string, teamID string, params organization.AddOrganizationTeamMemberParams) (organization.OrganizationTeamMemberView, error) {
	view, err := provider.workspaceSummaryOrganizationProvider.AddOrganizationTeamMemberForUser(ctx, userID, organizationID, teamID, params)
	if err == nil {
		provider.invalidator.InvalidateUser(view.UserID)
		provider.emitOrganizationWorkspaceMembersUpdated(ctx, userID, organizationID)
	}
	return view, err
}

func (provider workspaceSummaryInvalidatingOrganizationProvider) RemoveOrganizationTeamMemberForUser(ctx context.Context, userID string, organizationID string, teamID string, memberID string) error {
	err := provider.workspaceSummaryOrganizationProvider.RemoveOrganizationTeamMemberForUser(ctx, userID, organizationID, teamID, memberID)
	if err == nil {
		provider.invalidator.InvalidateOrganization(organizationID)
		provider.emitOrganizationWorkspaceMembersUpdated(ctx, userID, organizationID)
	}
	return err
}

func (provider workspaceSummaryInvalidatingOrganizationProvider) SwitchOrganizationMemberPrimaryTeamForUser(ctx context.Context, userID string, organizationID string, memberID string, teamID string) (organization.OrganizationMemberView, error) {
	view, err := provider.workspaceSummaryOrganizationProvider.SwitchOrganizationMemberPrimaryTeamForUser(ctx, userID, organizationID, memberID, teamID)
	if err == nil {
		provider.invalidator.InvalidateUser(view.UserID)
		provider.emitOrganizationWorkspaceMembersUpdated(ctx, userID, organizationID)
	}
	return view, err
}

func (provider workspaceSummaryInvalidatingOrganizationProvider) AddOrganizationUnitMemberForUser(ctx context.Context, userID string, organizationID string, unitID string, params organization.AddOrganizationUnitMemberParams) (organization.OrganizationUnitMemberView, error) {
	view, err := provider.workspaceSummaryOrganizationProvider.AddOrganizationUnitMemberForUser(ctx, userID, organizationID, unitID, params)
	if err == nil {
		provider.invalidator.InvalidateUser(view.UserID)
		provider.emitOrganizationWorkspaceMembersUpdated(ctx, userID, organizationID)
	}
	return view, err
}

func (provider workspaceSummaryInvalidatingOrganizationProvider) RemoveOrganizationUnitMemberForUser(ctx context.Context, userID string, organizationID string, unitID string, memberID string) error {
	err := provider.workspaceSummaryOrganizationProvider.RemoveOrganizationUnitMemberForUser(ctx, userID, organizationID, unitID, memberID)
	if err == nil {
		provider.invalidator.InvalidateOrganization(organizationID)
		provider.emitOrganizationWorkspaceMembersUpdated(ctx, userID, organizationID)
	}
	return err
}

func (provider workspaceSummaryInvalidatingOrganizationProvider) RunOrganizationFeishuDirectorySyncForUser(ctx context.Context, userID string, organizationID string, syncRunID string) (organization.OrganizationFeishuDirectoryImportResult, error) {
	result, err := provider.workspaceSummaryOrganizationProvider.RunOrganizationFeishuDirectorySyncForUser(ctx, userID, organizationID, syncRunID)
	if err == nil {
		provider.invalidator.InvalidateOrganization(organizationID)
		provider.emitOrganizationWorkspaceMembersUpdated(ctx, userID, organizationID)
	}
	return result, err
}

func (provider workspaceSummaryInvalidatingOrganizationProvider) SyncOrganizationFeishuDirectoryForUser(ctx context.Context, userID string, organizationID string) (organization.OrganizationFeishuDirectoryImportResult, error) {
	result, err := provider.workspaceSummaryOrganizationProvider.SyncOrganizationFeishuDirectoryForUser(ctx, userID, organizationID)
	if err == nil {
		provider.invalidator.InvalidateOrganization(organizationID)
		provider.emitOrganizationWorkspaceMembersUpdated(ctx, userID, organizationID)
	}
	return result, err
}
