// Copyright 2026 Woodpecker Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package pubsub_test

import (
	"context"
	"sync"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"

	"go.woodpecker-ci.org/woodpecker/v3/server/pubsub"
	"go.woodpecker-ci.org/woodpecker/v3/server/pubsub/memory"
)

func TestPubSub(t *testing.T) {
	// for each pubsub adapter (currently we have only one)
	t.Run("in_memory", func(t *testing.T) {
		testPubSub(t, memory.New())
	})
}

func testPubSub(t *testing.T, adapter pubsub.PubSub) {
	assert.NoError(
		t,
		adapter.Publish(t.Context(), pubsub.Topics{"a": {}}, pubsub.Message{ID: "1", Data: []byte(`dummy`)}),
		"expect no issue publish to a pubsub with no subscribers",
	)

	t.Run("test deduplication asumptions", func(t *testing.T) {
		treeTopicCloser, treeTopicGetMSGs := genTestSub(t, adapter, pubsub.Topics{"tree": {}})
		t.Cleanup(treeTopicCloser)
		closer, getMSGs := genTestSub(t, adapter, pubsub.Topics{"apples": {}, "tree": {}, "raspberry": {}})
		t.Cleanup(closer)
		assert.Len(t, getMSGs(), 0)

		time.Sleep(10 * time.Millisecond)
		assert.NoError(t, adapter.Publish(t.Context(), pubsub.Topics{"tree": {}, "raspberry": {}, "tails": {}}, pubsub.Message{ID: "2"}))
		assert.NoError(t, adapter.Publish(t.Context(), pubsub.Topics{"apples": {}, "raspberry": {}, "tails": {}}, pubsub.Message{ID: "3"}))
		time.Sleep(100 * time.Millisecond)

		if assert.Len(t, getMSGs(), 2) {
			assert.ElementsMatch(t, []string{"2", "3"}, messagesToIDs(getMSGs()))
		}

		assert.EqualValues(t, "2", treeTopicGetMSGs()[0].ID)
	})

	t.Run("test adapters calc for strange input", func(t *testing.T) {
		t.Run("empty topic", func(t *testing.T) {
			assert.Error(t, adapter.Subscribe(t.Context(), nil, func(pubsub.Message) {}))
			assert.Error(t, adapter.Subscribe(t.Context(), pubsub.Topics{}, func(pubsub.Message) {}))

			assert.Error(t, adapter.Publish(t.Context(), nil, pubsub.Message{}))
			assert.Error(t, adapter.Publish(t.Context(), pubsub.Topics{}, pubsub.Message{}))
		})
	})
}

func genTestSub(t *testing.T, adapter pubsub.PubSub, topics pubsub.Topics) (close func(), getMSGs func() []pubsub.Message) {
	ctx, closer := context.WithCancelCause(t.Context())
	var mu sync.Mutex
	var messages []pubsub.Message

	go func() {
		err := adapter.Subscribe(ctx, topics, func(m pubsub.Message) {
			mu.Lock()
			messages = append(messages, m)
			mu.Unlock()
		})
		assert.NoError(t, err)
	}()

	return func() { closer(nil) }, func() []pubsub.Message {
		mu.Lock()
		defer mu.Unlock()
		cp := make([]pubsub.Message, len(messages))
		copy(cp, messages)
		return cp
	}
}

func messagesToIDs(msgs []pubsub.Message) (ids []string) {
	for i := range msgs {
		ids = append(ids, msgs[i].ID)
	}
	return ids
}
