package service

import (
	"context"
	"testing"
	"time"

	"github.com/stretchr/testify/require"
	core_event "github.com/tidbcloud/eventbus-datastruct/event"
	"github.com/tidbcloud/pkgv2/auth/auth0"
	"github.com/tidbcloud/serverless-service/internal/config"
	"github.com/tidbcloud/serverless-service/internal/domain/facade"
	"github.com/tidbcloud/serverless-service/internal/infra/eventbus"
	tidbmgmtservicev1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_mgmt_service/v1"
	"github.com/tidbcloud/tiinf/transport/tieventbus"
	"go.uber.org/mock/gomock"
)

func TestMarshalEventDetail(t *testing.T) {
	t.Parallel()
	require.Empty(t, marshalEventDetail(nil))
	require.JSONEq(t, `{"id":"cf-1","name":"cdc"}`, marshalEventDetail(map[string]string{
		"id":   "cf-1",
		"name": "cdc",
	}))
}

func TestParseUintID(t *testing.T) {
	t.Parallel()
	require.Equal(t, uint64(0), parseUintID(""))
	require.Equal(t, uint64(0), parseUintID("abc"))
	require.Equal(t, uint64(123), parseUintID("123"))
}

func TestAuthorFromCtx(t *testing.T) {
	t.Parallel()
	author, authorID := authorFromCtx(context.Background())
	require.Empty(t, author)
	require.Equal(t, uint64(0), authorID)

	ctx := context.WithValue(context.Background(), auth0.AuthTokenClaimKey, &facade.User{ID: 42})
	author, authorID = authorFromCtx(ctx)
	require.Empty(t, author)
	require.Equal(t, uint64(42), authorID)

	apiKeyCtx := context.WithValue(context.Background(), auth0.AuthTypeKey, config.ApiKeyAuthType)
	apiKeyCtx = context.WithValue(apiKeyCtx, config.CtxIdentityKey, "key-1")
	author, authorID = authorFromCtx(apiKeyCtx)
	require.Equal(t, "apikey-key-1", author)
	require.Equal(t, uint64(0), authorID)
}

func TestPublishDisplayEventSkipsNilBusOrCluster(t *testing.T) {
	t.Parallel()
	publishDisplayEvent(context.Background(), nil, &tidbmgmtservicev1.Cluster{ClusterId: 1}, "CreateChangefeed",
		"summary", nil)
	publishDisplayEvent(context.Background(), &eventbus.EventbusService{}, nil, "CreateChangefeed",
		"summary", nil)
}

func TestPublishDisplayEvent(t *testing.T) {
	ctrl := gomock.NewController(t)
	bus := eventbus.NewMockEventBusService(ctrl)
	published := make(chan *tieventbus.Event, 1)
	bus.EXPECT().Publish(gomock.Any()).DoAndReturn(func(event *tieventbus.Event) error {
		published <- event
		return nil
	})

	cluster := &tidbmgmtservicev1.Cluster{
		ClusterId:      11,
		ProjectId:      22,
		OrganizationId: 33,
	}
	ctx := context.WithValue(context.Background(), auth0.AuthTokenClaimKey, &facade.User{ID: 7})
	publishDisplayEvent(
		ctx,
		bus,
		cluster,
		"CreateChangefeed",
		"Changefeed demo created.",
		map[string]string{"id": "cf-1"},
	)

	evt := <-published
	require.Equal(t, core_event.DisplayEventTopic, evt.Type)
	data, ok := evt.Data.(*core_event.DisplayEvent)
	require.True(t, ok)
	require.Equal(t, "CreateChangefeed", data.DisplayEventType)
	require.Equal(t, core_event.EventStatusSucceeded, data.DisplayStatus)
	require.Equal(t, "Changefeed demo created.", data.Summary)
	require.Equal(t, uint64(11), data.ClusterID)
	require.Equal(t, uint64(22), data.ProjectID)
	require.Equal(t, uint64(33), data.TenantID)
	require.Empty(t, data.Author)
	require.Equal(t, uint64(7), data.AuthorID)
}

func TestPublishDisplayEventAsyncIgnoresRequestCancel(t *testing.T) {
	ctrl := gomock.NewController(t)
	bus := eventbus.NewMockEventBusService(ctrl)
	published := make(chan *tieventbus.Event, 1)
	bus.EXPECT().Publish(gomock.Any()).DoAndReturn(func(event *tieventbus.Event) error {
		published <- event
		return nil
	})

	ctx, cancel := context.WithCancel(context.Background())
	cancel()
	require.Error(t, ctx.Err())

	publishDisplayEventAsync(
		ctx,
		bus,
		func(ctx context.Context) *tidbmgmtservicev1.Cluster {
			require.NoError(t, ctx.Err())
			return &tidbmgmtservicev1.Cluster{ClusterId: 1, ProjectId: 2, OrganizationId: 3}
		},
		"CreateChangefeed",
		"summary",
		nil,
	)

	select {
	case evt := <-published:
		data, ok := evt.Data.(*core_event.DisplayEvent)
		require.True(t, ok)
		require.Equal(t, "CreateChangefeed", data.DisplayEventType)
	case <-time.After(2 * time.Second):
		t.Fatal("display event was not published after request context cancel")
	}
}
