"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
const models_1 = require("../models");
const res_1 = require("../utils/res");
const cron_1 = require("cron");
const case_1 = require("../utils/case");
const cronUtils = require("../utils/cron");
const socket = require("../utils/socket");
const jsonUtils = require("../utils/json");
const helpers = require("../helpers");
const rsa = require("../crypto/rsa");
const moment = require("moment");
const path = require("path");
const network = require("../network");
const constants = require(path.join(__dirname, '../../config/constants.json'));
// store all current running jobs in memory
let jobs = {};
// init jobs from DB
exports.initializeCronJobs = () => __awaiter(void 0, void 0, void 0, function* () {
    yield helpers.sleep(1000);
    const subs = yield getRawSubs({ where: { ended: false } });
    subs.length && subs.forEach(sub => {
        console.log("=> starting subscription cron job", sub.id + ":", sub.cron);
        startCronJob(sub);
    });
});
function startCronJob(sub) {
    return __awaiter(this, void 0, void 0, function* () {
        jobs[sub.id] = new cron_1.CronJob(sub.cron, function () {
            return __awaiter(this, void 0, void 0, function* () {
                const subscription = yield models_1.models.Subscription.findOne({ where: { id: sub.id } });
                if (!subscription) {
                    delete jobs[sub.id];
                    return this.stop();
                }
                console.log('EXEC CRON =>', subscription.id);
                if (subscription.paused) { // skip, still in jobs{} tho
                    return this.stop();
                }
                let STOP = checkSubscriptionShouldAlreadyHaveEnded(subscription);
                if (STOP) { // end the job and return
                    console.log("stop");
                    subscription.update({ ended: true });
                    delete jobs[subscription.id];
                    return this.stop();
                }
                // SEND PAYMENT!!!
                sendSubscriptionPayment(subscription, false);
            });
        }, null, true);
    });
}
function checkSubscriptionShouldAlreadyHaveEnded(sub) {
    if (sub.endDate) {
        const now = new Date();
        if (now.getTime() > sub.endDate.getTime()) {
            return true;
        }
    }
    if (sub.endNumber) {
        if (sub.count >= sub.endNumber) {
            return true;
        }
    }
    return false;
}
function checkSubscriptionShouldEndAfterThisPayment(sub) {
    if (sub.endDate) {
        const { ms } = cronUtils.parse(sub.cron);
        const now = new Date();
        if ((now.getTime() + ms) > sub.endDate.getTime()) {
            return true;
        }
    }
    if (sub.endNumber) {
        if (sub.count + 1 >= sub.endNumber) {
            return true;
        }
    }
    return false;
}
function msgForSubPayment(owner, sub, isFirstMessage, forMe) {
    let text = '';
    if (isFirstMessage) {
        const alias = forMe ? 'You' : owner.alias;
        text = `${alias} subscribed\n`;
    }
    else {
        text = 'Subscription\n';
    }
    text += `Amount: ${sub.amount} sats\n`;
    text += `Interval: ${cronUtils.parse(sub.cron).interval}\n`;
    if (sub.endDate) {
        text += `End: ${moment(sub.endDate).format('MM/DD/YY')}\n`;
        text += `Status: ${sub.count + 1} sent`;
    }
    else if (sub.endNumber) {
        text += `Status: ${sub.count + 1} of ${sub.endNumber} sent`;
    }
    return text;
}
function sendSubscriptionPayment(sub, isFirstMessage) {
    return __awaiter(this, void 0, void 0, function* () {
        const owner = yield models_1.models.Contact.findOne({ where: { isOwner: true } });
        var date = new Date();
        date.setMilliseconds(0);
        const subscription = yield models_1.models.Subscription.findOne({ where: { id: sub.id } });
        if (!subscription) {
            return;
        }
        const chat = yield models_1.models.Chat.findOne({ where: { id: subscription.chatId } });
        if (!subscription) {
            console.log("=> no sub for this payment!!!");
            return;
        }
        const forMe = false;
        const text = msgForSubPayment(owner, sub, isFirstMessage, forMe);
        const contact = yield models_1.models.Contact.findByPk(sub.contactId);
        const enc = rsa.encrypt(contact.contactKey, text);
        network.sendMessage({
            chat: chat,
            sender: owner,
            type: constants.message_types.direct_payment,
            message: { amount: sub.amount, content: enc },
            amount: sub.amount,
            success: (data) => __awaiter(this, void 0, void 0, function* () {
                const shouldEnd = checkSubscriptionShouldEndAfterThisPayment(subscription);
                const obj = {
                    totalPaid: parseFloat(subscription.totalPaid || 0) + parseFloat(subscription.amount),
                    count: parseInt(subscription.count || 0) + 1,
                    ended: false,
                };
                if (shouldEnd) {
                    obj.ended = true;
                    if (jobs[sub.id])
                        jobs[subscription.id].stop();
                    delete jobs[subscription.id];
                }
                yield subscription.update(obj);
                const forMe = true;
                const text2 = msgForSubPayment(owner, sub, isFirstMessage, forMe);
                const encText = rsa.encrypt(owner.contactKey, text2);
                const message = yield models_1.models.Message.create({
                    chatId: chat.id,
                    sender: owner.id,
                    type: constants.message_types.direct_payment,
                    status: constants.statuses.confirmed,
                    messageContent: encText,
                    amount: subscription.amount,
                    amountMsat: parseFloat(subscription.amount) * 1000,
                    date: date,
                    createdAt: date,
                    updatedAt: date,
                    subscriptionId: subscription.id,
                });
                socket.sendJson({
                    type: 'direct_payment',
                    response: jsonUtils.messageToJson(message, chat)
                });
            }),
            failure: (err) => __awaiter(this, void 0, void 0, function* () {
                console.log("SEND PAY ERROR");
                let errMessage = constants.payment_errors[err] || 'Unknown';
                errMessage = 'Payment Failed: ' + errMessage;
                const message = yield models_1.models.Message.create({
                    chatId: chat.id,
                    sender: owner.id,
                    type: constants.message_types.direct_payment,
                    status: constants.statuses.failed,
                    messageContent: errMessage,
                    amount: sub.amount,
                    amountMsat: parseFloat(sub.amount) * 1000,
                    date: date,
                    createdAt: date,
                    updatedAt: date,
                    subscriptionId: sub.id,
                });
                socket.sendJson({
                    type: 'direct_payment',
                    response: jsonUtils.messageToJson(message, chat)
                });
            })
        });
    });
}
// pause sub
function pauseSubscription(req, res) {
    return __awaiter(this, void 0, void 0, function* () {
        const id = parseInt(req.params.id);
        try {
            const sub = yield models_1.models.Subscription.findOne({ where: { id } });
            if (sub) {
                sub.update({ paused: true });
                if (jobs[id])
                    jobs[id].stop();
                res_1.success(res, jsonUtils.subscriptionToJson(sub, null));
            }
            else {
                res_1.failure(res, 'not found');
            }
        }
        catch (e) {
            console.log('ERROR pauseSubscription', e);
            res_1.failure(res, e);
        }
    });
}
exports.pauseSubscription = pauseSubscription;
;
// restart sub
function restartSubscription(req, res) {
    return __awaiter(this, void 0, void 0, function* () {
        const id = parseInt(req.params.id);
        try {
            const sub = yield models_1.models.Subscription.findOne({ where: { id } });
            if (sub) {
                sub.update({ paused: false });
                if (jobs[id])
                    jobs[id].start();
                res_1.success(res, jsonUtils.subscriptionToJson(sub, null));
            }
            else {
                res_1.failure(res, 'not found');
            }
        }
        catch (e) {
            console.log('ERROR restartSubscription', e);
            res_1.failure(res, e);
        }
    });
}
exports.restartSubscription = restartSubscription;
;
function getRawSubs(opts = {}) {
    return __awaiter(this, void 0, void 0, function* () {
        const options = Object.assign({ order: [['id', 'asc']] }, opts);
        try {
            const subs = yield models_1.models.Subscription.findAll(options);
            return subs;
        }
        catch (e) {
            throw e;
        }
    });
}
// all subs
exports.getAllSubscriptions = (req, res) => __awaiter(void 0, void 0, void 0, function* () {
    try {
        const subs = yield getRawSubs();
        res_1.success(res, subs.map(sub => jsonUtils.subscriptionToJson(sub, null)));
    }
    catch (e) {
        console.log('ERROR getAllSubscriptions', e);
        res_1.failure(res, e);
    }
});
// one sub by id
function getSubscription(req, res) {
    return __awaiter(this, void 0, void 0, function* () {
        try {
            const sub = yield models_1.models.Subscription.findOne({ where: { id: req.params.id } });
            res_1.success(res, jsonUtils.subscriptionToJson(sub, null));
        }
        catch (e) {
            console.log('ERROR getSubscription', e);
            res_1.failure(res, e);
        }
    });
}
exports.getSubscription = getSubscription;
;
// delete sub by id
function deleteSubscription(req, res) {
    return __awaiter(this, void 0, void 0, function* () {
        const id = req.params.id;
        if (!id)
            return;
        try {
            if (jobs[id]) {
                jobs[id].stop();
                delete jobs[id];
            }
            models_1.models.Subscription.destroy({ where: { id } });
            res_1.success(res, true);
        }
        catch (e) {
            console.log('ERROR deleteSubscription', e);
            res_1.failure(res, e);
        }
    });
}
exports.deleteSubscription = deleteSubscription;
;
// all subs for contact id
exports.getSubscriptionsForContact = (req, res) => __awaiter(void 0, void 0, void 0, function* () {
    try {
        const subs = yield getRawSubs({ where: { contactId: req.params.contactId } });
        res_1.success(res, subs.map(sub => jsonUtils.subscriptionToJson(sub, null)));
    }
    catch (e) {
        console.log('ERROR getSubscriptionsForContact', e);
        res_1.failure(res, e);
    }
});
// create new sub
function createSubscription(req, res) {
    return __awaiter(this, void 0, void 0, function* () {
        const date = new Date();
        date.setMilliseconds(0);
        const s = jsonToSubscription(Object.assign(Object.assign({}, req.body), { count: 0, total_paid: 0, createdAt: date, ended: false, paused: false }));
        if (!s.cron) {
            return res_1.failure(res, 'Invalid interval');
        }
        try {
            const owner = yield models_1.models.Contact.findOne({ where: { isOwner: true } });
            const chat = yield helpers.findOrCreateChat({
                chat_id: req.body.chat_id,
                owner_id: owner.id,
                recipient_id: req.body.contact_id,
            });
            s.chatId = chat.id; // add chat id if newly created
            if (!owner || !chat) {
                return res_1.failure(res, 'Invalid chat or contact');
            }
            const sub = yield models_1.models.Subscription.create(s);
            startCronJob(sub);
            const isFirstMessage = true;
            sendSubscriptionPayment(sub, isFirstMessage);
            res_1.success(res, jsonUtils.subscriptionToJson(sub, chat));
        }
        catch (e) {
            console.log('ERROR createSubscription', e);
            res_1.failure(res, e);
        }
    });
}
exports.createSubscription = createSubscription;
;
function editSubscription(req, res) {
    return __awaiter(this, void 0, void 0, function* () {
        console.log('=> editSubscription');
        const date = new Date();
        date.setMilliseconds(0);
        const id = parseInt(req.params.id);
        const s = jsonToSubscription(Object.assign(Object.assign({}, req.body), { count: 0, createdAt: date, ended: false, paused: false }));
        try {
            if (!id || !s.chatId || !s.cron) {
                return res_1.failure(res, 'Invalid data');
            }
            const subRecord = yield models_1.models.Subscription.findOne({ where: { id } });
            if (!subRecord) {
                return res_1.failure(res, 'No subscription found');
            }
            // stop so it can be restarted
            if (jobs[id])
                jobs[id].stop();
            const obj = {
                cron: s.cron,
                updatedAt: date,
            };
            if (s.amount)
                obj.amount = s.amount;
            if (s.endDate)
                obj.endDate = s.endDate;
            if (s.endNumber)
                obj.endNumber = s.endNumber;
            const sub = yield subRecord.update(obj);
            const end = checkSubscriptionShouldAlreadyHaveEnded(sub);
            if (end) {
                yield subRecord.update({ ended: true });
                delete jobs[id];
            }
            else {
                startCronJob(sub); // restart
            }
            const chat = yield models_1.models.Chat.findOne({ where: { id: s.chatId } });
            res_1.success(res, jsonUtils.subscriptionToJson(sub, chat));
        }
        catch (e) {
            console.log('ERROR createSubscription', e);
            res_1.failure(res, e);
        }
    });
}
exports.editSubscription = editSubscription;
;
function jsonToSubscription(j) {
    console.log("=>", j);
    const cron = cronUtils.make(j.interval);
    return case_1.toCamel(Object.assign(Object.assign({}, j), { cron }));
}
//# sourceMappingURL=subscriptions.js.map