package storetests

import (
	"math"
	"testing"

	"github.com/mattermost/focalboard/server/model"
	"github.com/mattermost/focalboard/server/services/store"
	"github.com/mattermost/focalboard/server/utils"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func StoreTestComplianceHistoryStore(t *testing.T, setup func(t *testing.T) (store.Store, func())) {
	t.Run("GetBoardsForCompliance", func(t *testing.T) {
		store, tearDown := setup(t)
		defer tearDown()
		testGetBoardsForCompliance(t, store)
	})
	t.Run("GetBoardsComplianceHistory", func(t *testing.T) {
		store, tearDown := setup(t)
		defer tearDown()
		testGetBoardsComplianceHistory(t, store)
	})
	t.Run("GetBlocksComplianceHistory", func(t *testing.T) {
		store, tearDown := setup(t)
		defer tearDown()
		testGetBlocksComplianceHistory(t, store)
	})
}

func testGetBoardsForCompliance(t *testing.T, store store.Store) {
	team1 := testTeamID
	team2 := utils.NewID(utils.IDTypeTeam)

	boardsAdded1 := createTestBoards(t, store, team1, testUserID, 10)
	boardsAdded2 := createTestBoards(t, store, team2, testUserID, 7)

	deleteTestBoard(t, store, boardsAdded1[0].ID, testUserID)
	deleteTestBoard(t, store, boardsAdded1[1].ID, testUserID)
	boardsAdded1 = boardsAdded1[2:]

	t.Run("Invalid teamID", func(t *testing.T) {
		opts := model.QueryBoardsForComplianceOptions{
			TeamID: utils.NewID(utils.IDTypeTeam),
		}

		boards, hasMore, err := store.GetBoardsForCompliance(opts)

		assert.Empty(t, boards)
		assert.False(t, hasMore)
		assert.NoError(t, err)
	})

	t.Run("All teams", func(t *testing.T) {
		opts := model.QueryBoardsForComplianceOptions{}

		boards, hasMore, err := store.GetBoardsForCompliance(opts)

		assert.ElementsMatch(t, extractIDs(t, boards), extractIDs(t, boardsAdded1, boardsAdded2))
		assert.False(t, hasMore)
		assert.NoError(t, err)
	})

	t.Run("Specific team", func(t *testing.T) {
		opts := model.QueryBoardsForComplianceOptions{
			TeamID: team1,
		}

		boards, hasMore, err := store.GetBoardsForCompliance(opts)

		assert.ElementsMatch(t, extractIDs(t, boards), extractIDs(t, boardsAdded1))
		assert.False(t, hasMore)
		assert.NoError(t, err)
	})

	t.Run("Pagination", func(t *testing.T) {
		opts := model.QueryBoardsForComplianceOptions{
			Page:    0,
			PerPage: 3,
		}

		reps := 0
		allBoards := make([]*model.Board, 0, 20)

		for {
			boards, hasMore, err := store.GetBoardsForCompliance(opts)
			require.NoError(t, err)
			require.NotEmpty(t, boards)
			allBoards = append(allBoards, boards...)

			if !hasMore {
				break
			}
			opts.Page++
			reps++
		}

		assert.ElementsMatch(t, extractIDs(t, allBoards), extractIDs(t, boardsAdded1, boardsAdded2))
	})
}

func testGetBoardsComplianceHistory(t *testing.T, store store.Store) {
	team1 := testTeamID
	team2 := utils.NewID(utils.IDTypeTeam)

	boardsTeam1 := createTestBoards(t, store, team1, testUserID, 11)
	boardsTeam2 := createTestBoards(t, store, team2, testUserID, 7)
	boardsAdded := make([]*model.Board, 0)
	boardsAdded = append(boardsAdded, boardsTeam1...)
	boardsAdded = append(boardsAdded, boardsTeam2...)

	deleteTestBoard(t, store, boardsTeam1[0].ID, testUserID)
	deleteTestBoard(t, store, boardsTeam1[1].ID, testUserID)
	boardsDeleted := boardsTeam1[0:2]
	boardsTeam1 = boardsTeam1[2:]

	t.Log("boardsTeam1: ", extractIDs(t, boardsTeam1))
	t.Log("boardsTeam2: ", extractIDs(t, boardsTeam2))
	t.Log("boardsAdded: ", extractIDs(t, boardsAdded))
	t.Log("boardsDeleted: ", extractIDs(t, boardsDeleted))

	t.Run("Invalid teamID", func(t *testing.T) {
		opts := model.QueryBoardsComplianceHistoryOptions{
			TeamID: utils.NewID(utils.IDTypeTeam),
		}

		boardHistories, hasMore, err := store.GetBoardsComplianceHistory(opts)

		assert.Empty(t, boardHistories)
		assert.False(t, hasMore)
		assert.NoError(t, err)
	})

	t.Run("All teams, include deleted", func(t *testing.T) {
		opts := model.QueryBoardsComplianceHistoryOptions{
			IncludeDeleted: true,
		}

		boardHistories, hasMore, err := store.GetBoardsComplianceHistory(opts)

		// boardHistories should contain a record for each board added, plus a record for the 2 deleted.
		assert.ElementsMatch(t, extractIDs(t, boardHistories), extractIDs(t, boardsAdded, boardsDeleted))
		assert.False(t, hasMore)
		assert.NoError(t, err)
	})

	t.Run("All teams, exclude deleted", func(t *testing.T) {
		opts := model.QueryBoardsComplianceHistoryOptions{
			IncludeDeleted: false,
		}

		boardHistories, hasMore, err := store.GetBoardsComplianceHistory(opts)

		// boardHistories should contain a record for each board added, minus the two deleted.
		assert.ElementsMatch(t, extractIDs(t, boardHistories), extractIDs(t, boardsTeam1, boardsTeam2))
		assert.False(t, hasMore)
		assert.NoError(t, err)
	})

	t.Run("Specific team", func(t *testing.T) {
		opts := model.QueryBoardsComplianceHistoryOptions{
			TeamID: team1,
		}

		boardHistories, hasMore, err := store.GetBoardsComplianceHistory(opts)

		assert.ElementsMatch(t, extractIDs(t, boardHistories), extractIDs(t, boardsTeam1))
		assert.False(t, hasMore)
		assert.NoError(t, err)
	})

	t.Run("Pagination", func(t *testing.T) {
		opts := model.QueryBoardsComplianceHistoryOptions{
			Page:    0,
			PerPage: 3,
		}

		reps := 0
		allHistories := make([]*model.BoardHistory, 0)

		for {
			reps++
			boardHistories, hasMore, err := store.GetBoardsComplianceHistory(opts)
			require.NoError(t, err)
			require.NotEmpty(t, boardHistories)
			allHistories = append(allHistories, boardHistories...)

			if !hasMore {
				break
			}
			opts.Page++
		}

		assert.ElementsMatch(t, extractIDs(t, allHistories), extractIDs(t, boardsTeam1, boardsTeam2))
		expectedCount := len(boardsTeam1) + len(boardsTeam2)
		assert.Equal(t, math.Floor(float64(expectedCount/opts.PerPage)+1), float64(reps))
	})
}

func testGetBlocksComplianceHistory(t *testing.T, store store.Store) {
	team1 := testTeamID
	team2 := utils.NewID(utils.IDTypeTeam)

	boardsTeam1 := createTestBoards(t, store, team1, testUserID, 3)
	boardsTeam2 := createTestBoards(t, store, team2, testUserID, 1)

	// add cards (13 in total)
	cards1Team1 := createTestCards(t, store, testUserID, boardsTeam1[0].ID, 3)
	cards2Team1 := createTestCards(t, store, testUserID, boardsTeam1[1].ID, 5)
	cards3Team1 := createTestCards(t, store, testUserID, boardsTeam1[2].ID, 2)
	cards1Team2 := createTestCards(t, store, testUserID, boardsTeam2[0].ID, 3)

	deleteTestBoard(t, store, boardsTeam1[0].ID, testUserID)
	cardsDeleted := cards1Team1

	t.Run("Invalid teamID", func(t *testing.T) {
		opts := model.QueryBlocksComplianceHistoryOptions{
			TeamID: utils.NewID(utils.IDTypeTeam),
		}

		boards, hasMore, err := store.GetBlocksComplianceHistory(opts)

		assert.Empty(t, boards)
		assert.False(t, hasMore)
		assert.NoError(t, err)
	})

	t.Run("All teams, include deleted", func(t *testing.T) {
		opts := model.QueryBlocksComplianceHistoryOptions{
			IncludeDeleted: true,
		}

		blockHistories, hasMore, err := store.GetBlocksComplianceHistory(opts)

		// blockHistories should have records for all cards added, plus all cards deleted
		assert.ElementsMatch(t, extractIDs(t, blockHistories, nil),
			extractIDs(t, cards1Team1, cards2Team1, cards3Team1, cards1Team2, cardsDeleted))
		assert.False(t, hasMore)
		assert.NoError(t, err)
	})

	t.Run("All teams, exclude deleted", func(t *testing.T) {
		opts := model.QueryBlocksComplianceHistoryOptions{}

		blockHistories, hasMore, err := store.GetBlocksComplianceHistory(opts)

		// blockHistories should have records for all cards added that have not been deleted
		assert.ElementsMatch(t, extractIDs(t, blockHistories, nil),
			extractIDs(t, cards2Team1, cards3Team1, cards1Team2))
		assert.False(t, hasMore)
		assert.NoError(t, err)
	})

	t.Run("Specific team", func(t *testing.T) {
		opts := model.QueryBlocksComplianceHistoryOptions{
			TeamID: team1,
		}

		blockHistories, hasMore, err := store.GetBlocksComplianceHistory(opts)

		assert.ElementsMatch(t, extractIDs(t, blockHistories), extractIDs(t, cards2Team1, cards3Team1))
		assert.False(t, hasMore)
		assert.NoError(t, err)
	})

	t.Run("Pagination", func(t *testing.T) {
		opts := model.QueryBlocksComplianceHistoryOptions{
			Page:    0,
			PerPage: 3,
		}

		reps := 0
		allHistories := make([]*model.BlockHistory, 0)

		for {
			reps++
			blockHistories, hasMore, err := store.GetBlocksComplianceHistory(opts)
			require.NoError(t, err)
			require.NotEmpty(t, blockHistories)
			allHistories = append(allHistories, blockHistories...)

			if !hasMore {
				break
			}
			opts.Page++
		}

		assert.ElementsMatch(t, extractIDs(t, allHistories), extractIDs(t, cards2Team1, cards3Team1, cards1Team2))

		expectedCount := len(cards2Team1) + len(cards3Team1) + len(cards1Team2)
		assert.Equal(t, math.Floor(float64(expectedCount/opts.PerPage)+1), float64(reps))
	})
}
