package fanout

import (
	"context"
	"fmt"
	"sort"
	"strings"

	"github.com/tidbcloud/AgentTeam/internal/store/message"
)

func taskDeliveryTreeScopeIDs(appMessage message.AppMessage, delivery message.MessageDelivery) []string {
	scopeIDs := make(map[string]struct{})
	appendTaskDeliveryTreeScopeIDs(scopeIDs, appMessage, delivery)
	return sortedTaskScopeIDs(scopeIDs)
}

func taskDeliveryTreesScopeIDs(deliveries []message.MessageDelivery) []string {
	scopeIDs := make(map[string]struct{})
	for _, delivery := range deliveries {
		appMessage := message.AppMessage{}
		if delivery.AppMessage != nil {
			appMessage = *delivery.AppMessage
		}
		appendTaskDeliveryTreeScopeIDs(scopeIDs, appMessage, delivery)
	}
	return sortedTaskScopeIDs(scopeIDs)
}

func appendTaskDeliveryTreeScopeIDs(scopeIDs map[string]struct{}, appMessage message.AppMessage, delivery message.MessageDelivery) {
	if scopeID, isTask := taskDeliveryScope(appMessage, delivery); isTask && scopeID != "" {
		scopeIDs[scopeID] = struct{}{}
	}
	for _, followup := range delivery.FollowupDeliveries {
		followupMessage := message.AppMessage{}
		if followup.AppMessage != nil {
			followupMessage = *followup.AppMessage
		}
		appendTaskDeliveryTreeScopeIDs(scopeIDs, followupMessage, followup)
	}
}

func sortedTaskScopeIDs(values map[string]struct{}) []string {
	scopeIDs := make([]string, 0, len(values))
	for scopeID := range values {
		scopeIDs = append(scopeIDs, scopeID)
	}
	sort.Strings(scopeIDs)
	return scopeIDs
}

func taskDeliveryIntentScope(payload message.ScopeDeliveryIntentPayload) (string, bool) {
	scopeID := strings.TrimSpace(payload.Message.TargetKey)
	isTask := strings.TrimSpace(payload.Message.ChannelType) == message.ConversationKindTask ||
		(payload.Conversation != nil && strings.TrimSpace(payload.Conversation.Kind) == message.ConversationKindTask) ||
		strings.HasPrefix(scopeID, "tsk_")
	return scopeID, isTask
}

func taskDeliveryScope(appMessage message.AppMessage, delivery message.MessageDelivery) (string, bool) {
	scopeID := strings.TrimSpace(delivery.Message.TargetKey)
	if scopeID == "" {
		scopeID = strings.TrimSpace(appMessage.ChannelID)
	}
	if scopeID == "" {
		scopeID = strings.TrimSpace(delivery.ThreadID)
	}
	isTask := strings.TrimSpace(appMessage.ChannelType) == message.ConversationKindTask ||
		strings.TrimSpace(delivery.Message.ChannelType) == message.ConversationKindTask ||
		(delivery.Conversation != nil && strings.TrimSpace(delivery.Conversation.Kind) == message.ConversationKindTask) ||
		strings.HasPrefix(scopeID, "tsk_")
	return scopeID, isTask
}

func targetedTaskMessageDelivery(appMessage message.AppMessage, delivery message.MessageDelivery) bool {
	if !delivery.HumanRecipientsTargeted {
		return false
	}
	_, isTask := taskDeliveryScope(appMessage, delivery)
	return isTask
}

func (fanout DeliveryFanout) persistTargetedTaskUnreadSnapshots(
	ctx context.Context,
	workspaceID string,
	scopeID string,
	memberIDs []string,
) error {
	if len(memberIDs) == 0 {
		return nil
	}
	// Direct, non-outbox test/adaptor calls do not own a durable completion
	// boundary. Every production v2 inline/replay path installs the lease
	// attempt in context; only that path must hold wake/completion behind the
	// snapshot barrier.
	if _, ok := scopeFanoutLeaseAttemptFromContext(ctx); !ok {
		return nil
	}
	if fanout.taskUnreadBarrier == nil {
		return fmt.Errorf("%w: snapshot barrier is not configured", errTargetedTaskUnreadPending)
	}
	if err := fanout.taskUnreadBarrier.PersistTaskUnreadSnapshots(
		ctx,
		strings.TrimSpace(workspaceID),
		strings.TrimSpace(scopeID),
		append([]string(nil), memberIDs...),
	); err != nil {
		return fmt.Errorf("%w: persist targeted Task unread snapshots: %w", errTargetedTaskUnreadPending, err)
	}
	return nil
}

func (fanout DeliveryFanout) requireTargetedTaskUnreadBarrier(ctx context.Context, memberIDs []string) error {
	if len(memberIDs) == 0 {
		return nil
	}
	// Direct adapter calls have no durable outbox completion boundary. The
	// inline and replay paths always install a lease attempt and must fail
	// closed when either half of targeted unread durability is not wired.
	if _, ok := scopeFanoutLeaseAttemptFromContext(ctx); !ok {
		return nil
	}
	if fanout.unread == nil {
		return fmt.Errorf("%w: runtime is not configured", errTargetedTaskUnreadPending)
	}
	if fanout.taskUnreadBarrier == nil {
		return fmt.Errorf("%w: snapshot barrier is not configured", errTargetedTaskUnreadPending)
	}
	return nil
}
