package app

import (
	"context"
	"fmt"
	"strings"
	"time"

	"github.com/tidbcloud/AgentTeam/internal/core/domain"
	"github.com/tidbcloud/AgentTeam/internal/coredb"
	"github.com/tidbcloud/AgentTeam/internal/store/message"
	"github.com/tidbcloud/AgentTeam/internal/store/model"
)

// appServiceMessageDeliveryConfigurator applies a producer-specific browser
// projection before the canonical lifecycle freezes and persists the delivery.
// App Notification uses it for its aggregated DM conversation projection; all
// other reviewed producers pass nil.
type appServiceMessageDeliveryConfigurator func(*message.MessageDelivery)

func (store *Store) appendAppServiceMessageWithOptions(
	ctx context.Context,
	tx coredb.Tx,
	installation model.RuntimeAppInstallation,
	target message.ThreadAccess,
	content string,
	metadata map[string]any,
	silent bool,
	options appendMessageOptions,
	contract appServiceMessageContract,
	configure appServiceMessageDeliveryConfigurator,
) (message.MessageDelivery, postCommitMessage, error) {
	// Runtime Apps control metadata on this path. Reject workflow authority
	// fields before routing/subscription work as well as at the shared append
	// fence so a App notification cannot forge callback context or wake Agents.
	if err := message.ValidateWorkflowContextMetadata(metadata, false); err != nil {
		return message.MessageDelivery{}, postCommitMessage{}, badRequest(err.Error())
	}
	normalizedSource, err := options.Source.NormalizeForWrite()
	if err != nil {
		return message.MessageDelivery{}, postCommitMessage{}, mapMessagingError(err)
	}
	options.Source = normalizedSource

	trimmedContent := strings.TrimSpace(content)
	normalizedAttachmentIDs, err := normalizeAttachmentIDs(options.AttachmentIDs)
	if err != nil {
		return message.MessageDelivery{}, postCommitMessage{}, err
	}
	if trimmedContent == "" && len(normalizedAttachmentIDs) == 0 {
		return message.MessageDelivery{}, postCommitMessage{}, badRequest("content is required")
	}

	options.Metadata = metadata
	if options.SuppressAgentRouting {
		options.ExplicitRoutingPlan = &message.AgentRoutingPlan{}
	} else if silent {
		routingPlan, err := message.LoadScopeAgentRoutingPlan(ctx, tx, installation.MemberID, target, trimmedContent, false)
		if err != nil {
			return message.MessageDelivery{}, postCommitMessage{}, err
		}
		options.ExplicitRoutingPlan = &message.AgentRoutingPlan{
			DurableRecipients: routingPlan.DurableRecipients,
		}
	} else if options.ExplicitRoutingPlan == nil {
		if target.ReplyThread != nil {
			if err := message.ValidateReplyThreadRoutingContent(trimmedContent); err != nil {
				return message.MessageDelivery{}, postCommitMessage{}, err
			}
			if err := store.syncReplyThreadSubscriptions(
				ctx,
				tx,
				installation.MemberID,
				domain.MemberTypeService,
				target.ChannelID,
				target.ReplyThread.ParentMessageID,
				nil,
				nil,
				0,
				0,
				time.Now().UTC(),
			); err != nil {
				return message.MessageDelivery{}, postCommitMessage{}, err
			}
			threadParticipants, err := message.LoadScopeThreadParticipantAgentRecipients(
				ctx,
				tx,
				target.ChannelID,
				installation.MemberID,
			)
			if err != nil {
				return message.MessageDelivery{}, postCommitMessage{}, err
			}
			mentionedRecipients, err := message.LoadScopeMentionedAgentRecipients(ctx, tx, target.ReplyThread.ParentChannelID, installation.MemberID, trimmedContent)
			if err != nil {
				return message.MessageDelivery{}, postCommitMessage{}, err
			}
			options.ExplicitRoutingPlan = &message.AgentRoutingPlan{
				DurableRecipients: message.MergeAgentRecipients(threadParticipants, mentionedRecipients),
				WakeRecipients:    message.MergeAgentRecipients(message.FilterAutoWakeAgentRecipients(threadParticipants), mentionedRecipients),
			}
		} else if target.ThreadType == domain.ThreadTypeChannel {
			routingPlan, err := message.LoadScopeAgentRoutingPlan(ctx, tx, installation.MemberID, target, trimmedContent, options.AllowControlledBroadcast)
			if err != nil {
				return message.MessageDelivery{}, postCommitMessage{}, err
			}
			defaultRecipients, err := message.LoadScopeDefaultAgentRecipients(ctx, tx, target.ChannelID, installation.MemberID)
			if err != nil {
				return message.MessageDelivery{}, postCommitMessage{}, err
			}
			defaultRecipients = message.MergeAgentRecipients(defaultRecipients, routingPlan.DurableRecipients)
			options.ExplicitRoutingPlan = &message.AgentRoutingPlan{
				DurableRecipients: defaultRecipients,
				WakeRecipients:    message.MergeAgentRecipients(message.FilterAutoWakeAgentRecipients(defaultRecipients), routingPlan.WakeRecipients),
			}
		}
	}
	if !options.SuppressAgentRouting && !silent && target.ThreadType == domain.ThreadTypeChannel {
		ownerPrivateRouting, err := store.resolveOwnerPrivateRecipientsForMessage(
			ctx,
			tx,
			installation.WorkspaceID,
			target,
			installation.MemberID,
			domain.MemberTypeService,
			trimmedContent,
			ownerPrivateRecipientResolutionOptions{
				IncludeDirectMentions:  false,
				IncludeChannelAutoWake: target.ReplyThread == nil,
				IncludeThreadContext:   target.ReplyThread != nil,
			},
		)
		if err != nil {
			return message.MessageDelivery{}, postCommitMessage{}, err
		}
		if len(ownerPrivateRouting.DurableRecipients) > 0 || len(ownerPrivateRouting.WakeRecipients) > 0 {
			if options.ExplicitRoutingPlan == nil {
				options.ExplicitRoutingPlan = &message.AgentRoutingPlan{}
			}
			options.ExplicitRoutingPlan.DurableRecipients = message.MergeAgentRecipients(
				options.ExplicitRoutingPlan.DurableRecipients,
				ownerPrivateRouting.DurableRecipients,
			)
			options.ExplicitRoutingPlan.WakeRecipients = message.MergeAgentRecipients(
				options.ExplicitRoutingPlan.WakeRecipients,
				ownerPrivateRouting.WakeRecipients,
			)
		}
	}

	delivery, err := store.appendMessageToThread(
		ctx,
		tx,
		installation.MemberID,
		domain.MemberTypeService,
		target,
		trimmedContent,
		normalizedAttachmentIDs,
		options,
		messageAppendAppServiceContracted,
	)
	if err != nil {
		return message.MessageDelivery{}, postCommitMessage{}, err
	}

	appMessage := appMessageFromDelivery(
		appChannelIDForTarget(target),
		message.AuthorProfile{
			ActorType:   message.AuthorActorTypeService,
			PrincipalID: strings.TrimSpace(installation.MemberID),
			MemberID:    strings.TrimSpace(installation.MemberID),
			TargetID:    strings.TrimSpace(installation.Handle),
			DisplayName: strings.TrimSpace(installation.AppName),
			ServiceKind: message.AuthorServiceKindApp,
			ServiceSlug: strings.TrimSpace(installation.AppSlug),
		},
		delivery,
	)
	if !options.Source.IsZero() {
		appMessage.Source = options.Source.Normalize()
	}
	messages := []message.AppMessage{appMessage}
	if err := attachServiceAuthorProfilePresentationWithQueryer(ctx, tx, installation.WorkspaceID, messages); err != nil {
		return message.MessageDelivery{}, postCommitMessage{}, fmt.Errorf("attach app service message presentation: %w", err)
	}
	appMessage = messages[0]
	if err := attachResolvedMentionsForSentAppMessage(ctx, tx, installation.WorkspaceID, &appMessage); err != nil {
		return message.MessageDelivery{}, postCommitMessage{}, err
	}
	attachAppMessageToDelivery(&delivery, &appMessage)
	if configure != nil {
		configure(&delivery)
	}
	return store.finalizeAppServiceMessageTx(ctx, tx, installation.WorkspaceID, delivery, contract)
}
