/* ----------------------------------------------------------------------------

 Question Server
 GitLab: <https://gitlab.com/MrFry/mrfrys-node-server>

 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program. If not, see <https://www.gnu.org/licenses/>.

 ------------------------------------------------------------------------- */

export default {
    ReadFile: ReadFile,
    ReadJSON: ReadJSON,
    WriteFile: WriteFile,
    writeFileAsync: writeFileAsync,
    AppendToFile: AppendToFile,
    FileExists: FileExists,
    CreatePath: CreatePath,
    WatchFile: WatchFile,
    ReadDir: ReadDir,
    CopyFile: CopyFile,
    GetDateString: GetDateString,
    formatUrl: formatUrl,
    deleteFile: deleteFile,
    uploadFile: uploadFile,
    statFile: statFile,
    renameFile: renameFile,
    deleteDir: deleteDir,
    formatBytes: formatBytes,
    getGitRevision: getGitRevision,
    getScriptVersion: getScriptVersion,
}

import * as child_process from 'child_process'
import fs from 'fs'
import { v4 as uuidv4 } from 'uuid'
import logger from '../utils/logger'

import { paths } from './files'
import { Request } from '../types/basicTypes'

interface URLFormatOptions {
    pathname?: string
    query?: { [key: string]: string }
}

function formatUrl(options: URLFormatOptions): string {
    const path = options.pathname || ''
    if (!options.query || Object.keys(options.query).length === 0) {
        return path
    }
    const queryString =
        '?' +
        Object.keys(options.query)
            .map((key) => {
                return `${key}=${encodeURIComponent(options.query[key])}`
            })
            .join('&')
    return path + queryString
}

function GetDateString(
    referenceDate?: Date | string,
    noTime?: boolean
): string {
    const date = referenceDate ? new Date(referenceDate) : new Date()

    if (noTime) {
        return (
            date.getFullYear() +
            '-' +
            ('0' + (date.getMonth() + 1)).slice(-2) +
            '-' +
            ('0' + date.getDate()).slice(-2)
        )
    } else {
        return (
            date.getFullYear() +
            '-' +
            ('0' + (date.getMonth() + 1)).slice(-2) +
            '-' +
            ('0' + date.getDate()).slice(-2) +
            ' ' +
            ('0' + date.getHours()).slice(-2) +
            ':' +
            ('0' + date.getMinutes()).slice(-2) +
            ':' +
            ('0' + date.getSeconds()).slice(-2)
        )
    }
}

function CopyFile(from: string, to: string): void {
    CreatePath(to)
    fs.copyFileSync(from, to)
}

function ReadDir(path: string, listHidden?: boolean): Array<string> {
    if (listHidden) {
        return fs.readdirSync(path)
    } else {
        return fs.readdirSync(path).filter((file) => {
            return !file.startsWith('.')
        })
    }
}

function ReadJSON<T = any>(name: string): T {
    try {
        return JSON.parse(ReadFile(name))
    } catch (err) {
        console.error(err)
        throw new Error('Coulndt parse JSON in "ReadJSON", file: ' + name)
    }
}

function ReadFile(name: string): string {
    if (!FileExists(name)) {
        throw new Error('No such file: ' + name)
    }
    return fs.readFileSync(name, 'utf8')
}

function FileExists(path: string): boolean {
    return fs.existsSync(path)
}

function WatchFile(file: string, callback: Function): void {
    if (FileExists(file)) {
        fs.watchFile(file, () => {
            fs.readFile(file, 'utf8', (err, data) => {
                if (err) {
                    // console.log(err)
                } else {
                    callback(data)
                }
            })
        })
    } else {
        throw new Error(`${file} does not exits to watch`)
    }
}

function CreatePath(path: string, onlyPath?: boolean): void {
    if (FileExists(path)) {
        return
    }

    const spath = path.split('/')
    let currDir = spath[0]
    for (let i = 1; i < spath.length; i++) {
        if (currDir !== '' && !fs.existsSync(currDir)) {
            try {
                fs.mkdirSync(currDir)
            } catch (err) {
                console.log('Failed to make ' + currDir + ' directory... ')
                console.error(err)
            }
        }
        currDir += '/' + spath[i]
    }
    if (onlyPath) {
        fs.mkdirSync(path)
    }
}

