"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.validateWithNetworkResponses = void 0;
const rxjs_1 = require("rxjs");
const checkCollectionCompletion_1 = require("./checkCollectionCompletion");
const manageStorageMatrix_1 = require("./manageStorageMatrix");
const retrieveFilteredSystemMessages_1 = require("./retrieveFilteredSystemMessages");
const utils_1 = require("./utils");
/**
 * This function orchestrates the process of validating network responses against expected results.
 *
 * 1. **Message Retrieval**: The function starts by retrieving relevant system messages to this query indefinetely.
 * 2. **Collection Completion Check**: During retrieval, the function checks if the collection is complete.
 * 3. **Storage Matrix Creation**: If the collection is complete, the function converts the responses into a structured storage matrix.
 * 4. **Matrix Consistency Check**: The function then checks if all nodes agree on the storage matrix.
 * 5. **Response Verification**: Finally, the function verifies if all the messages from the HTTP response are present in the storage matrix.
 *
 * If all these steps are completed successfully within a certain timeout period, the function returns true, indicating that the network responses are valid.
 * If any of these steps are not completed within the timeout period, an error is thrown, indicating that the network verification has timed out.
 */
const validateWithNetworkResponses = ({ queryInput, queryUrl, logger, nodeManager, systemMessages$, responseStream, verificationOptions, }) => {
    const DEFAULT_TIMEOUT = 15000;
    // TODO might be good to have a good callback for when the system stream is really ready. For now, we're hardcoding it as
    //  500ms after start, which is not ideal.
    const systemStreamSubscriptionIsReady$ = (0, rxjs_1.merge)(systemMessages$, (0, rxjs_1.of)(1)).pipe((0, rxjs_1.delay)(500), (0, rxjs_1.first)());
    const queryNodeAddress$ = (0, utils_1.nodeAddressFromUrl)(queryUrl, nodeManager).pipe((0, rxjs_1.map)((address) => address.toLowerCase()));
    const participatingNodesExtractedFromContract$ = (0, rxjs_1.defer)(() => nodeManager.getActiveNodes());
    const metadataStream$ = systemStreamSubscriptionIsReady$.pipe((0, rxjs_1.switchMap)(() => responseStream.metadataStream));
    const httpResponse$ = systemStreamSubscriptionIsReady$.pipe((0, rxjs_1.switchMap)(() => responseStream.asObservable()));
    const participatingNodesExtractedFromResponse$ = metadataStream$.pipe((0, rxjs_1.map)((s) => s.participatingNodesAddress), 
    // we expect it to be present always, when using this function
    (0, rxjs_1.first)(Boolean), (0, utils_1.rethrowErrorWithSourceActionName)(`getting participating nodes from response`));
    /**
     * first will be the ones from contract, as this is quicker to fetch.
     * we want this intermediary value to already filter responses from network.
     *
     * then, from the http response we may get the final answer, the addresses
     * that the query node really considered for answering.
     *
     * edge cases expect more nodes on contract than the ones present on response.
     * this is important, else we would filter out possible responses.
     */
    const activeNodeAddresses$ = (0, rxjs_1.concat)(participatingNodesExtractedFromContract$, participatingNodesExtractedFromResponse$).pipe((0, rxjs_1.share)({ resetOnRefCountZero: true }));
    const httpResponseRequestId$ = (0, utils_1.lowercaseRequestidFromLogstoreMetadata)(metadataStream$).pipe(
    // as we start this process in parallel, before getting the response from Query Node
    // we will start it with null, not filtering values by this matter until we have one
    (0, rxjs_1.startWith)(null));
    const { propagates$, request$, responses$, startCollectingSystemMessages$ } = (0, retrieveFilteredSystemMessages_1.retrieveFilteredSystemMessages)(systemMessages$, queryNodeAddress$, httpResponseRequestId$, queryInput);
    // pipe from collection start, so we're sure to start the process from same observable
    const doneCollectingSystemMessages$ = startCollectingSystemMessages$.pipe((0, rxjs_1.switchMap)(() => (0, checkCollectionCompletion_1.checkCollectionCompletion)({
        propagates$: propagates$,
        responses$: responses$,
        expectedNodes$: activeNodeAddresses$,
        request$: request$,
    })), 
    // only emits once, when done
    (0, rxjs_1.first)((isDone) => isDone), (0, utils_1.rethrowErrorWithSourceActionName)(`checking collection completion`));
    // once we're done collecting, get the responses into a storage matrix
    const storageMatrix$ = doneCollectingSystemMessages$.pipe(
    // collecting is done, so we start the next part that is handling responses
    (0, rxjs_1.switchMap)(() => responses$), (0, rxjs_1.map)(manageStorageMatrix_1.convertToStorageMatrix), (0, rxjs_1.share)({ resetOnRefCountZero: true }));
    const nodesAgreeOnMatrix$ = storageMatrix$.pipe((0, rxjs_1.map)(manageStorageMatrix_1.nodesAgreeOnStorageMatrix), 
    // should error if completed without a value.
    // also, will stop on first because we only get a value once we're done collecting
    (0, rxjs_1.first)(Boolean));
    const messagesFromHttpResponse$ = httpResponse$.pipe((0, rxjs_1.toArray)());
    const allMessagesArePresentInStorageMatrix$ = (0, rxjs_1.combineLatest)({
        messagesFromHttpResponse: messagesFromHttpResponse$,
        storageMatrix: storageMatrix$,
    }).pipe((0, rxjs_1.map)(manageStorageMatrix_1.verifyMessagePresenceInStorageMatrix));
    const conditionsWereValidated$ = (0, rxjs_1.combineLatest)([
        allMessagesArePresentInStorageMatrix$,
        nodesAgreeOnMatrix$,
    ]).pipe((0, rxjs_1.map)((values) => values.every(Boolean)), (0, rxjs_1.first)(Boolean));
    const timeoutError$ = (0, rxjs_1.combineLatest)({
        // combining values from each so that we can log or even return this value if desired
        propagates: propagates$,
        request: request$,
        responses: responses$,
        timer: (0, rxjs_1.timer)(verificationOptions?.timeout ?? DEFAULT_TIMEOUT),
    }).pipe((0, rxjs_1.map)(({ timer: _timer, ..._rest }) => {
        // this maybe useful for debugging, however it is too verbose for production (?)
        // logger.error('Timeout error', rest);
        throw new Error('Network verification timed out');
    }));
    return (0, rxjs_1.firstValueFrom)((0, rxjs_1.race)(conditionsWereValidated$, timeoutError$).pipe((0, rxjs_1.catchError)((error) => {
        if (typeof error === 'object' && error && 'message' in error) {
            logger.error(error.message);
        }
        else {
            logger.error(String(error));
        }
        return (0, rxjs_1.throwError)(() => error);
    })));
};
exports.validateWithNetworkResponses = validateWithNetworkResponses;
//# sourceMappingURL=validateNetworkResponses.js.map