import { Request, Response } from "express";
import { Secret } from "../../models";
import Folder from "../../models/folder";
import { BadRequestError } from "../../utils/errors";
import {
  appendFolder,
  deleteFolderById,
  getAllFolderIds,
  searchByFolderIdWithDir,
  searchByFolderId,
  validateFolderName,
  generateFolderId,
  getParentFromFolderId,
} from "../../services/FolderService";
import { ADMIN, MEMBER } from "../../variables";
import { validateMembership } from "../../helpers/membership";
import { FolderVersion } from "../../ee/models";
import { EESecretService } from "../../ee/services";

// TODO
// verify workspace id/environment
export const createFolder = async (req: Request, res: Response) => {
  const { workspaceId, environment, folderName, parentFolderId } = req.body;
  if (!validateFolderName(folderName)) {
    throw BadRequestError({
      message: "Folder name cannot contain spaces. Only underscore and dashes",
    });
  }

  const folders = await Folder.findOne({
    workspace: workspaceId,
    environment,
  }).lean();
  // space has no folders initialized
  if (!folders) {
    const id = generateFolderId();
    const folder = new Folder({
      workspace: workspaceId,
      environment,
      nodes: {
        id: "root",
        name: "root",
        version: 1,
        children: [{ id, name: folderName, children: [], version: 1 }],
      },
    });
    await folder.save();
    const folderVersion = new FolderVersion({
      workspace: workspaceId,
      environment,
      nodes: folder.nodes,
    });
    await folderVersion.save();
    await EESecretService.takeSecretSnapshot({
      workspaceId,
      environment,
    });
    return res.json({ folder: { id, name: folderName } });
  }

  const folder = appendFolder(folders.nodes, { folderName, parentFolderId });
  await Folder.findByIdAndUpdate(folders._id, folders);

  const parentFolder = searchByFolderId(folders.nodes, parentFolderId);
  const folderVersion = new FolderVersion({
    workspace: workspaceId,
    environment,
    nodes: parentFolder,
  });
  await folderVersion.save();

  await EESecretService.takeSecretSnapshot({
    workspaceId,
    environment,
    folderId: parentFolderId,
  });

  return res.json({ folder });
};

export const updateFolderById = async (req: Request, res: Response) => {
  const { folderId } = req.params;
  const { name, workspaceId, environment } = req.body;

  const folders = await Folder.findOne({ workspace: workspaceId, environment });
  if (!folders) {
    throw BadRequestError({ message: "The folder doesn't exist" });
  }

  // check that user is a member of the workspace
  await validateMembership({
    userId: req.user._id.toString(),
    workspaceId,
    acceptedRoles: [ADMIN, MEMBER],
  });

  const parentFolder = getParentFromFolderId(folders.nodes, folderId);
  if (!parentFolder) {
    throw BadRequestError({ message: "The folder doesn't exist" });
  }
  const folder = parentFolder.children.find(({ id }) => id === folderId);
  if (!folder) {
    throw BadRequestError({ message: "The folder doesn't exist" });
  }

  parentFolder.version += 1;
  folder.name = name;

  await Folder.findByIdAndUpdate(folders._id, folders);
  const folderVersion = new FolderVersion({
    workspace: workspaceId,
    environment,
    nodes: parentFolder,
  });
  await folderVersion.save();

  await EESecretService.takeSecretSnapshot({
    workspaceId,
    environment,
    folderId: parentFolder.id,
  });

  return res.json({
    message: "Successfully updated folder",
    folder: { name: folder.name, id: folder.id },
  });
};

export const deleteFolder = async (req: Request, res: Response) => {
  const { folderId } = req.params;
  const { workspaceId, environment } = req.body;

  const folders = await Folder.findOne({ workspace: workspaceId, environment });
  if (!folders) {
    throw BadRequestError({ message: "The folder doesn't exist" });
  }

  // check that user is a member of the workspace
  await validateMembership({
    userId: req.user._id.toString(),
    workspaceId,
    acceptedRoles: [ADMIN, MEMBER],
  });

  const delOp = deleteFolderById(folders.nodes, folderId);
  if (!delOp) {
    throw BadRequestError({ message: "The folder doesn't exist" });
  }
  const { deletedNode: delFolder, parent: parentFolder } = delOp;

  parentFolder.version += 1;
  const delFolderIds = getAllFolderIds(delFolder);

  await Folder.findByIdAndUpdate(folders._id, folders);
  const folderVersion = new FolderVersion({
    workspace: workspaceId,
    environment,
    nodes: parentFolder,
  });
  await folderVersion.save();
  if (delFolderIds.length) {
    await Secret.deleteMany({
      folder: { $in: delFolderIds.map(({ id }) => id) },
      workspace: workspaceId,
      environment,
    });
  }

  await EESecretService.takeSecretSnapshot({
    workspaceId,
    environment,
    folderId: parentFolder.id,
  });

  res.send({ message: "successfully deleted folders", folders: delFolderIds });
};

// TODO: validate workspace
export const getFolders = async (req: Request, res: Response) => {
  const { workspaceId, environment, parentFolderId } = req.query as {
    workspaceId: string;
    environment: string;
    parentFolderId?: string;
  };

  const folders = await Folder.findOne({ workspace: workspaceId, environment });
  if (!folders) {
    res.send({ folders: [], dir: [] });
    return;
  }

  // check that user is a member of the workspace
  await validateMembership({
    userId: req.user._id.toString(),
    workspaceId,
    acceptedRoles: [ADMIN, MEMBER],
  });

  if (!parentFolderId) {
    const rootFolders = folders.nodes.children.map(({ id, name }) => ({
      id,
      name,
    }));
    res.send({ folders: rootFolders });
    return;
  }

  const folderBySearch = searchByFolderIdWithDir(folders.nodes, parentFolderId);
  if (!folderBySearch) {
    throw BadRequestError({ message: "The folder doesn't exist" });
  }
  const { folder, dir } = folderBySearch;

  res.send({
    folders: folder.children.map(({ id, name }) => ({ id, name })),
    dir,
  });
};
