// Copyright 2024 The Prometheus 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 notifications

import (
	"sync"
	"testing"
	"time"

	"github.com/stretchr/testify/require"
)

// TestNotificationLifecycle tests adding, modifying, and deleting notifications.
func TestNotificationLifecycle(t *testing.T) {
	notifs := NewNotifications(10, nil)

	// Add a notification.
	notifs.AddNotification("Test Notification 1")

	// Check if the notification was added.
	notifications := notifs.Get()
	require.Len(t, notifications, 1, "Expected 1 notification after addition.")
	require.Equal(t, "Test Notification 1", notifications[0].Text, "Notification text mismatch.")
	require.True(t, notifications[0].Active, "Expected notification to be active.")

	// Modify the notification.
	notifs.AddNotification("Test Notification 1")
	notifications = notifs.Get()
	require.Len(t, notifications, 1, "Expected 1 notification after modification.")

	// Delete the notification.
	notifs.DeleteNotification("Test Notification 1")
	notifications = notifs.Get()
	require.Empty(t, notifications, "Expected no notifications after deletion.")
}

// TestSubscriberReceivesNotifications tests that a subscriber receives notifications, including modifications and deletions.
func TestSubscriberReceivesNotifications(t *testing.T) {
	notifs := NewNotifications(10, nil)

	// Subscribe to notifications.
	sub, unsubscribe, ok := notifs.Sub()
	require.True(t, ok)

	var wg sync.WaitGroup
	wg.Add(1)

	receivedNotifications := make([]Notification, 0)

	// Goroutine to listen for notifications.
	go func() {
		defer wg.Done()
		for notification := range sub {
			receivedNotifications = append(receivedNotifications, notification)
		}
	}()

	// Add notifications.
	notifs.AddNotification("Test Notification 1")
	notifs.AddNotification("Test Notification 2")

	// Modify a notification.
	notifs.AddNotification("Test Notification 1")

	// Delete a notification.
	notifs.DeleteNotification("Test Notification 2")

	// Wait for notifications to propagate.
	time.Sleep(100 * time.Millisecond)

	unsubscribe()
	wg.Wait() // Wait for the subscriber goroutine to finish.

	// Verify that we received the expected number of notifications.
	require.Len(t, receivedNotifications, 4, "Expected 4 notifications (2 active, 1 modified, 1 deleted).")

	// Check the content and state of received notifications.
	expected := []struct {
		Text   string
		Active bool
	}{
		{"Test Notification 1", true},
		{"Test Notification 2", true},
		{"Test Notification 1", true},
		{"Test Notification 2", false},
	}

	for i, n := range receivedNotifications {
		require.Equal(t, expected[i].Text, n.Text, "Notification text mismatch at index %d.", i)
		require.Equal(t, expected[i].Active, n.Active, "Notification active state mismatch at index %d.", i)
	}
}

// TestMultipleSubscribers tests that multiple subscribers receive notifications independently.
func TestMultipleSubscribers(t *testing.T) {
	notifs := NewNotifications(10, nil)

	// Subscribe two subscribers to notifications.
	sub1, unsubscribe1, ok1 := notifs.Sub()
	require.True(t, ok1)

	sub2, unsubscribe2, ok2 := notifs.Sub()
	require.True(t, ok2)

	var wg sync.WaitGroup
	wg.Add(2)

	receivedSub1 := make([]Notification, 0)
	receivedSub2 := make([]Notification, 0)

	// Goroutine for subscriber 1.
	go func() {
		defer wg.Done()
		for notification := range sub1 {
			receivedSub1 = append(receivedSub1, notification)
		}
	}()

	// Goroutine for subscriber 2.
	go func() {
		defer wg.Done()
		for notification := range sub2 {
			receivedSub2 = append(receivedSub2, notification)
		}
	}()

	// Add and delete notifications.
	notifs.AddNotification("Test Notification 1")
	notifs.DeleteNotification("Test Notification 1")

	// Wait for notifications to propagate.
	time.Sleep(100 * time.Millisecond)

	// Unsubscribe both.
	unsubscribe1()
	unsubscribe2()

	wg.Wait()

	// Both subscribers should have received the same 2 notifications.
	require.Len(t, receivedSub1, 2, "Expected 2 notifications for subscriber 1.")
	require.Len(t, receivedSub2, 2, "Expected 2 notifications for subscriber 2.")

	// Verify that both subscribers received the same notifications.
	for i := 0; i < 2; i++ {
		require.Equal(t, receivedSub1[i], receivedSub2[i], "Subscriber notification mismatch at index %d.", i)
	}
}

// TestUnsubscribe tests that unsubscribing prevents further notifications from being received.
func TestUnsubscribe(t *testing.T) {
	notifs := NewNotifications(10, nil)

	// Subscribe to notifications.
	sub, unsubscribe, ok := notifs.Sub()
	require.True(t, ok)

	var wg sync.WaitGroup
	wg.Add(1)

	receivedNotifications := make([]Notification, 0)

	// Goroutine to listen for notifications.
	go func() {
		defer wg.Done()
		for notification := range sub {
			receivedNotifications = append(receivedNotifications, notification)
		}
	}()

	// Add a notification and then unsubscribe.
	notifs.AddNotification("Test Notification 1")
	time.Sleep(100 * time.Millisecond) // Allow time for notification delivery.
	unsubscribe()                      // Unsubscribe.

	// Add another notification after unsubscribing.
	notifs.AddNotification("Test Notification 2")

	// Wait for the subscriber goroutine to finish.
	wg.Wait()

	// Only the first notification should have been received.
	require.Len(t, receivedNotifications, 1, "Expected 1 notification before unsubscribe.")
	require.Equal(t, "Test Notification 1", receivedNotifications[0].Text, "Unexpected notification text.")
}

// TestMaxSubscribers tests that exceeding the max subscribers limit prevents additional subscriptions.
func TestMaxSubscribers(t *testing.T) {
	maxSubscribers := 2
	notifs := NewNotifications(maxSubscribers, nil)

	// Subscribe the maximum number of subscribers.
	_, unsubscribe1, ok1 := notifs.Sub()
	require.True(t, ok1, "Expected first subscription to succeed.")

	_, unsubscribe2, ok2 := notifs.Sub()
	require.True(t, ok2, "Expected second subscription to succeed.")

	// Try to subscribe more than the max allowed.
	_, _, ok3 := notifs.Sub()
	require.False(t, ok3, "Expected third subscription to fail due to max subscriber limit.")

	// Unsubscribe one subscriber and try again.
	unsubscribe1()

	_, unsubscribe4, ok4 := notifs.Sub()
	require.True(t, ok4, "Expected subscription to succeed after unsubscribing a subscriber.")

	// Clean up the subscriptions.
	unsubscribe2()
	unsubscribe4()
}
