import { IResolvers } from 'apollo-server-express'
import fs from 'fs'
import randombytes from 'randombytes'
import sharp from 'sharp'
import ffmpeg from 'fluent-ffmpeg'
import { tmpDir, uploadDir } from './constants'
import { checkPermission } from '../user/resolvers'

export const resolvers: IResolvers = {
  Query: {
    fsFiles: async (parent, { directory }, context, info) => {
      checkPermission(context, 'ADMIN')
      const data = await fsFiles(directory)
      return data
    },
    files: (parent, args, context, info) => {
      checkPermission(context, 'ADMIN')
      return context.db.query.files(args, info)
    },
    videos: (parent, args, context, info) => {
      checkPermission(context)
      return context.db.query.videos(args, info)
    },
    video: (parent, args, context, info) => {
      checkPermission(context, 'ADMIN')
      return context.db.query.video(args, info)
    },
  },
  Mutation: {
    uploadFile: async (parent, { comment, file }, context, info) => {
      checkPermission(context, 'ADMIN')
      const fileInfo = await uploadFile(file)

      return context.db.mutation.createFile(
        {
          data: {
            ...fileInfo,
            comment,
            user: { connect: { id: context.req.userId } },
          },
        },
        info
      )
    },
    createVideo: (parent, args, context, info) => {
      checkPermission(context, 'ADMIN')
      return context.db.mutation.createVideo(args, info)
    },
    updateVideo: (parent, args, context, info) => {
      checkPermission(context, 'ADMIN')
      return context.db.mutation.updateVideo(args, info)
    },
  },
}

async function fsFiles(directory: string) {
  const fileList = await fs.promises.readdir(directory)
  return Promise.all(
    fileList.map(async (filename) => {
      const path = `${directory}/${filename}`
      const { size, ctime, mtime } = await fs.promises.stat(path)
      return {
        filename,
        path,
        size,
        ctime,
        mtime,
      }
    })
  )
}

async function uploadFile(file: any) {
  const { createReadStream, filename, mimetype, encoding } = await file
  const stream = createReadStream()

  const fsFilename = randombytes(16).toString('hex')
  const tmpPath = `${tmpDir}/${fsFilename}`
  const path = `${uploadDir}/${fsFilename}`
  let thumbnail = null
  const thumbnailFile = `thmb${fsFilename}`
  const thumbnailPath = `${uploadDir}/${thumbnailFile}`
  await new Promise((resolve, reject) => {
    const file = fs.createWriteStream(tmpPath)
    file.on('finish', resolve)
    file.on('error', (error) => {
      fs.unlink(tmpPath, () => {
        reject(error)
      })
    })
    stream.on('error', (error: any) => file.destroy(error))
    stream.pipe(file)
  })

  if (mimetype.startsWith('image/')) {
    console.log('image')
    try {
      await processImage(tmpPath, path)
      await createThumbnail(tmpPath, thumbnailPath)
      await fs.promises.unlink(tmpPath)
      thumbnail = thumbnailPath
    } catch (error) {
      try {
        await fs.promises.unlink(tmpPath)
        await fs.promises.unlink(path)
      } catch (ignore) {}
      throw error
    }
  } else if (mimetype.startsWith('video/')) {
    console.log('video')
    try {
      ffmpeg(path)
        .screenshot({
          timestamps: ['25%'],
          filename: thumbnailFile,
          folder: uploadDir,
          size: '200x?',
        })
        .on('end', () => fs.promises.rename(`${thumbnailPath}.png`, thumbnailPath))
      await fs.promises.rename(tmpPath, path)
      thumbnail = thumbnailPath
    } catch (error) {
      try {
        await fs.promises.unlink(tmpPath)
        await fs.promises.unlink(path)
      } catch (ignore) {}
      throw error
    }
  } else {
    console.log('no image')
    try {
      await fs.promises.rename(tmpPath, path)
    } catch (error) {
      try {
        await fs.promises.unlink(tmpPath)
        await fs.promises.unlink(path)
      } catch (ignore) {}
      throw error
    }
  }

  const { size } = await fs.promises.stat(path)

  return {
    path,
    mimetype,
    thumbnail,
    filename,
    encoding,
    size,
  }
}

function processImage(tmpFile: string, outputFile: string) {
  return sharp(tmpFile)
    .resize(1600, 1600, {
      fit: sharp.fit.inside,
      withoutEnlargement: true,
    })
    .jpeg()
    .toFile(outputFile)
}

function createThumbnail(tmpFile: string, thumbnail: string) {
  return sharp(tmpFile)
    .resize(200, 200, {
      fit: sharp.fit.inside,
    })
    .jpeg()
    .toFile(thumbnail)
}
