package ws

import (
	"sync"
	"testing"

	"github.com/mattermost/focalboard/server/auth"
	"github.com/mattermost/focalboard/server/model"

	"github.com/mattermost/mattermost-server/v6/shared/mlog"

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

func TestTeamSubscription(t *testing.T) {
	server := NewServer(&auth.Auth{}, "token", false, &mlog.Logger{}, nil)
	session := &websocketSession{
		conn:   &websocket.Conn{},
		mu:     sync.Mutex{},
		teams:  []string{},
		blocks: []string{},
	}
	teamID := "fake-team-id"

	t.Run("Should correctly add a session", func(t *testing.T) {
		server.addListener(session)
		require.Len(t, server.listeners, 1)
		require.Empty(t, server.listenersByTeam)
		require.Empty(t, session.teams)
	})

	t.Run("Should correctly subscribe to a team", func(t *testing.T) {
		require.False(t, session.isSubscribedToTeam(teamID))

		server.subscribeListenerToTeam(session, teamID)

		require.Len(t, server.listenersByTeam[teamID], 1)
		require.Contains(t, server.listenersByTeam[teamID], session)
		require.Len(t, session.teams, 1)
		require.Contains(t, session.teams, teamID)

		require.True(t, session.isSubscribedToTeam(teamID))
	})

	t.Run("Subscribing again to a subscribed team would have no effect", func(t *testing.T) {
		require.True(t, session.isSubscribedToTeam(teamID))

		server.subscribeListenerToTeam(session, teamID)

		require.Len(t, server.listenersByTeam[teamID], 1)
		require.Contains(t, server.listenersByTeam[teamID], session)
		require.Len(t, session.teams, 1)
		require.Contains(t, session.teams, teamID)

		require.True(t, session.isSubscribedToTeam(teamID))
	})

	t.Run("Should correctly unsubscribe to a team", func(t *testing.T) {
		require.True(t, session.isSubscribedToTeam(teamID))

		server.unsubscribeListenerFromTeam(session, teamID)

		require.Empty(t, server.listenersByTeam[teamID])
		require.Empty(t, session.teams)

		require.False(t, session.isSubscribedToTeam(teamID))
	})

	t.Run("Unsubscribing again to an unsubscribed team would have no effect", func(t *testing.T) {
		require.False(t, session.isSubscribedToTeam(teamID))

		server.unsubscribeListenerFromTeam(session, teamID)

		require.Empty(t, server.listenersByTeam[teamID])
		require.Empty(t, session.teams)

		require.False(t, session.isSubscribedToTeam(teamID))
	})

	t.Run("Should correctly be removed from the server", func(t *testing.T) {
		server.removeListener(session)

		require.Empty(t, server.listeners)
	})

	t.Run("If subscribed to teams and removed, should be removed from the teams subscription list", func(t *testing.T) {
		teamID2 := "other-fake-team-id"

		server.addListener(session)
		server.subscribeListenerToTeam(session, teamID)
		server.subscribeListenerToTeam(session, teamID2)

		require.Len(t, server.listeners, 1)
		require.Contains(t, server.listenersByTeam[teamID], session)
		require.Contains(t, server.listenersByTeam[teamID2], session)

		server.removeListener(session)

		require.Empty(t, server.listeners)
		require.Empty(t, server.listenersByTeam[teamID])
		require.Empty(t, server.listenersByTeam[teamID2])
	})
}

func TestBlocksSubscription(t *testing.T) {
	server := NewServer(&auth.Auth{}, "token", false, &mlog.Logger{}, nil)
	session := &websocketSession{
		conn:   &websocket.Conn{},
		mu:     sync.Mutex{},
		teams:  []string{},
		blocks: []string{},
	}
	blockID1 := "block1"
	blockID2 := "block2"
	blockID3 := "block3"
	blockIDs := []string{blockID1, blockID2, blockID3}

	t.Run("Should correctly add a session", func(t *testing.T) {
		server.addListener(session)
		require.Len(t, server.listeners, 1)
		require.Empty(t, server.listenersByTeam)
		require.Empty(t, session.teams)
	})

	t.Run("Should correctly subscribe to a set of blocks", func(t *testing.T) {
		require.False(t, session.isSubscribedToBlock(blockID1))
		require.False(t, session.isSubscribedToBlock(blockID2))
		require.False(t, session.isSubscribedToBlock(blockID3))

		server.subscribeListenerToBlocks(session, blockIDs)

		require.Len(t, server.listenersByBlock[blockID1], 1)
		require.Contains(t, server.listenersByBlock[blockID1], session)
		require.Len(t, server.listenersByBlock[blockID2], 1)
		require.Contains(t, server.listenersByBlock[blockID2], session)
		require.Len(t, server.listenersByBlock[blockID3], 1)
		require.Contains(t, server.listenersByBlock[blockID3], session)
		require.Len(t, session.blocks, 3)
		require.ElementsMatch(t, blockIDs, session.blocks)

		require.True(t, session.isSubscribedToBlock(blockID1))
		require.True(t, session.isSubscribedToBlock(blockID2))
		require.True(t, session.isSubscribedToBlock(blockID3))

		t.Run("Subscribing again to a subscribed block would have no effect", func(t *testing.T) {
			require.True(t, session.isSubscribedToBlock(blockID1))
			require.True(t, session.isSubscribedToBlock(blockID2))
			require.True(t, session.isSubscribedToBlock(blockID3))

			server.subscribeListenerToBlocks(session, blockIDs)

			require.Len(t, server.listenersByBlock[blockID1], 1)
			require.Contains(t, server.listenersByBlock[blockID1], session)
			require.Len(t, server.listenersByBlock[blockID2], 1)
			require.Contains(t, server.listenersByBlock[blockID2], session)
			require.Len(t, server.listenersByBlock[blockID3], 1)
			require.Contains(t, server.listenersByBlock[blockID3], session)
			require.Len(t, session.blocks, 3)
			require.ElementsMatch(t, blockIDs, session.blocks)

			require.True(t, session.isSubscribedToBlock(blockID1))
			require.True(t, session.isSubscribedToBlock(blockID2))
			require.True(t, session.isSubscribedToBlock(blockID3))
		})
	})

	t.Run("Should correctly unsubscribe to a set of blocks", func(t *testing.T) {
		require.True(t, session.isSubscribedToBlock(blockID1))
		require.True(t, session.isSubscribedToBlock(blockID2))
		require.True(t, session.isSubscribedToBlock(blockID3))

		server.unsubscribeListenerFromBlocks(session, blockIDs)

		require.Empty(t, server.listenersByBlock[blockID1])
		require.Empty(t, server.listenersByBlock[blockID2])
		require.Empty(t, server.listenersByBlock[blockID3])
		require.Empty(t, session.blocks)

		require.False(t, session.isSubscribedToBlock(blockID1))
		require.False(t, session.isSubscribedToBlock(blockID2))
		require.False(t, session.isSubscribedToBlock(blockID3))
	})

	t.Run("Unsubscribing again to an unsubscribed block would have no effect", func(t *testing.T) {
		require.False(t, session.isSubscribedToBlock(blockID1))

		server.unsubscribeListenerFromBlocks(session, []string{blockID1})

		require.Empty(t, server.listenersByBlock[blockID1])
		require.Empty(t, session.blocks)

		require.False(t, session.isSubscribedToBlock(blockID1))
	})

	t.Run("Should correctly be removed from the server", func(t *testing.T) {
		server.removeListener(session)

		require.Empty(t, server.listeners)
	})

	t.Run("If subscribed to blocks and removed, should be removed from the blocks subscription list", func(t *testing.T) {
		server.addListener(session)
		server.subscribeListenerToBlocks(session, blockIDs)

		require.Len(t, server.listeners, 1)
		require.Len(t, server.listenersByBlock[blockID1], 1)
		require.Contains(t, server.listenersByBlock[blockID1], session)
		require.Len(t, server.listenersByBlock[blockID2], 1)
		require.Contains(t, server.listenersByBlock[blockID2], session)
		require.Len(t, server.listenersByBlock[blockID3], 1)
		require.Contains(t, server.listenersByBlock[blockID3], session)
		require.Len(t, session.blocks, 3)
		require.ElementsMatch(t, blockIDs, session.blocks)

		server.removeListener(session)

		require.Empty(t, server.listeners)
		require.Empty(t, server.listenersByBlock[blockID1])
		require.Empty(t, server.listenersByBlock[blockID2])
		require.Empty(t, server.listenersByBlock[blockID3])
	})
}

func TestGetUserIDForTokenInSingleUserMode(t *testing.T) {
	singleUserToken := "single-user-token"
	server := NewServer(&auth.Auth{}, "token", false, &mlog.Logger{}, nil)
	server.singleUserToken = singleUserToken

	t.Run("Should return nothing if the token is empty", func(t *testing.T) {
		require.Empty(t, server.getUserIDForToken(""))
	})

	t.Run("Should return nothing if the token is invalid", func(t *testing.T) {
		require.Empty(t, server.getUserIDForToken("invalid-token"))
	})

	t.Run("Should return the single user ID if the token is correct", func(t *testing.T) {
		require.Equal(t, model.SingleUser, server.getUserIDForToken(singleUserToken))
	})
}
