package server

import (
	"context"
	"math/rand"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"

	nbpeer "github.com/netbirdio/netbird/management/server/peer"
	"github.com/netbirdio/netbird/management/server/store"
	"github.com/netbirdio/netbird/management/server/types"
)

// TestGroupIPv6Assignment verifies that peers gain or lose IPv6 addresses
// when they are added to or removed from an IPv6-enabled group.
func TestGroupIPv6Assignment(t *testing.T) {
	am, _, err := createManager(t)
	require.NoError(t, err)

	ctx := context.Background()
	userID := groupAdminUserID

	account, err := createAccount(am, "ipv6-grp-test", userID, "ipv6test.example.com")
	require.NoError(t, err)

	// Allocate IPv6 subnet for the account
	account.Network.NetV6 = types.AllocateIPv6Subnet(rand.New(rand.NewSource(time.Now().UnixNano())))
	require.NoError(t, am.Store.SaveAccount(ctx, account))

	// Create setup key
	setupKey, err := am.CreateSetupKey(ctx, account.Id, "ipv6-key", types.SetupKeyReusable, time.Hour, nil, 999, userID, false, false)
	require.NoError(t, err)

	// Create an IPv6-enabled group
	ipv6GroupID := "ipv6-enabled-grp"
	err = am.CreateGroup(ctx, account.Id, userID, &types.Group{
		ID:     ipv6GroupID,
		Name:   "IPv6 Enabled",
		Issued: types.GroupIssuedAPI,
		Peers:  []string{},
	})
	require.NoError(t, err)

	// Enable IPv6 on that group
	settings, err := am.Store.GetAccountSettings(ctx, store.LockingStrengthNone, account.Id)
	require.NoError(t, err)
	settings.IPv6EnabledGroups = []string{ipv6GroupID}
	require.NoError(t, am.Store.SaveAccountSettings(ctx, account.Id, settings))

	// Register a peer (will be in "All" group, not the IPv6 group)
	key, err := wgtypes.GeneratePrivateKey()
	require.NoError(t, err)

	peer, _, _, _, err := am.AddPeer(ctx, "", setupKey.Key, "", &nbpeer.Peer{
		Key:  key.PublicKey().String(),
		Meta: nbpeer.PeerSystemMeta{Hostname: "ipv6-test-host"},
	}, false)
	require.NoError(t, err)
	assert.False(t, peer.IPv6.IsValid(), "peer should not have IPv6 before joining an IPv6-enabled group")

	t.Run("GroupAddPeer assigns IPv6", func(t *testing.T) {
		err := am.GroupAddPeer(ctx, account.Id, ipv6GroupID, peer.ID)
		require.NoError(t, err)

		p, err := am.Store.GetPeerByID(ctx, store.LockingStrengthNone, account.Id, peer.ID)
		require.NoError(t, err)
		assert.True(t, p.IPv6.IsValid(), "peer should have an IPv6 address after joining the group")
	})

	t.Run("GroupDeletePeer clears IPv6", func(t *testing.T) {
		err := am.GroupDeletePeer(ctx, account.Id, ipv6GroupID, peer.ID)
		require.NoError(t, err)

		p, err := am.Store.GetPeerByID(ctx, store.LockingStrengthNone, account.Id, peer.ID)
		require.NoError(t, err)
		assert.False(t, p.IPv6.IsValid(), "peer should not have IPv6 after removal from the group")
	})

	t.Run("UpdateGroup with peer addition assigns IPv6", func(t *testing.T) {
		grp, err := am.Store.GetGroupByID(ctx, store.LockingStrengthNone, account.Id, ipv6GroupID)
		require.NoError(t, err)

		grp.Peers = append(grp.Peers, peer.ID)
		err = am.UpdateGroup(ctx, account.Id, userID, grp)
		require.NoError(t, err)

		p, err := am.Store.GetPeerByID(ctx, store.LockingStrengthNone, account.Id, peer.ID)
		require.NoError(t, err)
		assert.True(t, p.IPv6.IsValid(), "peer should have IPv6 after UpdateGroup adds it")
	})

	t.Run("UpdateGroup with peer removal clears IPv6", func(t *testing.T) {
		grp, err := am.Store.GetGroupByID(ctx, store.LockingStrengthNone, account.Id, ipv6GroupID)
		require.NoError(t, err)

		grp.Peers = []string{}
		err = am.UpdateGroup(ctx, account.Id, userID, grp)
		require.NoError(t, err)

		p, err := am.Store.GetPeerByID(ctx, store.LockingStrengthNone, account.Id, peer.ID)
		require.NoError(t, err)
		assert.False(t, p.IPv6.IsValid(), "peer should lose IPv6 after UpdateGroup removes it")
	})

	t.Run("non-IPv6 group changes do not affect IPv6", func(t *testing.T) {
		err := am.CreateGroup(ctx, account.Id, userID, &types.Group{
			ID:     "regular-grp",
			Name:   "Regular Group",
			Issued: types.GroupIssuedAPI,
			Peers:  []string{},
		})
		require.NoError(t, err)

		err = am.GroupAddPeer(ctx, account.Id, "regular-grp", peer.ID)
		require.NoError(t, err)

		p, err := am.Store.GetPeerByID(ctx, store.LockingStrengthNone, account.Id, peer.ID)
		require.NoError(t, err)
		assert.False(t, p.IPv6.IsValid(), "peer should not get IPv6 from a non-IPv6 group")
	})
}