function WriteFile(content: string, path: string): void {
    CreatePath(path)
    fs.writeFileSync(path, content)
}

function writeFileAsync(content: string, path: string): void {
    CreatePath(path)
    fs.writeFile(path, content, function (err) {
        if (err) {
            logger.Log(
                'Error writing file: ' + path + ' (sync)',
                logger.GetColor('redbg')
            )
        }
    })
}

function AppendToFile(data: string, file: string): void {
    CreatePath(file)
    try {
        fs.appendFileSync(file, '\n' + data)
    } catch (err) {
        logger.Log(
            'Error appendig to file log file: ' + file + ' (sync)',
            logger.GetColor('redbg')
        )
        logger.Log(data)
        console.error(err)
    }
}

function deleteFile(fname: string): Boolean {
    if (FileExists(fname)) {
        fs.unlinkSync(fname)
        return true
    }
    return false
}

function deleteDir(dirName: string): Boolean {
    if (FileExists(dirName)) {
        fs.rmSync(dirName, { recursive: true })
        return true
    }
    return false
}

function uploadFile(
    req: Request,
    path: string
): Promise<{
    body: Request['body']
    fileName: string
    filePath: string
}> {
    return new Promise((resolve, reject) => {
        try {
            if (!req.files) {
                logger.Log(
                    `Unable to upload file, req.files is undefined`,
                    logger.GetColor('redbg')
                )
                return
            }
            const file = req.files.file
            // FIXME: Object.keys(req.files).forEach((file) => { saveFile() })

            CreatePath(path, true)

            let fileName = file.name.replace(/\.+/g, '.').replace(/\/+/g, '/')
            let fileDestination = path + '/' + fileName
            if (FileExists(fileDestination)) {
                const id = uuidv4().split('-')[0]

                const temp = file.name.split('.')
                const extension = temp.pop()
                fileName = temp.join('.') + '_' + id + '.' + extension
                fileDestination = path + '/' + fileName
            }

            file.mv(fileDestination, (err: Error) => {
                if (err) {
                    logger.Log(
                        `Unable to upload file!`,
                        logger.GetColor('redbg')
                    )
                    console.error(err)
                    reject(err)
                } else {
                    logger.Log(
                        `Uploaded: ${fileName} to ${fileDestination}`,
                        logger.GetColor('blue')
                    )
                    resolve({
                        body: req.body,
                        fileName: fileName,
                        filePath: fileDestination,
                    })
                }
            })
        } catch (err) {
            logger.Log(
                `Unable to upload file, error on stderr`,
                logger.GetColor('redbg')
            )
            console.error(err)
            reject(err)
        }
    })
}

function statFile(file: string): fs.Stats {
    if (FileExists(file)) {
        return fs.statSync(file)
    } else {
        return null
    }
}

function renameFile(oldPath: string, newPath: string): string {
    if (FileExists(oldPath)) {
        fs.renameSync(oldPath, newPath)
        return newPath
    } else {
        return null
    }
}

function formatBytes(number: number, unit: 'MB' | 'GB' = 'MB'): string {
    let res = number / 1024 / 1024 // MB
    if (unit === 'MB') {
        return `${res} MB`
    }
    res = res / 1024
    if (unit === 'GB') {
        return `${res} GB`
    }
    return `${number} byte`
}

function getGitRevision(dir: string): string {
    try {
        return child_process
            .execSync('git rev-parse HEAD', {
                cwd: dir,
                stdio: [0, 'pipe', null],
            })
            .toString()
            .trim()
    } catch (e) {
        return 'Failed to get revision'
    }
}

function getScriptVersion(): string {
    const scriptContent = ReadFile(paths.moodleTestUserscriptPath)

    let temp: string | string[] = scriptContent.split('\n').find((x) => {
        return x.includes('@version')
    })
    temp = temp.split(' ')
    temp = temp[temp.length - 1]

    return temp
}
