"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.registerRoutes = void 0;
const Comms = __importStar(require("../Comms"));
const Context_1 = require("../Context");
const Utils_1 = require("../Utils");
const funcs = __importStar(require("./functions"));
const logging = __importStar(require("./logging"));
const state_1 = require("./state");
const Self_1 = require("../Self");
const util_1 = require("util");
const NodeList_1 = require("../NodeList");
const debugMiddleware_1 = require("../../network/debugMiddleware");
const Archivers_1 = require("../Archivers");
const InternalRouteEnum_1 = require("../../types/enum/InternalRouteEnum");
const nestedCounters_1 = require("../../utils/nestedCounters");
const profiler_1 = require("../../utils/profiler");
const LostArchiverInvestigateReq_1 = require("../../types/LostArchiverInvestigateReq");
const Helpers_1 = require("../../types/Helpers");
const TypeIdentifierEnum_1 = require("../../types/enum/TypeIdentifierEnum");
const GossipValidation_1 = require("../../utils/GossipValidation");
/** Gossip */
const lostArchiverUpGossip = (payload, sender, tracker) => {
    // the original gossip source is a node or nodes that the archiver notified as to the fact
    // that rumors of its death were highly exaggerated
    // If Lost Archiver Detection is disabled, return
    if (Context_1.config.p2p.enableLostArchiversCycles === false) {
        return;
    }
    if (!(0, GossipValidation_1.checkGossipPayload)(payload, { type: 's', downMsg: 'o', refuteMsg: 'o', cycle: 's', sign: 'o' }, 'lostArchiverUpGossip', sender)) {
        return;
    }
    // to-do: need a guard on logging logging.info() and others?
    logging.info(`lostArchiverUpGossip: payload: ${(0, util_1.inspect)(payload)}, sender: ${sender}, tracker: ${tracker}`);
    // check args
    /* prettier-ignore */ if (!sender) {
        logging.warn(`lostArchiverUpGossip: missing sender`);
        return;
    }
    /* prettier-ignore */ if (!tracker) {
        logging.warn(`lostArchiverUpGossip: missing tracker`);
        return;
    }
    const error = funcs.errorForArchiverUpMsg(payload);
    if (error) {
        nestedCounters_1.nestedCountersInstance.countEvent('lostArchivers', `lostArchiverUpGossip invalid payload ${error}`);
        logging.warn(`lostArchiverUpGossip: invalid payload error: ${error}, payload: ${(0, util_1.inspect)(payload)}`);
        return;
    }
    const upMsg = payload;
    const target = upMsg.downMsg.investigateMsg.target;
    // to-do: check target is a string or hexstring and min length
    const record = state_1.lostArchiversMap.get(target);
    if (!record) {
        // nothing to do
        logging.info(`lostArchiverUpGossip: no record for target ${target}. returning`);
        return;
    }
    logging.info(`lostArchiverUpGossip: record: ${record}`);
    // Return if we have already processed this message
    if (record && ['up'].includes(record.status))
        return;
    logging.info(`lostArchiverUpGossip: setting status to 'up' and gossiping`);
    record.status = 'up';
    record.archiverUpMsg = upMsg;
    // to-do: idea:
    // record.updated = {source: 'lostArchiverUpGossip', cycle: currentCycle, quarter: currentQuarter, what: 'up'}
    // or even:
    // record.updated.push({source: 'lostArchiverUpGossip', cycle: currentCycle, quarter: currentQuarter, what: 'up'})
    // ... is LostArchiverRecord in the cycle record? if not, we're good to add this debugging info
    Comms.sendGossip('lost-archiver-up', payload, tracker, Self_1.id, NodeList_1.byIdOrder, false); // isOrigin: false
    record.gossippedUpMsg = true;
};
const lostArchiverDownGossip = (payload, sender, tracker) => {
    // the original gossip source is the investigator node that confirmed the archiver is down
    // If Lost Archiver Detection is disabled, return
    if (Context_1.config.p2p.enableLostArchiversCycles === false) {
        return;
    }
    if (!(0, GossipValidation_1.checkGossipPayload)(payload, { type: 's', investigateMsg: 'o', cycle: 's', sign: 'o' }, 'lostArchiverDownGossip', sender)) {
        return;
    }
    logging.info(`lostArchiverDownGossip: payload: ${(0, util_1.inspect)(payload)}, sender: ${sender}, tracker: ${tracker}`);
    // check args
    if (!sender) {
        logging.warn(`lostArchiverDownGossip: missing sender`);
        return;
    }
    if (!tracker) {
        logging.warn(`lostArchiverDownGossip: missing tracker`);
        return;
    }
    const error = funcs.errorForArchiverDownMsg(payload);
    if (error) {
        nestedCounters_1.nestedCountersInstance.countEvent('lostArchivers', `lostArchiverDownGossip invalid payload ${error}`);
        logging.warn(`lostArchiverDownGossip: invalid payload error: ${error}, payload: ${(0, util_1.inspect)(payload)}`);
        return;
    }
    const downMsg = payload;
    const target = downMsg.investigateMsg.target;
    // to-do: check target is a string or hexstring and min length
    let record = state_1.lostArchiversMap.get(target);
    // Return if we have already processed this message
    if (record && ['up', 'down'].includes(record.status))
        return;
    // If we dont have an entry in lostArchiversMap, make one
    if (!record) {
        record = funcs.createLostArchiverRecord({
            target,
            status: 'down',
            archiverDownMsg: downMsg,
        });
        state_1.lostArchiversMap.set(target, record);
    }
    // Otherwise, set status to 'down' and save the downMsg
    else {
        record.status = 'down';
        record.archiverDownMsg = downMsg;
    }
    Comms.sendGossip('lost-archiver-down', payload, tracker, Self_1.id, NodeList_1.byIdOrder, false); // isOrigin: false
    record.gossippedDownMsg = true;
    // to-do: idea: record the update
};
/** Internal */
// const investigateLostArchiverRoute: Route<InternalHandler<SignedObject<InvestigateArchiverMsg>>> = {
//   method: 'GET',
//   name: 'lost-archiver-investigate',
//   handler: (payload, response, sender) => {
//     // we're being told to investigate a seemingly lost archiver
//     // If Lost Archiver Detection is disabled, return
//     if (config.p2p.enableLostArchiversCycles === false) {
//       return
//     }
//     logging.info(`investigateLostArchiverRoute: payload: ${inspect(payload)}, sender: ${sender}`)
//     // check args
//     if (!payload) { logging.warn(`investigateLostArchiverRoute: missing payload`); return }
//     if (!response) { logging.warn(`investigateLostArchiverRoute: missing response`); return }
//     if (!sender) { logging.warn(`investigateLostArchiverRoute: missing sender`); return }
//     const error = funcs.errorForInvestigateArchiverMsg(payload)
//     if (error) {
//       nestedCountersInstance.countEvent('lostArchivers', `investigateLostArchiverRoute invalid payload ${error}`)
//       logging.warn(
//         `investigateLostArchiverRoute: invalid payload error: ${error}, payload: ${inspect(payload)}`
//       )
//       return
//     }
//     if (id !== payload.investigator) {
//       logging.info(`investigateLostArchiverRoute: not the investigator. returning`)
//       return
//       // to-do: check cycle too?
//       // original comment:
//       // Ignore hits here if we're not the designated Investigator for the given Archiver and cycle
//     }
//     logging.info(`investigateLostArchiverRoute: calling investigateArchiver()`)
//     funcs.investigateArchiver(payload)
//   },
// }
const investigateLostArchiverRouteBinary = {
    name: InternalRouteEnum_1.InternalRouteEnum.binary_lost_archiver_investigate,
    handler: (payload, respond, header) => {
        // If Lost Archiver Detection is disabled, return
        if (Context_1.config.p2p.enableLostArchiversCycles === false) {
            return;
        }
        const route = InternalRouteEnum_1.InternalRouteEnum.binary_lost_archiver_investigate;
        nestedCounters_1.nestedCountersInstance.countEvent('internal', route);
        profiler_1.profilerInstance.scopedProfileSectionStart(route, false, payload.length);
        try {
            const reqStream = (0, Helpers_1.getStreamWithTypeCheck)(payload, TypeIdentifierEnum_1.TypeIdentifierEnum.cGetAccountDataReq);
            if (!reqStream) {
                nestedCounters_1.nestedCountersInstance.countEvent('internal', `${route}-invalid_request`);
                logging.warn(`${route}: Invalid request stream`);
                return;
            }
            // Deserialize the payload
            const deserializedPayload = (0, LostArchiverInvestigateReq_1.deserializeLostArchiverInvestigateReq)(reqStream);
            logging.info(`investigateLostArchiverRoute: payload: ${(0, util_1.inspect)(deserializedPayload)}, sender: ${header.sender_id}`);
            // Basic argument checks
            if (!deserializedPayload) {
                logging.warn(`${route}: Missing payload`);
                return;
            }
            if (!respond) {
                logging.warn(`${route}: Missing response method`);
                return;
            }
            if (!header.sender_id) {
                logging.warn(`${route}: Missing sender ID`);
                return;
            }
            const error = funcs.errorForInvestigateArchiverMsg(deserializedPayload);
            if (error) {
                nestedCounters_1.nestedCountersInstance.countEvent('lostArchivers', `investigateLostArchiverRouteBinary invalid payload ${error}`);
                logging.warn(`${route}: Invalid payload error: ${error}, payload: ${(0, util_1.inspect)(payload)}`);
                return;
            }
            // Investigator ID check
            if (Self_1.id !== deserializedPayload.investigator) {
                logging.info(`${route}: Not the investigator, ignoring the request.`);
                // Potential TODO: Check cycle as well?
                return;
            }
            // Proceed with the investigation logic
            logging.info(`${route}: Calling investigateArchiver()`);
            funcs.investigateArchiver(deserializedPayload);
        }
        catch (error) {
            // Log and handle any errors that occurred during the process
            nestedCounters_1.nestedCountersInstance.countEvent('internal', `${route}-exception`);
            logging.error(`${route}: Error processing request - ${error.message}`);
        }
        finally {
            // Ensure profiling ends correctly in all cases
            profiler_1.profilerInstance.scopedProfileSectionEnd(route);
        }
    },
};
/** External */
const refuteLostArchiverRoute = {
    method: 'POST',
    name: 'lost-archiver-refute',
    handler: (req, res) => {
        // If Lost Archiver Detection is disabled, return
        if (Context_1.config.p2p.enableLostArchiversCycles === false) {
            return;
        }
        // to-do: verify that validateArchiverUpMsg checks the signature or that whatever machinery invokes this route does; or do it ourselves
        // called by a refuting archiver
        const error = funcs.errorForArchiverRefutesLostMsg(req.body);
        if (error) {
            nestedCounters_1.nestedCountersInstance.countEvent('lostArchivers', `refuteLostArchiverRoute invalid payload ${error}`);
            logging.warn(`refuteLostArchiverRoute: invalid archiver up message error: ${error}, payload: ${(0, util_1.inspect)(req.body)}`);
            res.json({ status: 'failure', message: error });
            return;
        }
        const refuteMsg = req.body;
        const target = refuteMsg.archiver;
        // to-do: check target is a string or hexstring and min length
        let record = state_1.lostArchiversMap.get(target);
        if (!record) {
            record = funcs.createLostArchiverRecord({ target, status: 'up', archiverRefuteMsg: refuteMsg });
            state_1.lostArchiversMap.set(target, record);
        }
        if (record.status !== 'up')
            record.status = 'up';
        if (!record.archiverRefuteMsg)
            record.archiverRefuteMsg = refuteMsg;
        res.json({ status: 'success' });
    },
};
const reportFakeLostArchiverRoute = {
    method: 'GET',
    name: 'report-fake-lost-archiver',
    handler: (req, res) => {
        // If Lost Archiver Detection is disabled, return
        if (Context_1.config.p2p.enableLostArchiversCycles === false) {
            return;
        }
        logging.warn('/report-fake-lost-archiver: reporting fake lost archiver');
        // the archiver can be specified with an optional 'publicKey' or 'publickey' query param
        // otherwise a random one is chosen
        let publicKey = typeof req.query.publicKey === 'string' ? req.query.publicKey : null;
        if (publicKey == null)
            publicKey = req.body.publickey; // lowercase k
        const pick = publicKey ? 'specified' : 'random';
        const archiver = publicKey ? (0, Archivers_1.getArchiverWithPublicKey)(publicKey) : (0, Utils_1.getRandomAvailableArchiver)();
        if (archiver) {
            funcs.reportLostArchiver(archiver.publicKey, 'fake lost archiver report');
            res.json(Context_1.crypto.sign({
                status: 'accepted',
                pick,
                archiver,
                message: 'will report fake lost archiver',
            }));
        }
        else {
            res.json(Context_1.crypto.sign({
                status: 'failed',
                pick,
                archiver,
                message: 'archiver not found',
            }));
        }
    },
};
const routes = {
    debugExternal: [reportFakeLostArchiverRoute],
    external: [refuteLostArchiverRoute],
    internal: [],
    internalBinary: [investigateLostArchiverRouteBinary],
    gossip: {
        'lost-archiver-up': lostArchiverUpGossip,
        'lost-archiver-down': lostArchiverDownGossip,
    },
};
function registerRoutes() {
    for (const route of routes.debugExternal) {
        Context_1.network._registerExternal(route.method, route.name, debugMiddleware_1.isDebugModeMiddleware, route.handler);
    }
    for (const route of routes.external) {
        Context_1.network._registerExternal(route.method, route.name, route.handler);
    }
    for (const route of routes.internal) {
        Comms.registerInternal(route.name, route.handler);
    }
    for (const route of routes.internalBinary) {
        Comms.registerInternalBinary(route.name, route.handler);
    }
    for (const [name, handler] of Object.entries(routes.gossip)) {
        Comms.registerGossipHandler(name, handler);
    }
}
exports.registerRoutes = registerRoutes;
//# sourceMappingURL=routes.js.map