package app

import (
	"github.com/mattermost/focalboard/server/model"
	"github.com/mattermost/focalboard/server/services/notify"

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

func (a *App) CreateBoardsAndBlocks(bab *model.BoardsAndBlocks, userID string, addMember bool) (*model.BoardsAndBlocks, error) {
	var newBab *model.BoardsAndBlocks
	var members []*model.BoardMember
	var err error

	if addMember {
		newBab, members, err = a.store.CreateBoardsAndBlocksWithAdmin(bab, userID)
	} else {
		newBab, err = a.store.CreateBoardsAndBlocks(bab, userID)
	}

	if err != nil {
		return nil, err
	}

	// all new boards should belong to the same team
	teamID := newBab.Boards[0].TeamID

	// This can be synchronous because this action is not common
	for _, board := range newBab.Boards {
		a.wsAdapter.BroadcastBoardChange(teamID, board)
	}

	for _, block := range newBab.Blocks {
		b := block
		a.wsAdapter.BroadcastBlockChange(teamID, b)
		a.metrics.IncrementBlocksInserted(1)
		a.webhook.NotifyUpdate(b)
		a.notifyBlockChanged(notify.Add, &b, nil, userID)
	}

	if addMember {
		for _, member := range members {
			a.wsAdapter.BroadcastMemberChange(teamID, member.BoardID, member)
		}
	}

	if len(newBab.Blocks) != 0 {
		go func() {
			if uErr := a.UpdateCardLimitTimestamp(); uErr != nil {
				a.logger.Error(
					"UpdateCardLimitTimestamp failed after creating boards and blocks",
					mlog.Err(uErr),
				)
			}
		}()
	}

	return newBab, nil
}

func (a *App) PatchBoardsAndBlocks(pbab *model.PatchBoardsAndBlocks, userID string) (*model.BoardsAndBlocks, error) {
	oldBlocks, err := a.store.GetBlocksByIDs(pbab.BlockIDs)
	if err != nil {
		return nil, err
	}

	if a.IsCloudLimited() {
		containsLimitedBlocks, cErr := a.ContainsLimitedBlocks(oldBlocks)
		if cErr != nil {
			return nil, cErr
		}
		if containsLimitedBlocks {
			return nil, ErrPatchUpdatesLimitedCards
		}
	}

	oldBlocksMap := map[string]model.Block{}
	for _, block := range oldBlocks {
		oldBlocksMap[block.ID] = block
	}

	bab, err := a.store.PatchBoardsAndBlocks(pbab, userID)
	if err != nil {
		return nil, err
	}

	a.blockChangeNotifier.Enqueue(func() error {
		teamID := bab.Boards[0].TeamID

		for _, block := range bab.Blocks {
			oldBlock, ok := oldBlocksMap[block.ID]
			if !ok {
				a.logger.Error("Error notifying for block change on patch boards and blocks; cannot get old block", mlog.String("blockID", block.ID))
				continue
			}

			b := block
			a.metrics.IncrementBlocksPatched(1)
			a.wsAdapter.BroadcastBlockChange(teamID, b)
			a.webhook.NotifyUpdate(b)
			a.notifyBlockChanged(notify.Update, &b, &oldBlock, userID)
		}

		for _, board := range bab.Boards {
			a.wsAdapter.BroadcastBoardChange(board.TeamID, board)
		}
		return nil
	})

	return bab, nil
}

func (a *App) DeleteBoardsAndBlocks(dbab *model.DeleteBoardsAndBlocks, userID string) error {
	firstBoard, err := a.store.GetBoard(dbab.Boards[0])
	if err != nil {
		return err
	}

	// we need the block entity to notify of the block changes, so we
	// fetch and store the blocks first
	blocks := []*model.Block{}
	for _, blockID := range dbab.Blocks {
		block, err := a.store.GetBlock(blockID)
		if err != nil {
			return err
		}
		blocks = append(blocks, block)
	}

	if err := a.store.DeleteBoardsAndBlocks(dbab, userID); err != nil {
		return err
	}

	a.blockChangeNotifier.Enqueue(func() error {
		for _, block := range blocks {
			a.wsAdapter.BroadcastBlockDelete(firstBoard.TeamID, block.ID, block.BoardID)
			a.metrics.IncrementBlocksDeleted(1)
			a.notifyBlockChanged(notify.Update, block, block, userID)
		}

		for _, boardID := range dbab.Boards {
			a.wsAdapter.BroadcastBoardDelete(firstBoard.TeamID, boardID)
		}
		return nil
	})

	if len(dbab.Blocks) != 0 {
		go func() {
			if uErr := a.UpdateCardLimitTimestamp(); uErr != nil {
				a.logger.Error(
					"UpdateCardLimitTimestamp failed after deleting boards and blocks",
					mlog.Err(uErr),
				)
			}
		}()
	}

	return nil
}
