package app

import (
	"context"
	"database/sql"
	"fmt"
	"strings"
	"time"

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

func normalizeMessageAgentDeliveryInput(messageID string, interruptPolicy string) (string, string, error) {
	normalizedPolicy, ok := message.NormalizeAgentInterruptPolicy(interruptPolicy)
	if !ok || normalizedPolicy == "" {
		return "", "", badRequest("unsupported interruptPolicy")
	}
	trimmedMessageID := strings.TrimSpace(messageID)
	if trimmedMessageID == "" {
		return "", "", badRequest("messageId is required")
	}
	return trimmedMessageID, normalizedPolicy, nil
}

// PrepareMessageAgentDeliveryForUser validates an urgent replay request and
// builds the authoritative delivery without mutating message state. Callers
// can persist progress markers separately from the success marker.
func (store *Store) PrepareMessageAgentDeliveryForUser(ctx context.Context, userID string, workspaceID string, messageID string, interruptPolicy string) (message.AppMessage, message.MessageDelivery, error) {
	trimmedMessageID, normalizedPolicy, err := normalizeMessageAgentDeliveryInput(messageID, interruptPolicy)
	if err != nil {
		return message.AppMessage{}, message.MessageDelivery{}, err
	}

	var appMessage message.AppMessage
	var delivery message.MessageDelivery
	if err := coordinator.New(store.db).Run(ctx, "prepare message agent delivery", func(tx coredb.Tx) error {
		loaded, err := loadAgentDeliveryMessageForUpdate(ctx, tx, workspaceID, trimmedMessageID)
		if err != nil {
			return err
		}
		_, target, err := store.resolveAuthorizedHumanMessageTargetWithQueryer(ctx, tx, userID, workspaceID, loaded.record.ScopeID, channelReadAction, dmReadAction, threadReadAction)
		if err != nil {
			return err
		}
		updatedMetadata := message.MetadataWithAgentDeliveryInterruptPolicy(loaded.metadata, normalizedPolicy)
		_, normalizedMetadata, err := message.EncodeMessageMetadata(updatedMetadata)
		if err != nil {
			return err
		}
		appMessage, delivery, err = store.agentDeliveryReplayForMessage(ctx, tx, workspaceID, target, loaded, normalizedMetadata, normalizedPolicy)
		return err
	}); err != nil {
		return message.AppMessage{}, message.MessageDelivery{}, err
	}
	return appMessage, delivery, nil
}

// CommitMessageAgentDeliveryForUser persists the urgent marker for a delivery
// that has already been accepted by the daemon handoff path.
func (store *Store) CommitMessageAgentDeliveryForUser(ctx context.Context, userID string, workspaceID string, messageID string, interruptPolicy string) (message.AppMessage, error) {
	trimmedMessageID, normalizedPolicy, err := normalizeMessageAgentDeliveryInput(messageID, interruptPolicy)
	if err != nil {
		return message.AppMessage{}, err
	}

	now := time.Now().UTC()
	var appMessage message.AppMessage
	if err := coordinator.New(store.db).Run(ctx, "commit message agent delivery", func(tx coredb.Tx) error {
		loaded, err := loadAgentDeliveryMessageForUpdate(ctx, tx, workspaceID, trimmedMessageID)
		if err != nil {
			return err
		}
		_, target, err := store.resolveAuthorizedHumanMessageTargetWithQueryer(ctx, tx, userID, workspaceID, loaded.record.ScopeID, channelReadAction, dmReadAction, threadReadAction)
		if err != nil {
			return err
		}
		updatedMetadata := message.MetadataWithAgentDeliveryInterruptPolicy(loaded.metadata, normalizedPolicy)
		encodedMetadata, normalizedMetadata, err := message.EncodeMessageMetadata(updatedMetadata)
		if err != nil {
			return err
		}
		if _, err := tx.Exec(ctx, `
UPDATE scope_messages
SET metadata = $2, updated_at = $3
WHERE id = $1
`, trimmedMessageID, string(encodedMetadata), now); err != nil {
			return fmt.Errorf("mark message agent delivery metadata: %w", err)
		}

		appMessage, _, err = store.agentDeliveryReplayForMessage(ctx, tx, workspaceID, target, loaded, normalizedMetadata, normalizedPolicy)
		return err
	}); err != nil {
		return message.AppMessage{}, err
	}
	return appMessage, nil
}

// MarkMessageAgentDeliveryStatusForUser persists visible urgent-delivery
// progress without changing the replay payload used for daemon handoff.
func (store *Store) MarkMessageAgentDeliveryStatusForUser(ctx context.Context, userID string, workspaceID string, messageID string, interruptPolicy string, status string) (message.AppMessage, error) {
	trimmedMessageID, normalizedPolicy, err := normalizeMessageAgentDeliveryInput(messageID, interruptPolicy)
	if err != nil {
		return message.AppMessage{}, err
	}
	normalizedStatus := message.NormalizeAgentDeliveryStatus(status)
	if normalizedStatus == "" {
		return message.AppMessage{}, badRequest("unsupported agentDelivery status")
	}

	now := time.Now().UTC()
	var appMessage message.AppMessage
	if err := coordinator.New(store.db).Run(ctx, "mark message agent delivery status", func(tx coredb.Tx) error {
		loaded, err := loadAgentDeliveryMessageForUpdate(ctx, tx, workspaceID, trimmedMessageID)
		if err != nil {
			return err
		}
		_, target, err := store.resolveAuthorizedHumanMessageTargetWithQueryer(ctx, tx, userID, workspaceID, loaded.record.ScopeID, channelReadAction, dmReadAction, threadReadAction)
		if err != nil {
			return err
		}
		if strings.HasPrefix(strings.TrimSpace(loaded.record.ScopeID), "tsk_") {
			return badRequest("agentDelivery is not supported for task scope messages")
		}
		if !message.IsHumanVisibleScopeMessage(loaded.record) {
			return badRequest("agentDelivery is only supported for visible messages")
		}
		updatedMetadata := message.MetadataWithAgentDeliveryStatus(loaded.metadata, normalizedPolicy, normalizedStatus)
		encodedMetadata, normalizedMetadata, err := message.EncodeMessageMetadata(updatedMetadata)
		if err != nil {
			return err
		}
		if _, err := tx.Exec(ctx, `
UPDATE scope_messages
SET metadata = $2, updated_at = $3
WHERE id = $1
`, trimmedMessageID, string(encodedMetadata), now); err != nil {
			return fmt.Errorf("mark message agent delivery status metadata: %w", err)
		}
		delivery, err := store.agentDeliveryForMessageRecord(ctx, tx, workspaceID, target, loaded.record)
		if err != nil {
			return err
		}
		delivery.Message.Metadata = message.CopyMessageMetadata(normalizedMetadata)
		appMessage = appMessageFromDelivery(appChannelIDForTarget(target), delivery.Message.AuthorProfile, delivery)
		return nil
	}); err != nil {
		return message.AppMessage{}, err
	}
	return appMessage, nil
}

// SubmitMessageAgentDeliveryForUser marks an existing visible message as
// urgent and returns a replay delivery for the same agent recipients.
func (store *Store) SubmitMessageAgentDeliveryForUser(ctx context.Context, userID string, workspaceID string, messageID string, interruptPolicy string) (message.AppMessage, message.MessageDelivery, error) {
	_, delivery, err := store.PrepareMessageAgentDeliveryForUser(ctx, userID, workspaceID, messageID, interruptPolicy)
	if err != nil {
		return message.AppMessage{}, message.MessageDelivery{}, err
	}
	committedMessage, err := store.CommitMessageAgentDeliveryForUser(ctx, userID, workspaceID, messageID, interruptPolicy)
	if err != nil {
		return message.AppMessage{}, message.MessageDelivery{}, err
	}
	return committedMessage, delivery, nil
}

func (store *Store) agentDeliveryReplayForMessage(ctx context.Context, tx coredb.Tx, workspaceID string, target message.ThreadAccess, loaded agentDeliveryMessageRecord, metadata map[string]any, interruptPolicy string) (message.AppMessage, message.MessageDelivery, error) {
	if strings.HasPrefix(strings.TrimSpace(loaded.record.ScopeID), "tsk_") {
		return message.AppMessage{}, message.MessageDelivery{}, badRequest("agentDelivery is not supported for task scope messages")
	}
	if !message.IsHumanVisibleScopeMessage(loaded.record) {
		return message.AppMessage{}, message.MessageDelivery{}, badRequest("agentDelivery is only supported for visible messages")
	}
	if len(loaded.outboxPayload) > 0 {
		payload, err := message.UnmarshalScopeFanoutPayload(loaded.outboxPayload)
		if err != nil {
			return message.AppMessage{}, message.MessageDelivery{}, fmt.Errorf("load message fanout payload: %w", err)
		}
		delivery := payload.Delivery
		delivery.WakeRecipients = urgentAgentDeliveryWakeRecipients(delivery)
		if len(delivery.WakeRecipients) > 0 {
			appMessage := payload.AppMessage
			applyAgentDeliveryInterrupt(&appMessage, &delivery, metadata, interruptPolicy)
			return appMessage, delivery, nil
		}
	}
	return store.recomputeAgentDeliveryReplay(ctx, tx, workspaceID, target, loaded.record, metadata, interruptPolicy)
}

func (store *Store) recomputeAgentDeliveryReplay(ctx context.Context, tx coredb.Tx, workspaceID string, target message.ThreadAccess, record message.ScopeMessageRecord, metadata map[string]any, interruptPolicy string) (message.AppMessage, message.MessageDelivery, error) {
	routingPlan, err := store.replayRoutingPlanForMessage(ctx, tx, target, record, metadata)
	if err != nil {
		return message.AppMessage{}, message.MessageDelivery{}, err
	}
	delivery, err := store.agentDeliveryForMessageRecord(ctx, tx, workspaceID, target, record)
	if err != nil {
		return message.AppMessage{}, message.MessageDelivery{}, err
	}
	delivery.Recipients = append([]message.AgentRecipient(nil), routingPlan.DurableRecipients...)
	delivery.WakeRecipients = message.MergeAgentRecipients(routingPlan.WakeRecipients, routingPlan.DurableRecipients)
	if len(delivery.WakeRecipients) == 0 {
		return message.AppMessage{}, message.MessageDelivery{}, badRequest("message has no agent delivery recipients")
	}
	appMessage := appMessageFromDelivery(appChannelIDForTarget(target), delivery.Message.AuthorProfile, delivery)
	applyAgentDeliveryInterrupt(&appMessage, &delivery, metadata, interruptPolicy)
	return appMessage, delivery, nil
}

func (store *Store) replayRoutingPlanForMessage(ctx context.Context, tx coredb.Tx, target message.ThreadAccess, record message.ScopeMessageRecord, metadata map[string]any) (message.AgentRoutingPlan, error) {
	authorMemberID := strings.TrimSpace(record.AuthorMemberID)
	switch domain.MemberType(strings.TrimSpace(record.AuthorType)) {
	case domain.MemberTypeHuman:
		return replayHumanMessageRoutingPlan(ctx, tx, target, authorMemberID, record.MessageType, record.Content, metadata)
	default:
		return message.AgentRoutingPlan{}, errMessageAgentDeliveryReplayUnavailable()
	}
}

// replayHumanMessageRoutingPlan rebuilds only recipients bound by persisted
// mention metadata. Without the original fanout payload, default, thread,
// owner-private, broadcast, DM, and current-token routing are intentionally not
// replayed because they depend on mutable current state.
func replayHumanMessageRoutingPlan(ctx context.Context, tx coredb.Tx, target message.ThreadAccess, authorMemberID string, messageType string, content string, metadata map[string]any) (message.AgentRoutingPlan, error) {
	routingContent := mentionRoutingContent(messageType, content)
	if target.ReplyThread != nil {
		if err := message.ValidateReplyThreadRoutingContent(routingContent); err != nil {
			return message.AgentRoutingPlan{}, err
		}
	} else if message.ContainsControlledBroadcastToken(routingContent) {
		return message.AgentRoutingPlan{}, errMessageAgentDeliveryReplayUnavailable()
	}

	if len(message.ParseDirectMentionTokens(routingContent)) == 0 {
		return message.AgentRoutingPlan{}, errMessageAgentDeliveryReplayUnavailable()
	}
	mentionScopeID := strings.TrimSpace(target.ChannelID)
	if target.ReplyThread != nil {
		mentionScopeID = strings.TrimSpace(target.ReplyThread.ParentChannelID)
	}
	mentionedRecipients, ok, err := message.LoadScopePersistedMentionedAgentRecipientsWithMetadata(ctx, tx, mentionScopeID, authorMemberID, routingContent, metadata)
	if err != nil {
		return message.AgentRoutingPlan{}, err
	}
	if !ok || len(mentionedRecipients) == 0 {
		return message.AgentRoutingPlan{}, errMessageAgentDeliveryReplayUnavailable()
	}
	return message.FinalizeAgentRoutingPlan(message.AgentRoutingPlan{
		DurableRecipients: mentionedRecipients,
		WakeRecipients:    mentionedRecipients,
	}), nil
}

func errMessageAgentDeliveryReplayUnavailable() error {
	return badRequest("message has no replayable agent delivery payload")
}

func (store *Store) agentDeliveryForMessageRecord(ctx context.Context, tx coredb.Tx, workspaceID string, target message.ThreadAccess, record message.ScopeMessageRecord) (message.MessageDelivery, error) {
	authorMemberID := strings.TrimSpace(record.AuthorMemberID)
	authorType := domain.MemberType(strings.TrimSpace(record.AuthorType))
	authorDisplayName := authorMemberID
	if publicNames, err := loadPublicMemberNamesByMemberID(ctx, tx, workspaceID, []string{authorMemberID}); err != nil {
		return message.MessageDelivery{}, err
	} else if name := publicMemberNameForMemberID(authorMemberID, publicNames); name != "" {
		authorDisplayName = name
	}
	attachmentsByMessageID, err := store.loadAttachmentsForMessages(ctx, workspaceID, []string{record.ID})
	if err != nil {
		return message.MessageDelivery{}, err
	}
	authorProfile := message.AuthorProfile{
		ActorType:   string(authorType),
		PrincipalID: authorDisplayName,
		MemberID:    authorMemberID,
		TargetID:    authorDisplayName,
		DisplayName: authorDisplayName,
	}.Normalize()
	receiveMessage := message.ReceiveMessage{
		MessageID:               record.ID,
		TargetKey:               record.ScopeID,
		TargetSeq:               record.Seq,
		NotifyPrincipalMemberID: record.NotifyPrincipalMemberID,
		AuthorProfile:           authorProfile,
		MessageType:             record.MessageType,
		Content:                 record.Content,
		Timestamp:               record.CreatedAt,
		Attachments:             attachmentsByMessageID[record.ID],
	}
	if target.ReplyThread != nil {
		receiveMessage.ChannelType = "thread"
		receiveMessage.ChannelName = "thread-" + target.ReplyThread.ShortID
		receiveMessage.ParentChannelType = string(target.ThreadType)
		if target.ThreadType == domain.ThreadTypeDM {
			receiveMessage.ParentChannelName = authorMemberID
			receiveMessage.ParentChannelDisplayName = authorDisplayName
		} else {
			receiveMessage.ParentChannelName = target.ReplyThread.ParentChannelName
		}
	} else if target.ThreadType == domain.ThreadTypeDM {
		receiveMessage.ChannelType = string(domain.ThreadTypeDM)
		receiveMessage.ChannelName = authorMemberID
		receiveMessage.ChannelDisplayName = authorDisplayName
	} else {
		receiveMessage.ChannelType = string(domain.ThreadTypeChannel)
		receiveMessage.ChannelName = target.ChannelName
	}
	return message.MessageDelivery{
		ThreadID:                target.ThreadID,
		Seq:                     record.Seq,
		GlobalSeq:               record.WorkspaceGlobalSeq,
		MessageID:               record.ID,
		ClientMessageID:         record.ClientMessageID,
		NotifyPrincipalMemberID: record.NotifyPrincipalMemberID,
		Message:                 receiveMessage,
		MentionContext:          messageMentionContextForTarget(target),
		IdempotentReplay:        true,
	}, nil
}

func urgentAgentDeliveryWakeRecipients(delivery message.MessageDelivery) []message.AgentRecipient {
	if len(delivery.WakeRecipients) > 0 {
		return append([]message.AgentRecipient(nil), delivery.WakeRecipients...)
	}
	return append([]message.AgentRecipient(nil), delivery.Recipients...)
}

func applyAgentDeliveryInterrupt(appMessage *message.AppMessage, delivery *message.MessageDelivery, metadata map[string]any, interruptPolicy string) {
	if appMessage != nil {
		appMessage.Metadata = message.CopyMessageMetadata(metadata)
	}
	if delivery == nil {
		return
	}
	delivery.InterruptPolicy = interruptPolicy
	delivery.Message.InterruptPolicy = interruptPolicy
	delivery.Message.Metadata = message.CopyMessageMetadata(metadata)
	if appMessage != nil {
		appMessageCopy := *appMessage
		delivery.AppMessage = &appMessageCopy
	}
	delivery.IdempotentReplay = true
}

type agentDeliveryMessageRecord struct {
	record        message.ScopeMessageRecord
	metadata      map[string]any
	outboxPayload []byte
}

func loadAgentDeliveryMessageForUpdate(ctx context.Context, queryer coredb.Queryer, workspaceID string, messageID string) (agentDeliveryMessageRecord, error) {
	row := queryer.QueryRow(ctx, `
SELECT m.id, m.workspace_id, m.scope_id, m.seq, m.workspace_global_seq,
       m.author_member_id, m.author_type,
       COALESCE(NULLIF(m.notify_principal_member_id, ''), m.author_member_id),
       COALESCE(NULLIF(m.idempotency_subject_member_id, ''), m.author_member_id),
       COALESCE(m.client_message_id, ''), COALESCE(m.idempotency_payload_digest, ''),
       m.message_type, m.content, m.metadata, m.created_at, m.updated_at,
       CAST(o.payload AS CHAR)
FROM scope_messages m
LEFT JOIN scope_fanout_outbox o
  ON o.message_id = m.id
 AND o.event_ref_type = 'message'
 AND o.fanout_type = 'scope_fanout'
WHERE m.workspace_id = $1
  AND m.id = $2
LIMIT 1
FOR UPDATE
`, strings.TrimSpace(workspaceID), strings.TrimSpace(messageID))

	var loaded agentDeliveryMessageRecord
	var rawMetadata []byte
	var rawOutboxPayload sql.NullString
	if err := row.Scan(
		&loaded.record.ID,
		&loaded.record.WorkspaceID,
		&loaded.record.ScopeID,
		&loaded.record.Seq,
		&loaded.record.WorkspaceGlobalSeq,
		&loaded.record.AuthorMemberID,
		&loaded.record.AuthorType,
		&loaded.record.NotifyPrincipalMemberID,
		&loaded.record.IdempotencySubjectMemberID,
		&loaded.record.ClientMessageID,
		&loaded.record.IdempotencyPayloadDigest,
		&loaded.record.MessageType,
		&loaded.record.Content,
		&rawMetadata,
		&loaded.record.CreatedAt,
		&loaded.record.UpdatedAt,
		&rawOutboxPayload,
	); err != nil {
		if coredb.IsNotFound(err) {
			return agentDeliveryMessageRecord{}, ErrNotFound
		}
		return agentDeliveryMessageRecord{}, fmt.Errorf("load message for agent delivery: %w", err)
	}
	metadata, err := message.DecodeMessageMetadata(rawMetadata)
	if err != nil {
		return agentDeliveryMessageRecord{}, err
	}
	loaded.metadata = metadata
	loaded.record.Metadata = append([]byte(nil), rawMetadata...)
	if rawOutboxPayload.Valid {
		loaded.outboxPayload = []byte(strings.TrimSpace(rawOutboxPayload.String))
	}
	return loaded, nil
}
