/*jshint esversion: 6 */
const Channel = module.exports;

const Util = require("../common-util");
const nThen = require("nthen");
const Core = require("./core");
const Metadata = require("./metadata");

Channel.clearOwnedChannel = function (Env, safeKey, channelId, cb, Server) {
    if (typeof(channelId) !== 'string' || channelId.length !== 32) {
        return cb('INVALID_ARGUMENTS');
    }
    var unsafeKey = Util.unescapeKeyCharacters(safeKey);

    Metadata.getMetadata(Env, channelId, function (err, metadata) {
        if (err) { return void cb(err); }
        if (!Core.hasOwners(metadata)) { return void cb('E_NO_OWNERS'); }
        // Confirm that the channel is owned by the user in question
        if (!Core.isOwner(metadata, unsafeKey)) {
            return void cb('INSUFFICIENT_PERMISSIONS');
        }
        return void Env.msgStore.clearChannel(channelId, function (e) {
            if (e) { return void cb(e); }
            cb();

            const channel_cache = Env.historyKeeper.channel_cache;

            const clear = function () {
                // delete the channel cache because it will have been invalidated
                delete channel_cache[channelId];
            };

            nThen(function (w) {
                Server.getChannelUserList(channelId).forEach(function (userId) {
                    Server.send(userId, [
                        0,
                        Env.historyKeeper.id,
                        'MSG',
                        userId,
                        JSON.stringify({
                            error: 'ECLEARED',
                            channel: channelId
                        })
                    ], w());
                });
            }).nThen(function () {
                clear();
            }).orTimeout(function () {
                Env.Log.warn("ON_CHANNEL_CLEARED_TIMEOUT", channelId);
                clear();
            }, 30000);
        });
    });
};

Channel.removeOwnedChannel = function (Env, safeKey, channelId, cb, Server) {
    if (typeof(channelId) !== 'string' || !Core.isValidId(channelId)) {
        return cb('INVALID_ARGUMENTS');
    }
    var unsafeKey = Util.unescapeKeyCharacters(safeKey);

    if (Env.blobStore.isFileId(channelId)) {
        var blobId = channelId;

        return void nThen(function (w) {
            // check if you have permissions
            Env.blobStore.isOwnedBy(safeKey, blobId, w(function (err, owned) {
                if (err || !owned) {
                    w.abort();
                    return void cb("INSUFFICIENT_PERMISSIONS");
                }
            }));
        }).nThen(function (w) {
            // remove the blob
            return void Env.blobStore.archive.blob(blobId, w(function (err) {
                Env.Log.info('ARCHIVAL_OWNED_FILE_BY_OWNER_RPC', {
                    safeKey: safeKey,
                    blobId: blobId,
                    status: err? String(err): 'SUCCESS',
                });
                if (err) {
                    w.abort();
                    return void cb(err);
                }
            }));
        }).nThen(function () {
            // archive the proof
            return void Env.blobStore.archive.proof(safeKey, blobId, function (err) {
                Env.Log.info("ARCHIVAL_PROOF_REMOVAL_BY_OWNER_RPC", {
                    safeKey: safeKey,
                    blobId: blobId,
                    status: err? String(err): 'SUCCESS',
                });
                if (err) {
                    return void cb("E_PROOF_REMOVAL");
                }
                cb(void 0, 'OK');
            });
        });
    }

    Metadata.getMetadata(Env, channelId, function (err, metadata) {
        if (err) { return void cb(err); }
        if (!Core.hasOwners(metadata)) { return void cb('E_NO_OWNERS'); }
        if (!Core.isOwner(metadata, unsafeKey)) {
            return void cb('INSUFFICIENT_PERMISSIONS');
        }
        // temporarily archive the file
        return void Env.msgStore.archiveChannel(channelId, function (e) {
            Env.Log.info('ARCHIVAL_CHANNEL_BY_OWNER_RPC', {
                unsafeKey: unsafeKey,
                channelId: channelId,
                status: e? String(e): 'SUCCESS',
            });
            if (e) {
                return void cb(e);
            }
            cb(void 0, 'OK');

            const channel_cache = Env.historyKeeper.channel_cache;
            const metadata_cache = Env.historyKeeper.metadata_cache;

            const clear = function () {
                delete channel_cache[channelId];
                Server.clearChannel(channelId);
                delete metadata_cache[channelId];
            };

            // an owner of a channel deleted it
            nThen(function (w) {
                // close the channel in the store
                Env.msgStore.closeChannel(channelId, w());
            }).nThen(function (w) {
                // Server.channelBroadcast would be better
                // but we can't trust it to track even one callback,
                // let alone many in parallel.
                // so we simulate it on this side to avoid race conditions
                Server.getChannelUserList(channelId).forEach(function (userId) {
                    Server.send(userId, [
                        0,
                        Env.historyKeeper.id,
                        "MSG",
                        userId,
                        JSON.stringify({
                            error: 'EDELETED',
                            channel: channelId,
                        })
                    ], w());
                });
            }).nThen(function () {
                // clear the channel's data from memory
                // once you've sent everyone a notice that the channel has been deleted
                clear();
            }).orTimeout(function () {
                Env.Log.warn('ON_CHANNEL_DELETED_TIMEOUT', channelId);
                clear();
            }, 30000);
        });
    });
};

