package channel

import (
	"context"
	"os"
	"strings"
	"testing"
	"time"

	"github.com/stretchr/testify/require"

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

func TestDiscoverableChannelsUseScopeMembership(t *testing.T) {
	t.Parallel()

	sourceBytes, err := os.ReadFile("discovery.go")
	require.NoError(t, err)
	source := string(sourceBytes)
	require.Contains(t, source, "FROM scopes channel_scope")
	require.Contains(t, source, "JOIN scope_channel_profiles profile")
	require.Contains(t, source, "LEFT JOIN scope_memberships member_scope")
	require.NotContains(t, source, channelDiscoveryTableName("space", "participants"))

	ctx := context.Background()
	pool := startChannelDiscoveryTestDB(t, ctx)
	now := time.Date(2026, 5, 14, 11, 15, 0, 0, time.UTC)

	const (
		workspaceID     = "wsp_channel_discovery_scope"
		ownerUserID     = "usr_channel_discovery_owner"
		ownerMemberID   = "mem_channel_discovery_owner"
		viewerUserID    = "usr_channel_discovery_viewer"
		viewerMemberID  = "mem_channel_discovery_viewer"
		openChannelID   = "chn_channel_discovery_open"
		joinedChannelID = "chn_channel_discovery_joined"
		closedChannelID = "chn_channel_discovery_closed"
	)

	seedChannelDiscoveryWorkspace(t, ctx, pool, workspaceID, ownerUserID, ownerMemberID, now)
	seedChannelDiscoveryUserMember(t, ctx, pool, workspaceID, viewerUserID, viewerMemberID, now)
	seedChannelDiscoveryChannelScope(t, ctx, pool, workspaceID, openChannelID, "discover-open", ownerMemberID, now)
	seedChannelDiscoveryChannelScope(t, ctx, pool, workspaceID, joinedChannelID, "discover-joined", ownerMemberID, now)
	seedChannelDiscoveryChannelScope(t, ctx, pool, workspaceID, closedChannelID, "discover-closed", ownerMemberID, now)
	mustExecChannelDiscovery(t, ctx, pool, `
INSERT INTO scope_memberships (
    scope_id, member_id, visibility, owner_member_id, last_read_seq,
    last_read_global_seq, last_activity_global_seq, owner_context_dismissed_through_global_seq,
    effective_from_seq, effective_to_seq, created_at, updated_at, removed_at, archived_at
) VALUES ($1, $2, 'public', NULL, 0, 0, 0, 0, 0, NULL, $3, $3, NULL, NULL)
`, joinedChannelID, viewerMemberID, now)
	mustExecChannelDiscovery(t, ctx, pool, `
INSERT INTO scope_memberships (
    scope_id, member_id, visibility, owner_member_id, last_read_seq,
    last_read_global_seq, last_activity_global_seq, owner_context_dismissed_through_global_seq,
    effective_from_seq, effective_to_seq, created_at, updated_at, removed_at, archived_at
) VALUES ($1, $2, 'public', NULL, 0, 0, 0, 0, 0, 1, $3, $3, NULL, NULL)
`, closedChannelID, viewerMemberID, now)

	store := New(pool, Options{RequireWorkspaceAccess: func(ctx context.Context, userID string, workspaceID string) (string, error) {
		return channelDiscoveryRequireWorkspaceAccess(ctx, pool, userID, workspaceID)
	}})
	channels, err := store.ListDiscoverableChannelsForUser(ctx, viewerUserID, workspaceID)
	require.NoError(t, err)
	require.Equal(t, []DiscoverableChannel{
		{
			ChannelID:   closedChannelID,
			Slug:        "discover-closed",
			DisplayName: "discover-closed",
			Description: "",
			Visibility:  "public",
		},
		{
			ChannelID:   openChannelID,
			Slug:        "discover-open",
			DisplayName: "discover-open",
			Description: "",
			Visibility:  "public",
		},
	}, channels)

	channel, err := store.DiscoverableChannelForUser(ctx, viewerUserID, workspaceID, openChannelID)
	require.NoError(t, err)
	require.Equal(t, openChannelID, channel.ChannelID)

	_, err = store.DiscoverableChannelForUser(ctx, viewerUserID, workspaceID, joinedChannelID)
	require.ErrorIs(t, err, ErrNotFound)
}

var channelDiscoveryTiDB testkit.TiDBCoreDBFixture

func TestMain(m *testing.M) {
	os.Exit(testkit.RunTestsWithTiDBCoreDBFixture(m, "channel", &channelDiscoveryTiDB))
}

func startChannelDiscoveryTestDB(t *testing.T, ctx context.Context) coredb.DB {
	t.Helper()
	return channelDiscoveryTiDB.RollbackDB(t, ctx)
}

func channelDiscoveryRequireWorkspaceAccess(ctx context.Context, queryer coredb.QueryRower, userID string, workspaceID string) (string, error) {
	row := queryer.QueryRow(ctx, `
SELECT id
FROM members
WHERE workspace_id = $1
  AND user_id = $2
  AND status = 'active'
LIMIT 1
`, workspaceID, userID)
	var memberID string
	if err := row.Scan(&memberID); err != nil {
		return "", err
	}
	return memberID, nil
}

func seedChannelDiscoveryWorkspace(t *testing.T, ctx context.Context, pool coredb.QueryExecer, workspaceID string, ownerUserID string, ownerMemberID string, now time.Time) {
	t.Helper()
	orgID := workspaceID + "_org"
	mustExecChannelDiscovery(t, ctx, pool, `
INSERT INTO users (id, email, name, display_name, password_hash, email_verified_at, created_at, updated_at)
VALUES ($1, $2, $1, $1, 'hash', $3, $3, $3)
`, ownerUserID, ownerUserID+"@example.com", now)
	mustExecChannelDiscovery(t, ctx, pool, `
INSERT INTO organizations (id, slug, name, created_by_user_id, created_at, updated_at)
VALUES ($1, $2, $2, $3, $4, $4)
`, orgID, strings.ReplaceAll(orgID, "_", "-"), ownerUserID, now)
	mustExecChannelDiscovery(t, ctx, pool, `
INSERT INTO workspaces (id, organization_id, slug, name, latest_message_seq, created_at, updated_at)
VALUES ($1, $2, $3, $3, 0, $4, $4)
`, workspaceID, orgID, strings.ReplaceAll(workspaceID, "_", "-"), now)
	mustExecChannelDiscovery(t, ctx, pool, `
INSERT INTO members (id, workspace_id, type, display_name, user_id, workspace_role, status, created_at, updated_at)
VALUES ($1, $2, 'human', $1, $3, 'admin', 'active', $4, $4)
`, ownerMemberID, workspaceID, ownerUserID, now)
}

func seedChannelDiscoveryUserMember(t *testing.T, ctx context.Context, pool coredb.QueryExecer, workspaceID string, userID string, memberID string, now time.Time) {
	t.Helper()
	mustExecChannelDiscovery(t, ctx, pool, `
INSERT INTO users (id, email, name, display_name, password_hash, email_verified_at, created_at, updated_at)
VALUES ($1, $2, $1, $1, 'hash', $3, $3, $3)
`, userID, userID+"@example.com", now)
	mustExecChannelDiscovery(t, ctx, pool, `
INSERT INTO members (id, workspace_id, type, display_name, user_id, workspace_role, status, created_at, updated_at)
VALUES ($1, $2, 'human', $1, $3, 'member', 'active', $4, $4)
`, memberID, workspaceID, userID, now)
}

func seedChannelDiscoveryChannelScope(t *testing.T, ctx context.Context, pool coredb.QueryExecer, workspaceID string, channelID string, slug string, ownerMemberID string, now time.Time) {
	t.Helper()
	mustExecChannelDiscovery(t, ctx, pool, `
INSERT INTO scopes (id, workspace_id, type, identity_key, latest_seq, latest_global_seq, latest_message_at, archived_until_seq, status, created_by_member_id, created_at, updated_at)
VALUES ($1, $2, 'channel', $3, 0, 0, NULL, 0, 'active', $4, $5, $5)
`, channelID, workspaceID, "channel:"+channelID, ownerMemberID, now)
	mustExecChannelDiscovery(t, ctx, pool, `
INSERT INTO scope_channel_profiles (scope_id, workspace_id, slug, display_name, description, visibility, owner_member_id, is_all_channel, created_at, updated_at)
VALUES ($1, $2, $3, $3, '', 'public', $4, FALSE, $5, $5)
`, channelID, workspaceID, slug, ownerMemberID, now)
	mustExecChannelDiscovery(t, ctx, pool, `
INSERT INTO scope_memberships (
    scope_id, member_id, visibility, owner_member_id, last_read_seq,
    last_read_global_seq, last_activity_global_seq, owner_context_dismissed_through_global_seq,
    effective_from_seq, effective_to_seq, created_at, updated_at, removed_at, archived_at
) VALUES ($1, $2, 'public', NULL, 0, 0, 0, 0, 0, NULL, $3, $3, NULL, NULL)
`, channelID, ownerMemberID, now)
}

func channelDiscoveryTableName(parts ...string) string {
	return strings.Join(parts, "_")
}

func mustExecChannelDiscovery(t *testing.T, ctx context.Context, pool coredb.QueryExecer, query string, args ...any) {
	t.Helper()
	_, err := pool.Exec(ctx, query, args...)
	require.NoError(t, err)
}
