"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
    return function (target, key) { decorator(target, key, paramIndex); }
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Queries = exports.QueryType = void 0;
const protocol_1 = require("@streamr/protocol");
const utils_1 = require("@streamr/utils");
const rxjs_1 = require("rxjs");
const tsyringe_1 = require("tsyringe");
const Config_1 = require("./Config");
const HttpUtil_1 = require("./HttpUtil");
const LogStoreMessageStream_1 = require("./LogStoreMessageStream");
const NodeManager_1 = require("./registry/NodeManager");
const LoggerFactory_1 = require("./streamr/LoggerFactory");
const StreamrClientError_1 = require("./streamr/StreamrClientError");
const MessagePipelineFactory_1 = require("./streamr/subscribe/MessagePipelineFactory");
const GeneratorUtils_1 = require("./streamr/utils/GeneratorUtils");
const PushBuffer_1 = require("./streamr/utils/PushBuffer");
const validateNetworkResponses_1 = require("./utils/networkValidation/validateNetworkResponses");
const systemStreamUtils_1 = require("./utils/systemStreamUtils");
const MIN_SEQUENCE_NUMBER_VALUE = 0;
var QueryType;
(function (QueryType) {
    QueryType["Last"] = "last";
    QueryType["From"] = "from";
    QueryType["Range"] = "range";
})(QueryType || (exports.QueryType = QueryType = {}));
function isQueryLast(options) {
    return (options &&
        typeof options === 'object' &&
        'last' in options &&
        options.last != null);
}
function isQueryFrom(options) {
    return (options &&
        typeof options === 'object' &&
        'from' in options &&
        !('to' in options) &&
        options.from != null);
}
function isQueryRange(options) {
    return (options &&
        typeof options === 'object' &&
        'from' in options &&
        'to' in options &&
        options.to &&
        options.from != null);
}
const getHttpErrorTransform = () => {
    return async (err) => {
        let message;
        if (err instanceof HttpUtil_1.FetchHttpStreamResponseError) {
            const body = await err.response.text();
            let descriptionSnippet;
            try {
                const json = JSON.parse(body);
                descriptionSnippet = `: ${json.error}`;
            }
            catch {
                descriptionSnippet = '';
            }
            message = `Storage node fetch failed${descriptionSnippet}, httpStatus=${err.response.status}, url=${err.response.url}`;
        }
        else {
            message = err?.message ?? 'Unknown error';
        }
        return new StreamrClientError_1.StreamrClientError(message, 'STORAGE_NODE_ERROR');
    };
};
let Queries = class Queries {
    constructor(nodeManager, httpUtil, logStoreClientConfig, loggerFactory, systemMessages$, messagePipelineFactory) {
        this.logStoreClientConfig = logStoreClientConfig;
        this.systemMessages$ = systemMessages$;
        this.nodeManager = nodeManager;
        this.httpUtil = httpUtil;
        this.logger = loggerFactory.createLogger(module);
        this.messagePipelineFactory = messagePipelineFactory;
    }
    async query(streamPartId, input, options) {
        if (isQueryLast(input)) {
            if (input.last <= 0) {
                const emptyStream = new PushBuffer_1.PushBuffer();
                return new LogStoreMessageStream_1.LogStoreMessageStream(emptyStream, rxjs_1.EMPTY);
            }
            return this.fetchStream(QueryType.Last, streamPartId, {
                count: input.last,
            }, options);
        }
        else if (isQueryRange(input)) {
            return this.fetchStream(QueryType.Range, streamPartId, {
                fromTimestamp: new Date(input.from.timestamp).getTime(),
                fromSequenceNumber: input.from.sequenceNumber,
                toTimestamp: new Date(input.to.timestamp).getTime(),
                toSequenceNumber: input.to.sequenceNumber,
                publisherId: input.publisherId !== undefined
                    ? (0, utils_1.toEthereumAddress)(input.publisherId)
                    : undefined,
                msgChainId: input.msgChainId,
            }, options);
        }
        else if (isQueryFrom(input)) {
            return this.fetchStream(QueryType.From, streamPartId, {
                fromTimestamp: new Date(input.from.timestamp).getTime(),
                fromSequenceNumber: input.from.sequenceNumber,
                publisherId: input.publisherId !== undefined
                    ? (0, utils_1.toEthereumAddress)(input.publisherId)
                    : undefined,
            }, options);
        }
        else {
            throw new StreamrClientError_1.StreamrClientError(`can not query without valid query options: ${JSON.stringify({
                streamPartId,
                options: input,
            })}`, 'INVALID_ARGUMENT');
        }
    }
    async fetchStream(queryType, streamPartId, query, options) {
        const traceId = (0, utils_1.randomString)(5);
        this.logger.debug('Fetch query data', {
            loggerIdx: traceId,
            queryType,
            streamPartId,
            query,
        });
        // TODO: make it more robust, dealing with staleness of first best url
        const nodeUrl = this.logStoreClientConfig.nodeUrl ??
            (await this.nodeManager.getNodeUrlsByLatency())[0]; // first node is the best node
        const url = this.createUrl(nodeUrl, queryType, streamPartId, {
            ...query,
            // we will get raw request to desserialize and decrypt
            format: 'raw',
            verifyNetworkResponses: !!options?.verifyNetworkResponses,
        });
        const messageStream = options?.raw
            ? new PushBuffer_1.PushBuffer()
            : this.messagePipelineFactory.createMessagePipeline({ streamPartId });
        const lines = (0, GeneratorUtils_1.transformError)(this.httpUtil.fetchHttpStream(url, options?.abortSignal), getHttpErrorTransform());
        const dataStream = (0, rxjs_1.defer)(() => lines).pipe((0, rxjs_1.shareReplay)({
            refCount: true,
        }), (0, rxjs_1.map)((line) => line.trim()), // remove ' ' and '\n' from the end of the line
        (0, rxjs_1.filter)(Boolean), // remove empty lines
        (0, rxjs_1.map)((line) => {
            const msgObject = JSON.parse(line);
            if (Array.isArray(msgObject)) {
                if (options?.raw) {
                    return line;
                }
                return protocol_1.StreamMessage.deserialize(msgObject);
            }
            // last message must be metadata
            if (msgObject.type === 'metadata') {
                return msgObject;
            }
            throw new Error('Invalid message');
        }));
        const isStreamMessage = (msg) => msg instanceof protocol_1.StreamMessage || typeof msg === 'string';
        const [messagesSource, metadataSource] = (0, rxjs_1.partition)(dataStream, isStreamMessage);
        setImmediate(async () => {
            await (0, PushBuffer_1.pull)(messagesSource, messageStream);
        });
        const logStoreMessageStream = new LogStoreMessageStream_1.LogStoreMessageStream(messageStream, metadataSource);
        if (options?.verifyNetworkResponses) {
            await (0, validateNetworkResponses_1.validateWithNetworkResponses)({
                queryInput: {
                    query,
                    queryType,
                    streamPartId,
                },
                responseStream: logStoreMessageStream,
                systemMessages$: this.systemMessages$,
                nodeManager: this.nodeManager,
                logger: this.logger,
                queryUrl: nodeUrl,
                verificationOptions: typeof options.verifyNetworkResponses === 'object'
                    ? options.verifyNetworkResponses
                    : undefined,
            });
        }
        return logStoreMessageStream;
    }
    createUrl(baseUrl, endpointSuffix, streamPartId, query = {}) {
        const [streamId, streamPartition] = protocol_1.StreamPartIDUtils.getStreamIDAndPartition(streamPartId);
        const queryString = this.httpUtil.createQueryString(query);
        return `${baseUrl}/stores/${encodeURIComponent(streamId)}/data/partitions/${streamPartition}/${endpointSuffix}?${queryString}`;
    }
    getAuth() {
        return this.httpUtil.fetchAuthParams();
    }
};
exports.Queries = Queries;
exports.Queries = Queries = __decorate([
    (0, tsyringe_1.scoped)(tsyringe_1.Lifecycle.ContainerScoped),
    __param(0, (0, tsyringe_1.inject)(NodeManager_1.NodeManager)),
    __param(1, (0, tsyringe_1.inject)(HttpUtil_1.HttpUtil)),
    __param(2, (0, tsyringe_1.inject)(Config_1.LogStoreClientConfigInjectionToken)),
    __param(3, (0, tsyringe_1.inject)(LoggerFactory_1.LoggerFactoryInjectionToken)),
    __param(4, (0, tsyringe_1.inject)(systemStreamUtils_1.LogStoreClientSystemMessagesInjectionToken)),
    __param(5, (0, tsyringe_1.inject)(MessagePipelineFactory_1.MessagePipelineFactoryInjectionToken)),
    __metadata("design:paramtypes", [NodeManager_1.NodeManager,
        HttpUtil_1.HttpUtil, Object, Object, Object, Object])
], Queries);
//# sourceMappingURL=Queries.js.map