package channel

import (
	"context"
	"errors"
	"strings"
	"testing"
	"time"

	"github.com/go-sql-driver/mysql"
	"github.com/stretchr/testify/require"

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

type testExecResult struct {
	rowsAffected int64
}

func (result testExecResult) RowsAffected() int64 {
	return result.rowsAffected
}

type recordingExecOnly struct {
	query string
	args  []any
	err   error
}

func (execer *recordingExecOnly) Exec(_ context.Context, query string, args ...any) (coredb.Result, error) {
	execer.query = query
	execer.args = append([]any(nil), args...)
	return testExecResult{}, execer.err
}

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

	now := time.Date(2026, 4, 4, 13, 0, 0, 0, time.UTC)
	tests := []struct {
		name string
		err  error
	}{
		{
			name: "tidb primary key",
			err:  &mysql.MySQLError{Number: 1062, Message: "Duplicate entry 'chn-1-mem-1' for key 'PRIMARY'"},
		},
		{
			name: "tidb qualified primary key",
			err:  &mysql.MySQLError{Number: 1062, Message: "Duplicate entry 'chn-1-mem-1-1' for key 'scope_memberships.PRIMARY'"},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()

			execer := &recordingExecOnly{err: tt.err}
			err := InsertChannelMembershipIfMissing(context.Background(), execer, "chn-1", "mem-1", now)
			require.NoError(t, err)
			require.Equal(t, []any{"chn-1", "mem-1", now}, execer.args)
			require.Contains(t, execer.query, "INSERT INTO scope_memberships")
			require.Contains(t, execer.query, "visibility")
			require.NotContains(t, execer.query, "channel_memberships")
			require.NotContains(t, execer.query, channelMembershipsTableName("space", "participants"))
			require.NotContains(t, execer.query, "ON CONFLICT")
		})
	}
}

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

	execer := &recordingExecOnly{err: errors.New("boom")}
	err := InsertChannelMembershipIfMissing(context.Background(), execer, "chn-1", "mem-1", time.Date(2026, 4, 4, 13, 0, 0, 0, time.UTC))
	require.EqualError(t, err, "insert channel membership: boom")
}

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

	execer := &recordingExecOnly{}
	err := InsertAllChannelMembershipIfMissing(context.Background(), execer, "wsp-1", "mem-1", time.Date(2026, 4, 4, 13, 0, 0, 0, time.UTC))
	require.NoError(t, err)
	require.Equal(t, []any{"wsp-1", "mem-1", time.Date(2026, 4, 4, 13, 0, 0, 0, time.UTC)}, execer.args)
	require.Contains(t, execer.query, "INSERT INTO scope_memberships")
	require.Contains(t, execer.query, "JOIN scope_channel_profiles")
	require.NotContains(t, execer.query, "channel_memberships")
	require.NotContains(t, execer.query, channelMembershipsTableName("space", "participants"))
	require.NotContains(t, execer.query, strings.Join([]string{"JOIN", "space" + "s"}, " "))
	require.NotContains(t, execer.query, "INSERT IGNORE")
	require.NotContains(t, execer.query, "ON CONFLICT")
}

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

	now := time.Date(2026, 4, 4, 13, 0, 0, 0, time.UTC)
	tests := []struct {
		name string
		err  error
	}{
		{
			name: "tidb primary key",
			err:  &mysql.MySQLError{Number: 1062, Message: "Duplicate entry 'chn-1-mem-1' for key 'PRIMARY'"},
		},
		{
			name: "tidb qualified primary key",
			err:  &mysql.MySQLError{Number: 1062, Message: "Duplicate entry 'chn-1-mem-1-1' for key 'scope_memberships.PRIMARY'"},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()

			execer := &recordingExecOnly{err: tt.err}
			err := InsertAllChannelMembershipIfMissing(context.Background(), execer, "wsp-1", "mem-1", now)
			require.NoError(t, err)
			require.Equal(t, []any{"wsp-1", "mem-1", now}, execer.args)
			require.NotContains(t, execer.query, "channel_memberships")
			require.NotContains(t, execer.query, channelMembershipsTableName("space", "participants"))
			require.NotContains(t, execer.query, "INSERT IGNORE")
			require.NotContains(t, execer.query, "ON CONFLICT")
		})
	}
}

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