Channel.trimHistory = function (Env, safeKey, data, cb) {
    if (!(data && typeof(data.channel) === 'string' && typeof(data.hash) === 'string' && data.hash.length === 64)) {
        return void cb('INVALID_ARGS');
    }

    var channelId = data.channel;
    var unsafeKey = Util.unescapeKeyCharacters(safeKey);
    var hash = data.hash;

    nThen(function (w) {
        Metadata.getMetadata(Env, channelId, w(function (err, metadata) {
            if (err) { return void cb(err); }
            if (!Core.hasOwners(metadata)) {
                w.abort();
                return void cb('E_NO_OWNERS');
            }
            if (!Core.isOwner(metadata, unsafeKey)) {
                w.abort();
                return void cb("INSUFFICIENT_PERMISSIONS");
            }
            // else fall through to the next block
        }));
    }).nThen(function () {
        Env.msgStore.trimChannel(channelId, hash, function (err) {
            if (err) { return void cb(err); }
            // clear historyKeeper's cache for this channel
            Env.historyKeeper.channelClose(channelId);
            cb(void 0, 'OK');
            delete Env.historyKeeper.channel_cache[channelId];
            delete Env.historyKeeper.metadata_cache[channelId];
        });
    });
};

var ARRAY_LINE = /^\[/;

/*  Files can contain metadata but not content
    call back with true if the channel log has no content other than metadata
    otherwise false
*/
Channel.isNewChannel = function (Env, channel, cb) {
    if (!Core.isValidId(channel)) { return void cb('INVALID_CHAN'); }
    if (channel.length !== 32) { return void cb('INVALID_CHAN'); }

    var done = false;
    Env.msgStore.getMessages(channel, function (msg) {
        if (done) { return; }
        try {
            if (typeof(msg) === 'string' && ARRAY_LINE.test(msg)) {
                done = true;
                return void cb(void 0, false);
            }
        } catch (e) {
            Env.WARN('invalid message read from store', e);
        }
    }, function () {
        if (done) { return; }
        // no more messages...
        cb(void 0, true);
    });
};

/*  writePrivateMessage
    allows users to anonymously send a message to the channel
    prevents their netflux-id from being stored in history
    and from being broadcast to anyone that might currently be in the channel

    Otherwise behaves the same as sending to a channel
*/
Channel.writePrivateMessage = function (Env, args, cb, Server) {
    var channelId = args[0];
    var msg = args[1];

    // don't bother handling empty messages
    if (!msg) { return void cb("INVALID_MESSAGE"); }

    // don't support anything except regular channels
    if (!Core.isValidId(channelId) || channelId.length !== 32) {
        return void cb("INVALID_CHAN");
    }

    // We expect a modern netflux-websocket-server instance
    // if this API isn't here everything will fall apart anyway
    if (!(Server && typeof(Server.send) === 'function')) {
        return void cb("NOT_IMPLEMENTED");
    }

    // historyKeeper expects something with an 'id' attribute
    // it will fail unless you provide it, but it doesn't need anything else
    var channelStruct = {
        id: channelId,
    };

    // construct a message to store and broadcast
    var fullMessage = [
        0, // idk
        null, // normally the netflux id, null isn't rejected, and it distinguishes messages written in this way
        "MSG", // indicate that this is a MSG
        channelId, // channel id
        msg // the actual message content. Generally a string
    ];

    // historyKeeper already knows how to handle metadata and message validation, so we just pass it off here
    // if the message isn't valid it won't be stored.
    Env.historyKeeper.channelMessage(Server, channelStruct, fullMessage);

    Server.getChannelUserList(channelId).forEach(function (userId) {
        Server.send(userId, fullMessage);
    });

    cb();
};