package app

import (
	"context"
	"fmt"
	"strings"

	"github.com/tidbcloud/AgentTeam/internal/pinyinsearch"
	"github.com/tidbcloud/AgentTeam/internal/store/agent"
)

func (store *Store) ListChannelAddableAgentsForUser(ctx context.Context, userID string, workspaceID string, channelID string, options agent.AgentDirectoryOptions) (agent.AgentDirectoryPage, error) {
	channelAccess, err := store.requireChannelInvite(ctx, userID, workspaceID, channelID)
	if err != nil {
		return agent.AgentDirectoryPage{}, err
	}
	if channelAccess.Channel.Type != "channel" {
		return agent.AgentDirectoryPage{}, badRequest("thread channels do not have direct members")
	}

	normalized := normalizeChannelAddableAgentDirectoryOptions(options)
	cursor, err := decodeAgentDirectoryCursor(normalized.Cursor)
	if err != nil {
		return agent.AgentDirectoryPage{}, err
	}
	queryPattern := ""
	if normalized.Query != "" {
		queryPattern = "%" + strings.ToLower(normalized.Query) + "%"
	}
	pinyinPattern := ""
	if normalized.Query != "" {
		normalizedQuery := pinyinsearch.NormalizeQuery(normalized.Query)
		if normalizedQuery != "" {
			pinyinPattern = "%" + normalizedQuery + "%"
		}
	}

	rows, err := store.db.Query(ctx, `
SELECT
       a.id,
       a.name,
       m.id,
       m.display_name,
       a.avatar_url,
       mac.created_by_user_id,
       a.machine_id,
       mac.created_by_user_id = $2 AS can_manage,
       FALSE AS in_channel
FROM agents a
JOIN members m ON m.id = a.member_id
JOIN machines mac ON mac.workspace_id = a.workspace_id AND mac.id = a.machine_id
WHERE a.workspace_id = $1
  AND a.status <> 'archived'
  AND mac.status = 'active'
  AND mac.created_by_user_id = $2
  AND NOT EXISTS (
      SELECT 1
      FROM scope_memberships existing
      WHERE existing.scope_id = $3
        AND existing.member_id = a.member_id
        AND existing.visibility IN ('public', 'private')
        AND existing.removed_at IS NULL
        AND existing.archived_at IS NULL
        AND existing.effective_to_seq IS NULL
  )
  AND ($4 = '' OR LOWER(a.name) LIKE $4 OR LOWER(m.display_name) LIKE $4
       OR ($8 <> '' AND (LOWER(a.name_pinyin) LIKE $8 OR LOWER(a.name_pinyin_initials) LIKE $8
       OR LOWER(m.display_name_pinyin) LIKE $8 OR LOWER(m.display_name_pinyin_initials) LIKE $8)))
  AND (($6 = '' AND $7 = '') OR (m.display_name > $6 OR (m.display_name = $6 AND a.id > $7)))
ORDER BY m.display_name ASC, a.id ASC
LIMIT $5
`,
		strings.TrimSpace(workspaceID),
		strings.TrimSpace(userID),
		strings.TrimSpace(channelID),
		queryPattern,
		normalized.Limit+1,
		cursor.DisplayName,
		cursor.AgentID,
		pinyinPattern,
	)
	if err != nil {
		return agent.AgentDirectoryPage{}, fmt.Errorf("list channel addable agents: %w", err)
	}
	defer rows.Close()

	items := make([]agent.AgentDirectoryItem, 0, normalized.Limit+1)
	for rows.Next() {
		var item agent.AgentDirectoryItem
		var inChannel bool
		if err := rows.Scan(
			&item.ID,
			&item.Name,
			&item.MemberID,
			&item.DisplayName,
			&item.AvatarURL,
			&item.OwnerUserID,
			&item.MachineID,
			&item.CanManage,
			&inChannel,
		); err != nil {
			return agent.AgentDirectoryPage{}, fmt.Errorf("scan channel addable agent: %w", err)
		}
		item.InChannel = &inChannel
		items = append(items, item)
	}
	if err := rows.Err(); err != nil {
		return agent.AgentDirectoryPage{}, fmt.Errorf("iterate channel addable agents: %w", err)
	}

	hasMore := len(items) > normalized.Limit
	if hasMore {
		items = items[:normalized.Limit]
	}
	nextCursor := ""
	if hasMore && len(items) > 0 {
		last := items[len(items)-1]
		nextCursor, err = encodeAgentDirectoryCursor(agentDirectoryCursor{
			DisplayName: last.DisplayName,
			AgentID:     last.ID,
		})
		if err != nil {
			return agent.AgentDirectoryPage{}, err
		}
	}
	return agent.AgentDirectoryPage{
		Agents:     items,
		Limit:      normalized.Limit,
		Cursor:     normalized.Cursor,
		HasMore:    hasMore,
		NextCursor: nextCursor,
	}, nil
}

func normalizeChannelAddableAgentDirectoryOptions(options agent.AgentDirectoryOptions) agent.AgentDirectoryOptions {
	limit := options.Limit
	if limit <= 0 {
		limit = agent.DefaultAgentSearchLimit
	}
	if limit > agent.MaxAgentSearchLimit {
		limit = agent.MaxAgentSearchLimit
	}
	return agent.AgentDirectoryOptions{
		Limit:  limit,
		Cursor: strings.TrimSpace(options.Cursor),
		Query:  strings.TrimSpace(options.Query),
	}
}
