package message

import (
	"context"
	"fmt"
	"strings"

	"github.com/tidbcloud/AgentTeam/internal/coredb"
)

const (
	serviceAccountPublisherMetadataKey  = "serviceAccountPublisher"
	platformAccountBroadcastMetadataKey = "platformServiceAccountBroadcast"
)

// RedactServiceAccountPublisherMetadataForSharedProjection removes Human
// operator identity from a service-account article before it is written to a
// scope-wide realtime or conversation-index projection. Those projections are
// shared by every audience member and therefore cannot carry management-only
// byline data.
func RedactServiceAccountPublisherMetadataForSharedProjection(appMessage *AppMessage) {
	if appMessage == nil || strings.TrimSpace(appMessage.MessageType) != MessageTypeServiceAccountPost {
		return
	}
	metadata := CopyMessageMetadata(appMessage.Metadata)
	delete(metadata, serviceAccountPublisherMetadataKey)
	delete(metadata, platformAccountBroadcastMetadataKey)
	if len(metadata) == 0 {
		metadata = nil
	}
	appMessage.Metadata = metadata
}

// RedactServiceAccountPublisherMetadataForViewer keeps the private Human
// publisher fact only for an active workspace administrator or explicit
// service-account Manager, or for a Server Owner viewing a platform-managed
// service account.
func RedactServiceAccountPublisherMetadataForViewer(ctx context.Context, queryer coredb.Queryer, workspaceID string, viewerMemberID string, messages []AppMessage) error {
	scopeIDs := serviceAccountArticleScopeIDs(messages)
	if len(scopeIDs) == 0 {
		return nil
	}

	allowedScopeIDs := make(map[string]struct{}, len(scopeIDs))
	viewerMemberID = strings.TrimSpace(viewerMemberID)
	if viewerMemberID != "" {
		args := make([]any, 0, len(scopeIDs)+2)
		args = append(args, strings.TrimSpace(workspaceID), viewerMemberID)
		placeholders := make([]string, 0, len(scopeIDs))
		for index, scopeID := range scopeIDs {
			args = append(args, scopeID)
			placeholders = append(placeholders, fmt.Sprintf("$%d", index+3))
		}
		rows, err := queryer.Query(ctx, `
SELECT account.message_scope_id
FROM service_accounts account
JOIN members viewer_member
  ON viewer_member.workspace_id = account.workspace_id
 AND viewer_member.id = $2
 AND viewer_member.type = 'human'
 AND viewer_member.status = 'active'
LEFT JOIN users viewer_user ON viewer_user.id = viewer_member.user_id
LEFT JOIN service_account_managers manager
  ON manager.workspace_id = account.workspace_id
 AND manager.service_account_id = account.id
 AND manager.member_id = viewer_member.id
 AND manager.status = 'active'
 AND manager.ended_at IS NULL
WHERE account.workspace_id = $1
  AND account.message_scope_id IN (`+strings.Join(placeholders, ", ")+`)
  AND (
    (account.management_kind = 'workspace'
      AND (manager.id IS NOT NULL OR viewer_member.workspace_role IN ('owner', 'admin')))
    OR (account.management_kind = 'platform' AND viewer_user.is_admin = TRUE)
  )
`, args...)
		if err != nil {
			return fmt.Errorf("load service-account publisher viewer authority: %w", err)
		}
		defer rows.Close()
		for rows.Next() {
			var scopeID string
			if err := rows.Scan(&scopeID); err != nil {
				return fmt.Errorf("scan service-account publisher viewer authority: %w", err)
			}
			allowedScopeIDs[strings.TrimSpace(scopeID)] = struct{}{}
		}
		if err := rows.Err(); err != nil {
			return fmt.Errorf("iterate service-account publisher viewer authority: %w", err)
		}
	}

	for index := range messages {
		if strings.TrimSpace(messages[index].MessageType) != MessageTypeServiceAccountPost {
			continue
		}
		if _, allowed := allowedScopeIDs[strings.TrimSpace(messages[index].ChannelID)]; allowed {
			continue
		}
		RedactServiceAccountPublisherMetadataForSharedProjection(&messages[index])
	}
	return nil
}

func serviceAccountArticleScopeIDs(messages []AppMessage) []string {
	seen := make(map[string]struct{})
	result := make([]string, 0)
	for _, appMessage := range messages {
		if strings.TrimSpace(appMessage.MessageType) != MessageTypeServiceAccountPost {
			continue
		}
		scopeID := strings.TrimSpace(appMessage.ChannelID)
		if scopeID == "" {
			continue
		}
		if _, exists := seen[scopeID]; exists {
			continue
		}
		seen[scopeID] = struct{}{}
		result = append(result, scopeID)
	}
	return result
}
