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"
)

type ownerPrivateRecipientResolutionOptions struct {
	IncludeDirectMentions  bool
	IncludeChannelAutoWake bool
	IncludeThreadContext   bool
	DirectMentionTokens    []string
}

type ownerPrivateRecipientResolution struct {
	message.AgentRoutingPlan
	DirectMentionRecipients []message.AgentRecipient
}

func (store *Store) resolveOwnerPrivateRecipientsForMessage(ctx context.Context, tx coredb.Tx, workspaceID string, target message.ThreadAccess, authorMemberID string, authorType domain.MemberType, content string, options ownerPrivateRecipientResolutionOptions) (ownerPrivateRecipientResolution, error) {
	if target.ThreadType == domain.ThreadTypeDM {
		return ownerPrivateRecipientResolution{}, nil
	}
	if !options.IncludeDirectMentions && !options.IncludeChannelAutoWake && !options.IncludeThreadContext {
		return ownerPrivateRecipientResolution{}, nil
	}

	hostChannelID := strings.TrimSpace(target.ChannelID)
	threadChannelID := ""
	if target.ReplyThread != nil {
		threadChannelID = strings.TrimSpace(target.ChannelID)
		hostChannelID = strings.TrimSpace(target.ReplyThread.ParentChannelID)
	}
	parentMessageID := ""
	if target.ReplyThread != nil {
		parentMessageID = strings.TrimSpace(target.ReplyThread.ParentMessageID)
	}
	if hostChannelID == "" {
		return ownerPrivateRecipientResolution{}, nil
	}

	mentionTokens := []string(nil)
	if options.IncludeDirectMentions {
		if options.DirectMentionTokens != nil {
			mentionTokens = normalizeOwnerPrivateMentionTokens(options.DirectMentionTokens)
		} else {
			mentionTokens = normalizeOwnerPrivateMentionTokens(message.ParseDirectMentionTokensLimit(content, message.MaxScopeDirectMentionTokens))
		}
	}
	mentionedTokensCTE := "mentioned_tokens AS (\n    SELECT '' AS token WHERE 1 = 0\n)"
	mentionedTokenArgs := []any(nil)
	if len(mentionTokens) > 0 {
		cte, args, _, err := coredb.BuildStringValuesCTE("mentioned_tokens", "token", mentionTokens, 11)
		if err != nil {
			return ownerPrivateRecipientResolution{}, err
		}
		mentionedTokensCTE = cte
		mentionedTokenArgs = args
	}

	// Keep this producer-side context query on the same live owner/membership
	// authority facts as message.LoadScopeMentionedAgentRecipientsForTokens.
	// Direct mentions and context wake may merge, but neither may bypass an
	// inactive owner, stale private row, or reparented Agent/machine relation.
	query := fmt.Sprintf(`
WITH %s,
owner_private_candidates AS (
    SELECT
        host_scope.workspace_id,
        host_scope.id AS host_channel_id,
        sm.owner_member_id,
        a.id AS agent_id,
        a.machine_id,
        a.member_id AS agent_member_id,
        CASE
            WHEN thread_subscription.state = 'no-wake' THEN 0
            ELSE 1
        END AS thread_auto_wake_enabled,
        CASE
            WHEN channel_subscription.state = 'auto-wake' THEN 1
            ELSE 0
        END AS channel_auto_wake_enabled,
        COALESCE((
            SELECT mt.token
            FROM mentioned_tokens mt
            WHERE LOWER(a.name) = mt.token
               OR LOWER(a.member_id) = mt.token
            ORDER BY LENGTH(mt.token) DESC, mt.token ASC
            LIMIT 1
        ), '') AS explicit_mention_token,
        EXISTS (
            SELECT 1
            FROM scope_memberships thread_context
            WHERE $3 <> ''
              AND thread_context.scope_id = $3
              AND thread_context.member_id = sm.member_id
              AND thread_context.owner_member_id = sm.owner_member_id
              AND thread_context.visibility = 'private'
              AND thread_context.removed_at IS NULL
              AND thread_context.archived_at IS NULL
              AND thread_context.effective_to_seq IS NULL
              AND CURRENT_TIMESTAMP(6) >= COALESCE(thread_context.authority_from_at, thread_context.created_at)
              AND (
                  thread_context.authority_to_at IS NULL
                  OR CURRENT_TIMESTAMP(6) < thread_context.authority_to_at
              )
        ) AS candidate_in_thread_context,
        EXISTS (
            SELECT 1
            FROM scope_messages parent_message
            WHERE parent_message.workspace_id = host_scope.workspace_id
              AND parent_message.scope_id = host_scope.id
              AND parent_message.id = $10
              AND parent_message.author_type = 'agent'
              AND parent_message.author_member_id = sm.member_id
        ) AS candidate_is_thread_parent_author,
        EXISTS (
            SELECT 1
            FROM scope_memberships author_private_entry
            WHERE author_private_entry.scope_id = host_scope.id
              AND author_private_entry.member_id = $4
              AND author_private_entry.owner_member_id = sm.owner_member_id
              AND author_private_entry.visibility = 'private'
              AND author_private_entry.removed_at IS NULL
              AND author_private_entry.archived_at IS NULL
              AND author_private_entry.effective_to_seq IS NULL
              AND CURRENT_TIMESTAMP(6) >= COALESCE(author_private_entry.authority_from_at, author_private_entry.created_at)
              AND (
                  author_private_entry.authority_to_at IS NULL
                  OR CURRENT_TIMESTAMP(6) < author_private_entry.authority_to_at
              )
              AND EXISTS (
                  SELECT 1
                  FROM members author_owner_member
                  JOIN scope_memberships author_owner_membership
                    ON author_owner_membership.scope_id = host_scope.id
                   AND author_owner_membership.member_id = author_owner_member.id
                   AND author_owner_membership.visibility = 'public'
                   AND author_owner_membership.owner_member_id IS NULL
                   AND author_owner_membership.removed_at IS NULL
                   AND author_owner_membership.archived_at IS NULL
                   AND author_owner_membership.effective_to_seq IS NULL
                   AND CURRENT_TIMESTAMP(6) >= COALESCE(author_owner_membership.authority_from_at, author_owner_membership.created_at)
                   AND (
                       author_owner_membership.authority_to_at IS NULL
                       OR CURRENT_TIMESTAMP(6) < author_owner_membership.authority_to_at
                   )
                  JOIN agents author_agent
                    ON author_agent.workspace_id = host_scope.workspace_id
                   AND author_agent.member_id = author_private_entry.member_id
                  JOIN machines author_machine
                    ON author_machine.workspace_id = author_agent.workspace_id
                   AND author_machine.id = author_agent.machine_id
                  WHERE author_owner_member.workspace_id = host_scope.workspace_id
                    AND author_owner_member.id = author_private_entry.owner_member_id
                    AND author_owner_member.type = 'human'
                    AND author_owner_member.status = 'active'
                    AND author_owner_member.user_id IS NOT NULL
                    AND author_agent.status <> 'archived'
                    AND author_machine.status = 'active'
                    AND author_machine.created_by_user_id = author_owner_member.user_id
              )
              AND (
                  $3 = ''
                  OR EXISTS (
                      SELECT 1
                      FROM scope_memberships author_thread_context
                      WHERE author_thread_context.scope_id = $3
                        AND author_thread_context.member_id = $4
                        AND author_thread_context.owner_member_id = sm.owner_member_id
                        AND author_thread_context.visibility = 'private'
                        AND author_thread_context.removed_at IS NULL
                        AND author_thread_context.archived_at IS NULL
                        AND author_thread_context.effective_to_seq IS NULL
                        AND CURRENT_TIMESTAMP(6) >= COALESCE(author_thread_context.authority_from_at, author_thread_context.created_at)
                        AND (
                            author_thread_context.authority_to_at IS NULL
                            OR CURRENT_TIMESTAMP(6) < author_thread_context.authority_to_at
                        )
                  )
              )
        ) AS author_private_context_visible,
        EXISTS (
            SELECT 1
            FROM task_scope_executions tse
            JOIN task_scope_participant_metadata collaborator
              ON collaborator.owner_scope_execution_id = tse.id
             AND collaborator.member_id = $4
             AND collaborator.removed_at IS NULL
             AND collaborator.left_at IS NULL
            WHERE tse.workspace_id = host_scope.workspace_id
              AND tse.owner_member_id = sm.owner_member_id
              AND (
                  (tse.scope_type = 'channel' AND tse.scope_id = $2)
                  OR ($3 <> '' AND tse.scope_type = 'thread' AND tse.scope_id = $3)
              )
              AND collaborator.added_at <= $9
              AND (collaborator.expires_at IS NULL OR collaborator.expires_at > $9)
              AND (
                  $5 <> 'service'
                  OR EXISTS (
                      SELECT 1
                      FROM service_members author_service
                      JOIN service_installations author_installation
                        ON author_installation.id = author_service.service_installation_id
                       AND author_installation.service_kind = author_service.service_kind
                       AND author_installation.status = 'active'
                      WHERE author_service.workspace_id = host_scope.workspace_id
                        AND author_service.member_id = $4
                        AND author_service.service_kind = 'app'
                        AND author_service.status = 'active'
                  )
              )
        ) AS author_collaborator_context_visible
    FROM scopes host_scope
    JOIN scope_memberships sm
      ON sm.scope_id = host_scope.id
     AND sm.visibility = 'private'
     AND sm.owner_member_id IS NOT NULL
     AND sm.removed_at IS NULL
     AND sm.archived_at IS NULL
     AND sm.effective_to_seq IS NULL
     AND CURRENT_TIMESTAMP(6) >= COALESCE(sm.authority_from_at, sm.created_at)
     AND (
         sm.authority_to_at IS NULL
         OR CURRENT_TIMESTAMP(6) < sm.authority_to_at
     )
    JOIN agents a
      ON a.workspace_id = host_scope.workspace_id
     AND a.member_id = sm.member_id
    JOIN machines mac
      ON mac.workspace_id = a.workspace_id
     AND mac.id = a.machine_id
    JOIN members agent_member
      ON agent_member.workspace_id = host_scope.workspace_id
     AND agent_member.id = a.member_id
    LEFT JOIN scope_notification_settings channel_subscription
      ON channel_subscription.scope_id = host_scope.id
     AND channel_subscription.member_id = sm.member_id
    LEFT JOIN scope_notification_settings thread_subscription
      ON thread_subscription.scope_id = $3
     AND thread_subscription.member_id = sm.member_id
    WHERE host_scope.workspace_id = $1
      AND host_scope.id = $2
      AND host_scope.type = 'channel'
      AND host_scope.status = 'active'
      AND host_scope.archived_at IS NULL
      AND a.member_id <> $4
      AND agent_member.status = 'active'
      AND a.status <> 'archived'
      AND mac.status = 'active'
      AND EXISTS (
          SELECT 1
          FROM members target_owner_member
          JOIN scope_memberships target_owner_membership
            ON target_owner_membership.scope_id = host_scope.id
           AND target_owner_membership.member_id = target_owner_member.id
           AND target_owner_membership.visibility = 'public'
           AND target_owner_membership.owner_member_id IS NULL
           AND target_owner_membership.removed_at IS NULL
           AND target_owner_membership.archived_at IS NULL
           AND target_owner_membership.effective_to_seq IS NULL
           AND CURRENT_TIMESTAMP(6) >= COALESCE(target_owner_membership.authority_from_at, target_owner_membership.created_at)
           AND (
               target_owner_membership.authority_to_at IS NULL
               OR CURRENT_TIMESTAMP(6) < target_owner_membership.authority_to_at
           )
          WHERE target_owner_member.workspace_id = host_scope.workspace_id
            AND target_owner_member.id = sm.owner_member_id
            AND target_owner_member.type = 'human'
            AND target_owner_member.status = 'active'
            AND target_owner_member.user_id IS NOT NULL
            AND mac.created_by_user_id = target_owner_member.user_id
      )
)
SELECT
    agent_id,
    machine_id,
    agent_member_id,
    CASE
        WHEN explicit_mention_token <> '' THEN 1
        WHEN $7 AND $3 = '' AND channel_auto_wake_enabled = 1 THEN 1
        ELSE thread_auto_wake_enabled
    END AS auto_wake_enabled,
    CASE
        WHEN explicit_mention_token <> '' THEN 'direct_mention'
        ELSE 'owner_private_context'
    END AS delivery_reason,
    explicit_mention_token
FROM owner_private_candidates
WHERE (
      ($6 AND explicit_mention_token <> '')
      OR ($7 AND $3 = '' AND channel_auto_wake_enabled = 1)
      OR ($8 AND $3 <> '' AND candidate_in_thread_context)
      OR ($8 AND $3 <> '' AND candidate_is_thread_parent_author)
  )
  AND (
      ($5 = 'human' AND owner_member_id = $4)
      OR author_private_context_visible
      OR ($5 IN ('human', 'service') AND author_collaborator_context_visible)
  )
ORDER BY agent_member_id ASC
`, mentionedTokensCTE)
	args := []any{
		strings.TrimSpace(workspaceID),
		hostChannelID,
		threadChannelID,
		strings.TrimSpace(authorMemberID),
		string(authorType),
		options.IncludeDirectMentions,
		options.IncludeChannelAutoWake,
		options.IncludeThreadContext,
		time.Now().UTC(),
		parentMessageID,
	}
	args = append(args, mentionedTokenArgs...)

	rows, err := tx.Query(ctx, query, args...)
	if err != nil {
		return ownerPrivateRecipientResolution{}, fmt.Errorf("resolve owner-private recipients: %w", err)
	}
	defer rows.Close()

	var result ownerPrivateRecipientResolution
	for rows.Next() {
		var recipient message.AgentRecipient
		var mentionToken string
		if err := rows.Scan(&recipient.AgentID, &recipient.MachineID, &recipient.AgentMemberID, &recipient.AutoWakeEnabled, &recipient.DeliveryReason, &mentionToken); err != nil {
			return ownerPrivateRecipientResolution{}, fmt.Errorf("scan owner-private recipient: %w", err)
		}
		if recipient.DeliveryReason == message.AgentDeliveryReasonDirectMention {
			recipient.MentionToken = strings.TrimSpace(mentionToken)
		}
		result.DurableRecipients = append(result.DurableRecipients, recipient)
		if recipient.AutoWakeEnabled {
			result.WakeRecipients = append(result.WakeRecipients, recipient)
		}
		if recipient.DeliveryReason == message.AgentDeliveryReasonDirectMention {
			result.DirectMentionRecipients = append(result.DirectMentionRecipients, recipient)
		}
	}
	if err := rows.Err(); err != nil {
		return ownerPrivateRecipientResolution{}, fmt.Errorf("iterate owner-private recipients: %w", err)
	}
	return result, nil
}

func normalizeOwnerPrivateMentionTokens(tokens []string) []string {
	if len(tokens) == 0 {
		return nil
	}

	normalized := make([]string, 0, len(tokens))
	seen := make(map[string]struct{}, len(tokens))
	for _, token := range tokens {
		trimmed := strings.ToLower(strings.TrimSpace(token))
		if trimmed == "" {
			continue
		}
		if _, ok := seen[trimmed]; ok {
			continue
		}
		seen[trimmed] = struct{}{}
		normalized = append(normalized, trimmed)
		if len(normalized) >= message.MaxScopeDirectMentionTokens {
			break
		}
	}
	return normalized
}
