import path from "path"
import fs from "fs"
import { videoTranscode } from "../../../lib/videoTranscode"
import { nanoid } from "nanoid"
import Jimp from "jimp"

import pmap from "../../../utils/pMap"

const formidable = require("formidable")

const maximuns = {
    imageResolution: {
        width: 3840,
        height: 2160,
    },
    imageQuality: 80,
}

const handleUploadVideo = async (file, params) => {
    file.filepath = await videoTranscode(file.filepath, global.uploadCachePath)
    file.newFilename = path.basename(file.filepath)

    return file
}

const handleImage = async (file, params) => {
    const { width, height } = await new Promise((resolve, reject) => {
        Jimp.read(file.filepath)
            .then((image) => {
                resolve({
                    width: image.bitmap.width,
                    height: image.bitmap.height,
                })
            })
            .catch((err) => {
                reject(err)
            })
    })

    if (width > maximuns.imageResolution.width || height > maximuns.imageResolution.height) {
        await new Promise((resolve, reject) => {
            Jimp.read(file.filepath)
                .then((image) => {
                    image
                        .resize(maximuns.imageResolution.width, maximuns.imageResolution.height)
                        .quality(maximuns.imageQuality)
                        .write(file.filepath, resolve)
                })
                .catch((err) => {
                    reject(err)
                })
        })
    }

    file.newFilename = path.basename(file.filepath)

    return file
}

export default async (payload) => {
    if (!payload) {
        throw new Error("Missing payload")
    }

    const { req } = payload

    let params = {
        cacheUploadDir: global.uploadCachePath,
        maxFileSize: global.DEFAULT_POSTING_POLICY.maximumFileSize,
        maxFields: global.DEFAULT_POSTING_POLICY.maximunFilesPerRequest,
        acceptedMimeTypes: global.DEFAULT_POSTING_POLICY.acceptedMimeTypes,
    }

    if (payload.params) {
        params = {
            ...params,
            ...payload.params,
        }
    }

    let failed = []

    // check directories exist
    if (!fs.existsSync(params.cacheUploadDir)) {
        await fs.promises.mkdir(params.cacheUploadDir, { recursive: true })
    }

    // decode body form-data
    const form = formidable({
        multiples: true,
        keepExtensions: true,
        uploadDir: params.cacheUploadDir,
        maxFileSize: params.maxFileSize,
        maxFields: params.maxFields,
        filename: (name, ext) => {
            name = nanoid()

            return name + ext
        },
        filter: (stream) => {
            // check if is allowed mime type
            if (!params.acceptedMimeTypes.includes(stream.mimetype)) {
                failed.push({
                    fileName: file.originalFilename,
                    mimetype: file.mimetype,
                    error: "mimetype not allowed",
                })

                return false
            }

            return true
        }
    })

    const results = await new Promise((resolve, reject) => {
        const processedFiles = []
        const failedFiles = []

        let queuePromieses = []

        // create a new thread for each file
        form.parse(req, async (err, fields, data) => {
            if (err) {
                return reject(err)
            }

            if (!Array.isArray(data.files)) {
                data.files = [data.files]
            }

            for (let file of data.files) {
                if (!file) continue

                // create process queue
                queuePromieses.push(async () => {
                    // check if is video need to transcode
                    switch (file.mimetype) {
                        case "video/quicktime": {
                            file = await handleUploadVideo(file, params)
                            break
                        }
                        case "image/jpeg": {
                            file = await handleImage(file, params)
                            break
                        }
                        case "image/png": {
                            file = await handleImage(file, params)
                            break
                        }
                        case "image/gif": {
                            file = await handleImage(file, params)
                            break
                        }
                        case "image/bmp": {
                            file = await handleImage(file, params)
                            break
                        }
                        case "image/tiff": {
                            file = await handleImage(file, params)
                            break
                        }
                        default: {
                            // do nothing
                        }
                    }

                    const metadata = {
                        mimetype: file.mimetype,
                        size: file.size,
                        filepath: file.filepath,
                        filename: file.newFilename,
                    }

                    // upload to s3
                    await new Promise((_resolve, _reject) => {
                        global.storage.fPutObject(global.storage.defaultBucket, file.newFilename, file.filepath, metadata, (err, etag) => {
                            if (err) {
                                return _reject(new Error(`Failed to upload file to storage server > ${err.message}`))
                            }

                            return _resolve()
                        })
                    }).catch((err) => {
                        return reject(err)
                    })

                    // remove file from cache
                    await fs.promises.unlink(file.filepath)

                    // get url location
                    const remoteUrlObj = global.storage.composeRemoteURL(file.newFilename)

                    // push final filepath to urls
                    return {
                        name: file.originalFilename,
                        id: file.newFilename,
                        url: remoteUrlObj,
                    }
                })
            }

            // wait for all files to be processed
            await pmap(
                queuePromieses,
                async (fn) => {
                    const result = await fn().catch((err) => {
                        console.error(err)
                        failedFiles.push(err)

                        return null
                    })

                    if (result) {
                        processedFiles.push(result)
                    }
                },
                { concurrency: 5 }
            )

            return resolve({
                files: processedFiles,
                failed: failedFiles,
            })
        })
    })

    return results
}