import each from 'async/each';
import mime from 'mime-types';
import isBefore from 'date-fns/is_before';
import Project from '../models/project';
import { resolvePathToFile } from '../utils/filePath';
import { deleteObjectsFromS3, getObjectKey } from './aws.controller';

// Bug -> timestamps don't get created, but it seems like this will
// be fixed in mongoose soon
// https://github.com/Automattic/mongoose/issues/4049
export function createFile(req, res) {
  Project.findOneAndUpdate(
    {
      _id: req.params.project_id,
      user: req.user._id
    },
    {
      $push: {
        files: req.body
      }
    },
    {
      new: true
    }, (err, updatedProject) => {
      if (err || !updatedProject) {
        console.log(err);
        res.status(403).send({ success: false, message: 'Project does not exist, or user does not match owner.' });
        return;
      }
      const newFile = updatedProject.files[updatedProject.files.length - 1];
      updatedProject.files.id(req.body.parentId).children.push(newFile.id);
      updatedProject.save((innerErr, savedProject) => {
        if (innerErr) {
          console.log(innerErr);
          res.json({ success: false });
          return;
        }
        savedProject.populate({ path: 'user', select: 'username' }, (_, populatedProject) => {
          res.json({
            updatedFile: updatedProject.files[updatedProject.files.length - 1],
            project: populatedProject
          });
        });
      });
    }
  );
}

function getAllDescendantIds(files, nodeId) {
  const parentFile = files.find(file => file.id === nodeId);
  if (!parentFile) return [];
  return parentFile.children
    .reduce((acc, childId) => (
      [...acc, childId, ...getAllDescendantIds(files, childId)]
    ), []);
}

function deleteMany(files, ids) {
  const objectKeys = [];

  each(ids, (id, cb) => {
    if (files.id(id).url) {
      if (!process.env.S3_DATE
        || (process.env.S3_DATE && isBefore(new Date(process.env.S3_DATE), new Date(files.id(id).createdAt)))) {
        const objectKey = getObjectKey(files.id(id).url);
        objectKeys.push(objectKey);
      }
    }
    files.id(id).remove();
    cb();
  }, (err) => {
    deleteObjectsFromS3(objectKeys);
  });
}

function deleteChild(files, parentId, id) {
  return files.map((file) => {
    if (file.id === parentId) {
      file.children = file.children.filter(child => child !== id);
      return file;
    }
    return file;
  });
}

export function deleteFile(req, res) {
  Project.findById(req.params.project_id, (err, project) => {
    if (!project) {
      res.status(404).send({ success: false, message: 'Project does not exist.' });
    }
    if (!project.user.equals(req.user._id)) {
      res.status(403).send({ success: false, message: 'Session does not match owner of project.' });
      return;
    }

    // make sure file exists for project
    const fileToDelete = project.files.find(file => file.id === req.params.file_id);
    if (!fileToDelete) {
      res.status(404).send({ success: false, message: 'File does not exist in project.' });
      return;
    }

    const idsToDelete = getAllDescendantIds(project.files, req.params.file_id);
    deleteMany(project.files, [req.params.file_id, ...idsToDelete]);
    project.files = deleteChild(project.files, req.query.parentId, req.params.file_id);
    project.save((innerErr) => {
      res.json(project.files);
    });
  });
}

export function getFileContent(req, res) {
  Project.findById(req.params.project_id, (err, project) => {
    if (err || project === null) {
      res.status(404).send({ success: false, message: 'Project with that id does not exist.' });
      return;
    }
    const filePath = req.params[0];
    const resolvedFile = resolvePathToFile(filePath, project.files);
    if (!resolvedFile) {
      res.status(404).send({ success: false, message: 'File with that name and path does not exist.' });
      return;
    }
    const contentType = mime.lookup(resolvedFile.name) || 'application/octet-stream';
    res.set('Content-Type', contentType);
    res.send(resolvedFile.content);
  });
}