/* eslint-disable no-underscore-dangle */
import crypto from 'crypto';
import querystring from 'querystring';

import { sha256, uniqueRid, validateRid, createEncryptionToken, decrypt, encrypt } from "./tools"

import getServiceWidget from "utils/config/service-helpers";
import { httpProxy } from "utils/proxy/http";
import createLogger from "utils/logger";

const proxyName = "jdownloaderProxyHandler";
const logger = createLogger(proxyName);

async function getWidget(req) {
    const { group, service } = req.query;
    if (!group || !service) {
        logger.debug("Invalid or missing service '%s' or group '%s'", service, group);
        return null;
    }
    const widget = await getServiceWidget(group, service);
    if (!widget) {
        logger.debug("Invalid or missing widget for service '%s' in group '%s'", service, group);
        return null;
    }

    return widget;
}

async function login(loginSecret, deviceSecret, params) {
    const rid = uniqueRid();
    const path = `/my/connect?${querystring.stringify({ ...params, rid })}`;

    const signature = crypto
        .createHmac('sha256', loginSecret)
        .update(path)
        .digest('hex');
    const url = `${new URL(`https://api.jdownloader.org${path}&signature=${signature}`)}`

    const [status, contentType, data] = await httpProxy(url, {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
        },
    });

    if (status !== 200) {
        logger.error("HTTP %d communicating with jdownloader. Data: %s", status, data.toString());
        return [status, data];
    }

    try {
        const decryptedData = JSON.parse(decrypt(data.toString(), loginSecret))
        const sessionToken = decryptedData.sessiontoken;
        validateRid(decryptedData, rid);
        const serverEncryptionToken = createEncryptionToken(loginSecret, sessionToken);
        const deviceEncryptionToken = createEncryptionToken(deviceSecret, sessionToken);
        return [status, decryptedData, contentType, serverEncryptionToken, deviceEncryptionToken, sessionToken];
    } catch (e) {
        logger.error("Error decoding jdownloader API data. Data: %s", data.toString());
        return [status, null];
    }
}


async function getDevice(serverEncryptionToken, deviceName, params) {
    const rid = uniqueRid();
    const path = `/my/listdevices?${querystring.stringify({ ...params, rid })}`;
    const signature = crypto
        .createHmac('sha256', serverEncryptionToken)
        .update(path)
        .digest('hex');
    const url = `${new URL(`https://api.jdownloader.org${path}&signature=${signature}`)}`

    const [status, , data] = await httpProxy(url, {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
        },
    });

    if (status !== 200) {
        logger.error("HTTP %d communicating with jdownloader. Data: %s", status, data.toString());
        return [status, data];
    }

    try {
        const decryptedData = JSON.parse(decrypt(data.toString(), serverEncryptionToken))
        const filteredDevice = decryptedData.list.filter(device => device.name === deviceName);
        return [status, filteredDevice[0].id];
    } catch (e) {
        logger.error("Error decoding jdownloader API data. Data: %s", data.toString());
        return [status, null];
    }

}

function createBody(rid, query, params) {
    const baseBody = {
        apiVer: 1,
        rid,
        url: query
    };
    return params ? { ...baseBody, params: [JSON.stringify(params)] } : baseBody;
}

async function queryPackages(deviceEncryptionToken, deviceId, sessionToken, params) {
    const rid = uniqueRid();
    const body = encrypt(JSON.stringify(createBody(rid, '/downloadsV2/queryPackages', params)), deviceEncryptionToken);
    const url = `${new URL(`https://api.jdownloader.org/t_${encodeURI(sessionToken)}_${encodeURI(deviceId)}/downloadsV2/queryPackages`)}`
    const [status, , data] = await httpProxy(url, {
        method: 'POST',
        body,
    });

    if (status !== 200) {
        logger.error("HTTP %d communicating with jdownloader. Data: %s", status, data.toString());
        return [status, data];
    }

    try {
        const decryptedData = JSON.parse(decrypt(data.toString(), deviceEncryptionToken))
        return decryptedData.data;
    } catch (e) {
        logger.error("Error decoding JDRss jdownloader data. Data: %s", data.toString());
        return [status, null];
    }

}


export default async function jdownloaderProxyHandler(req, res) {
    const widget = await getWidget(req);

    if (!widget) {
        return res.status(400).json({ error: "Invalid proxy service type" });
    }
    logger.debug("Getting data from JDRss API");
    const { username } = widget
    const { password } = widget

    const appKey = "homepage"
    const loginSecret = sha256(`${username}${password}server`)
    const deviceSecret = sha256(`${username}${password}device`)
    const email = username;

    const loginData = await login(loginSecret, deviceSecret, {
        appKey,
        email
    })

    const deviceData = await getDevice(loginData[3], widget.client, {
        sessiontoken: loginData[5]
    })

    const packageStatus = await queryPackages(loginData[4], deviceData[1], loginData[5], {
        "bytesLoaded": true,
        "bytesTotal": true,
        "comment": false,
        "enabled": true,
        "eta": false,
        "priority": false,
        "finished": true,
        "running": true,
        "speed": true,
        "status": true,
        "childCount": false,
        "hosts": false,
        "saveTo": false,
        "maxResults": -1,
        "startAt": 0,
    }
    )

    let totalLoaded = 0;
    let totalBytes = 0;
    let totalSpeed = 0;
    packageStatus.forEach(file => {
        totalBytes += file.bytesTotal;
        totalLoaded += file.bytesLoaded;
        if (file.finished !== true && file.speed) {
            totalSpeed += file.speed;
        }
    });

    const data = {
        downloadCount: packageStatus.length,
        bytesRemaining: totalBytes - totalLoaded,
        totalBytes,
        totalSpeed
    };

    return res.send(data);

}
