package channel

import (
	"context"
	"fmt"
	"strconv"
	"strings"

	"github.com/tidbcloud/AgentTeam/internal/coredb"
)

type DiscoverableChannel struct {
	ChannelID   string
	Slug        string
	DisplayName string
	Description string
	Visibility  string
}

const (
	DefaultDiscoverableChannelListLimit = 50
	MaxDiscoverableChannelListLimit     = 100
)

type DiscoverableChannelPage struct {
	Channels   []DiscoverableChannel
	HasMore    bool
	NextCursor string
}

type DiscoverableChannelListOptions struct {
	Limit  int
	Cursor string
	Query  string
}

func (store *Store) ListDiscoverableChannelsForUser(ctx context.Context, userID string, workspaceID string) ([]DiscoverableChannel, error) {
	page, err := store.ListDiscoverableChannelsForUserPage(ctx, userID, workspaceID, DiscoverableChannelListOptions{Limit: MaxDiscoverableChannelListLimit})
	if err != nil {
		return nil, err
	}
	return page.Channels, nil
}

func (store *Store) ListDiscoverableChannelsForUserPage(ctx context.Context, userID string, workspaceID string, options DiscoverableChannelListOptions) (DiscoverableChannelPage, error) {
	memberID, err := store.requireWorkspaceAccess(ctx, userID, workspaceID)
	if err != nil {
		return DiscoverableChannelPage{}, err
	}
	limit := normalizeDiscoverableChannelListLimit(options.Limit)
	offset, err := decodeOffsetCursor(options.Cursor)
	if err != nil {
		return DiscoverableChannelPage{}, err
	}
	queryPrefix := ""
	if trimmedQuery := strings.TrimSpace(options.Query); trimmedQuery != "" {
		queryPrefix = strings.ToLower(trimmedQuery) + "%"
	}

	rows, err := store.db.Query(ctx, `
SELECT
    channel_scope.id,
    COALESCE(NULLIF(profile.slug, ''), channel_scope.id),
    COALESCE(NULLIF(profile.display_name, ''), NULLIF(profile.slug, ''), channel_scope.id),
    COALESCE(profile.description, ''),
    COALESCE(NULLIF(profile.visibility, ''), 'public')
FROM scopes channel_scope
JOIN scope_channel_profiles profile
  ON profile.scope_id = channel_scope.id
 AND profile.workspace_id = channel_scope.workspace_id
LEFT JOIN scope_memberships member_scope
  ON member_scope.scope_id = channel_scope.id
 AND member_scope.member_id = $2
 AND member_scope.removed_at IS NULL
 AND member_scope.archived_at IS NULL
 AND member_scope.effective_to_seq IS NULL
WHERE channel_scope.workspace_id = $1
  AND channel_scope.type = 'channel'
  AND channel_scope.status = 'active'
  AND channel_scope.archived_at IS NULL
  AND profile.visibility = 'public'
  AND profile.subsystem = 'im'
  AND profile.owner_member_id IS NOT NULL
  AND member_scope.member_id IS NULL
  AND ($5 = '' OR LOWER(profile.slug) LIKE $5 OR LOWER(profile.display_name) LIKE $5 OR LOWER(COALESCE(profile.description, '')) LIKE $5 OR LOWER(channel_scope.id) LIKE $5)
ORDER BY profile.slug ASC
LIMIT $3 OFFSET $4
`, workspaceID, memberID, limit+1, offset, queryPrefix)
	if err != nil {
		return DiscoverableChannelPage{}, fmt.Errorf("list discoverable channels: %w", err)
	}
	defer rows.Close()

	channels := make([]DiscoverableChannel, 0)
	for rows.Next() {
		var channel DiscoverableChannel
		if err := rows.Scan(&channel.ChannelID, &channel.Slug, &channel.DisplayName, &channel.Description, &channel.Visibility); err != nil {
			return DiscoverableChannelPage{}, fmt.Errorf("scan discoverable channel: %w", err)
		}
		channels = append(channels, channel)
	}
	if err := rows.Err(); err != nil {
		return DiscoverableChannelPage{}, fmt.Errorf("iterate discoverable channels: %w", err)
	}
	hasMore := len(channels) > limit
	if hasMore {
		channels = channels[:limit]
	}
	nextCursor := ""
	if hasMore {
		nextCursor = strconv.Itoa(offset + limit)
	}
	return DiscoverableChannelPage{Channels: channels, HasMore: hasMore, NextCursor: nextCursor}, nil
}

func normalizeDiscoverableChannelListLimit(limit int) int {
	if limit <= 0 {
		return DefaultDiscoverableChannelListLimit
	}
	if limit > MaxDiscoverableChannelListLimit {
		return MaxDiscoverableChannelListLimit
	}
	return limit
}

func decodeOffsetCursor(raw string) (int, error) {
	trimmed := strings.TrimSpace(raw)
	if trimmed == "" {
		return 0, nil
	}
	offset, err := strconv.Atoi(trimmed)
	if err != nil || offset < 0 {
		return 0, fmt.Errorf("cursor must be a non-negative integer")
	}
	return offset, nil
}

func (store *Store) DiscoverableChannelForUser(ctx context.Context, userID string, workspaceID string, channelID string) (DiscoverableChannel, error) {
	memberID, err := store.requireWorkspaceAccess(ctx, userID, workspaceID)
	if err != nil {
		return DiscoverableChannel{}, err
	}

	row := store.db.QueryRow(ctx, `
SELECT
    channel_scope.id,
    COALESCE(NULLIF(profile.slug, ''), channel_scope.id),
    COALESCE(NULLIF(profile.display_name, ''), NULLIF(profile.slug, ''), channel_scope.id),
    COALESCE(profile.description, ''),
    COALESCE(NULLIF(profile.visibility, ''), 'public')
FROM scopes channel_scope
JOIN scope_channel_profiles profile
  ON profile.scope_id = channel_scope.id
 AND profile.workspace_id = channel_scope.workspace_id
LEFT JOIN scope_memberships member_scope
  ON member_scope.scope_id = channel_scope.id
 AND member_scope.member_id = $2
 AND member_scope.removed_at IS NULL
 AND member_scope.archived_at IS NULL
 AND member_scope.effective_to_seq IS NULL
WHERE channel_scope.workspace_id = $1
  AND channel_scope.id = $3
  AND channel_scope.type = 'channel'
  AND channel_scope.status = 'active'
  AND channel_scope.archived_at IS NULL
  AND profile.visibility = 'public'
  AND profile.subsystem = 'im'
  AND profile.owner_member_id IS NOT NULL
  AND member_scope.member_id IS NULL
`, workspaceID, memberID, channelID)

	var channel DiscoverableChannel
	if err := row.Scan(&channel.ChannelID, &channel.Slug, &channel.DisplayName, &channel.Description, &channel.Visibility); err != nil {
		if coredb.IsNotFound(err) {
			return DiscoverableChannel{}, ErrNotFound
		}
		return DiscoverableChannel{}, fmt.Errorf("load discoverable channel: %w", err)
	}
	return channel, nil
}
