const { Transform } = require('stream')
const crypto = require('crypto')
const fs = require('fs')


class AppendInitVect extends Transform {
    constructor(initVect, opts) {
        super(opts)
        this.initVect = initVect
        this.appended = false
    }

    _transform(chunk, _encoding, cb) {
        if (!this.appended) {
            this.push(this.initVect)
            this.appended = true
        }

        this.push(chunk)
        cb()
    }
}

async function encryptFilePipeBinary({ filePathPlainText, filePathEncrypted, password }) {
    return new Promise((resolve) => {
        const initVect = crypto.randomBytes(16)
        const cipherKey = crypto.createHash('sha256').update(password).digest()
        const readStream = fs.createReadStream(filePathPlainText)
        const cipher = crypto.createCipheriv('aes256', cipherKey, initVect)
        const appendInitVect = new AppendInitVect(initVect)

        const writeStream = fs.createWriteStream(filePathEncrypted)

        readStream
            .pipe(cipher)
            .pipe(appendInitVect)
            .pipe(writeStream)
            .on('close', resolve)

    })


}

async function decryptFilePipeBinary({ filePathEncrypted, filePathPlainText, password }) {
    return new Promise((resolve, _reject) => {
        const readInitVect = fs.createReadStream(filePathEncrypted, { end: 15 })

        let initVect
        readInitVect.on('data', chunk => { initVect = chunk })

        readInitVect.on('close', () => {
            const cipherKey = crypto.createHash('sha256').update(password).digest()
            const readStream = fs.createReadStream(filePathEncrypted, { start: 16 })
            const decipher = crypto.createDecipheriv('aes256', cipherKey, initVect)
            const writeStream = fs.createWriteStream(filePathPlainText)

            readStream
                .pipe(decipher)
                .pipe(writeStream)
                .on('close', resolve)


        })
    })

}

async function encryptFileAES({ filePathPlainText, filePathEncrypted, password }) {
    return new Promise(async (resolve) => {
        const data = fs.readFileSync(filePathPlainText)
        const dataEnc = await encryptDataAES({
            data,
            password
        })

        fs.writeFileSync(filePathEncrypted,dataEnc,{encoding: 'utf8'})

        resolve()
     })
}

async function decryptFileAES({ filePathEncrypted, filePathDecrypted, password }) {
    return new Promise(async (resolve, reject) => { 
        
        try {
            const dataEnc = fs.readFileSync(filePathEncrypted,{encoding:'utf8'})
            const dataDenc = await decryptDataAES({
                dataEnc,
                password
            })

            fs.writeFileSync(filePathDecrypted,dataDenc,{encoding: 'utf8'})

            resolve()
        } catch (error) {
            reject(error)
        }
    })
}



async function encryptDataAES({ data, password }) {
    return new Promise((resolve) => {
        const iv = crypto.randomBytes(16)

        const passwordFixed = crypto.createHash('sha256').update(password).digest()
        const cipher = crypto.createCipheriv('aes256', passwordFixed, iv)

        let dataEnc = cipher.update(
            Buffer.from(data, 'utf8'),
            'utf8',
            'hex')
        dataEnc += cipher.final('hex')

        resolve(iv.toString('hex') + ".IV." + dataEnc)
    })
}

async function decryptDataAES({ dataEnc, password }) {
    return new Promise((resolve,reject) => {
        const iv = Buffer.from(
            dataEnc.split(".IV.")[0],
            'hex'
        )
        try {

            const onlyDataEnc = dataEnc.split(".IV.")[1]
            const passwordFixed = crypto.createHash('sha256').update(password).digest()
            const decipher = crypto.createDecipheriv('aes256', passwordFixed, iv)
            let dataClean = decipher.update(onlyDataEnc, 'hex', 'utf8')
        
            dataClean += decipher.final('utf8')

            resolve(dataClean)
        } catch (error) {
            reject(error)
        }

        

    })
}

async function encryptKeyRSA({ key, publicKey }) {
    return new Promise((resolve) => {
        const keyEnc = crypto.publicEncrypt({
            key: publicKey,
            padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
            oaepHash: "sha256"
        }, key)

        resolve(keyEnc)
    })
}

async function decryptKeyRSA({ keyEnc, privateKey, privateKeyPassword }) {
    return new Promise((resolve) => {
        const keyDenc = crypto.privateDecrypt({
            key: privateKey,
            passphrase: privateKeyPassword,
            padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
            oaepHash: "sha256"
        }, keyEnc)

        resolve(keyDenc)
    })
}

async function encryptDataRSA_AES({ data, publicKey }) {
    return new Promise(async (resolve) => {
        const key = crypto.randomBytes(32)

        const keyEnc = (await encryptKeyRSA({
            key, publicKey
        })).toString('hex')

        const dataEnc = await encryptDataAES({
            data: data,
            password: key.toString('hex')
        })

        resolve(keyEnc + ".MU." + dataEnc)
    })
}

async function decryptDataRSA_AES({ data, privateKey, privateKeyPassword }) {
    return new Promise(async (resolve) => {
        debugger
        const keyEnc = data.split(".MU.")[0]
        const dataEnc = data.split(".MU.")[1]

        const keyDenc = await decryptKeyRSA({
            keyEnc: Buffer.from(keyEnc, 'hex'),
            privateKey,
            privateKeyPassword
        })

        const dataDenc = await decryptDataAES({
            dataEnc,
            password: keyDenc.toString('hex')
        })

        resolve(dataDenc)
    })
}

async function generateKeyPair({ privateKeyPassword }) {
    return new Promise((resolve) => {
        const { publicKey, privateKey } = crypto.generateKeyPairSync("rsa", {
            modulusLength: 2048,
            publicKeyEncoding: {
                type: "spki",
                format: "pem"
            },
            privateKeyEncoding: {
                type: "pkcs8",
                format: "pem",
                cipher: "aes-256-cbc",
                passphrase: privateKeyPassword
            }
        })

        resolve({ publicKey, privateKey })
    })


}

async function encryptFileRSA_AES({ filePathPlainText, filePathEncrypted, publicKey }) {
    return new Promise(async (resolve) => {
        const data = fs.readFileSync(filePathPlainText, { encoding: 'utf8' })
        const dataEnc = await encryptDataRSA_AES({ data, publicKey })

        fs.writeFileSync(filePathEncrypted, dataEnc, { encoding: 'utf8' })

        resolve()
    })
}

async function decryptFileRSA_AES({ filePathEncrypted, filePathDecrypted, privateKey, privateKeyPassword }) {
    return new Promise(async (resolve) => {
        debugger
        const dataEnc = fs.readFileSync(filePathEncrypted, { encoding: 'utf8' })
        const dataDenc = await decryptDataRSA_AES({ data: dataEnc, privateKey, privateKeyPassword })

        fs.writeFileSync(filePathDecrypted, dataDenc, { encoding: 'utf8' })

        resolve()
    })
}

module.exports = {
    encryptFileAES,
    decryptFileAES,
    encryptDataAES,
    decryptDataAES,
    generateKeyPair,
    encryptKeyRSA,
    decryptKeyRSA,
    encryptFileRSA_AES,
    decryptFileRSA_AES,
    encryptDataRSA_AES,
    decryptDataRSA_AES
}

