"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());
    });
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Poller = exports.PollerPool = void 0;
const client_sqs_1 = require("@aws-sdk/client-sqs");
const credential_providers_1 = require("@aws-sdk/credential-providers");
const message_1 = require("../message");
const handler_1 = require("./handler");
const uuid_1 = require("uuid");
const lodash_1 = __importDefault(require("lodash"));
const error_1 = require("../error");
const tracing_1 = require("../tracing");
const context_1 = require("../context");
const tracing_2 = require("../tracing");
const logger_1 = require("../logger");
class PollerPool {
    constructor(config, handler) {
        this.useMiddlewares = (middlewares) => {
            for (const poller of this.pollers) {
                poller.setMiddlewares(middlewares);
            }
        };
        if (!config.maxConcurrency) {
            throw (new Error("PollerPool.maxConcurrency is 0"));
        }
        if (!config.maxMessages) {
            throw (new Error("PollerPool.maxMessages is 0"));
        }
        if (!config.pollerVisibilityTimeout) {
            config.pollerVisibilityTimeout = 20;
        }
        this.pollers = new Array();
        for (let i = 0; i < config.maxConcurrency; i++) {
            const pollerConfig = {
                region: config.region,
                endpoint: config.sqsEndpoint,
                visibilityTimeout: config.pollerVisibilityTimeout,
                sequentialMessageProcessig: config.sequentialMessageProcessig,
                maxMessages: config.maxMessages,
                messageAttributeNames: config.messageAttributeNames,
            };
            this.pollers.push(new Poller(`${i}`, pollerConfig, handler.spawn()));
        }
    }
    run() {
        this.pollers.forEach((p) => {
            const logger = (0, logger_1.newLogger)({
                level: process.env[logger_1.LOGGER_LEVEL_ENV],
                service: "charon",
                subject: p.Id(),
                tracingId: (0, tracing_2.newTracing)(),
            });
            let ctx = (0, context_1.newContext)();
            ctx = (0, logger_1.withLogger)(ctx, logger);
            p.start(ctx);
        });
    }
}
exports.PollerPool = PollerPool;
class Poller {
    constructor(id, config, handler) {
        this.Id = () => {
            return `POLLER-${this.id}`;
        };
        this.setMiddlewares = (middlewares) => {
            this.middlewares = middlewares;
        };
        this.start = (ctx) => {
            (0, logger_1.getLogger)(ctx).info(`sqs poller started`);
            return this.run(ctx);
        };
        this.run = (ctx) => {
            // eslint-disable-next-line @typescript-eslint/no-this-alias
            const that = this;
            Promise.resolve()
                .then(() => __awaiter(this, void 0, void 0, function* () { return (yield that.pull(ctx)); }))
                .catch(e => console.error(e))
                .then((success) => {
                if (!success) {
                    that.failedCount = Math.max(that.failedCount + 1, 20);
                }
                else {
                    that.failedCount = 0;
                }
                setTimeout(that.run, 1000 * that.failedCount);
            });
        };
        this.pull = (ctx) => __awaiter(this, void 0, void 0, function* () {
            const log = (0, logger_1.getLogger)(ctx);
            log.info(`polling`);
            const params = {
                MaxNumberOfMessages: this.config.maxMessages,
                MessageAttributeNames: this.config.messageAttributeNames,
                QueueUrl: this.config.endpoint,
                VisibilityTimeout: this.config.visibilityTimeout,
                WaitTimeSeconds: 20,
            };
            const response = yield this.client.send(new client_sqs_1.ReceiveMessageCommand(params))
                .catch(err => {
                log.withError(err).error("could not get SQS messages");
                return false;
            });
            if (!response)
                return false;
            log.with({ "count": (response.Messages || []).length }).debug(`got messages`);
            const results = [];
            const promises = [];
            for (const message of response.Messages || []) {
                // get tracing id and set in context
                // set tracing 
                let ctxx = (0, tracing_1.sqsTracing)((0, context_1.newContext)(), message);
                for (const middleware of this.middlewares) {
                    ctxx = middleware(ctxx, message);
                }
                const messageInputData = (0, message_1.Deserialize)(message.Body);
                const receipt = message.ReceiptHandle;
                if (messageInputData instanceof Error) {
                    results.push((0, handler_1.NewHandleResult)(receipt, undefined, new error_1.FatalError(messageInputData)));
                    continue;
                }
                promises.push(this.handler.handle(ctxx, receipt, messageInputData)
                    .then((result) => {
                    results.push(result);
                }).catch((e) => {
                    log.withError(e).error("Fatal error handling message");
                }));
                if (this.config.sequentialMessageProcessig) {
                    yield Promise.all(promises);
                }
            }
            yield Promise.all(promises);
            this.ackResults(ctx, results);
            return true;
        });
        this.ackResults = (ctx, results) => __awaiter(this, void 0, void 0, function* () {
            const chunkSize = 10;
            const aknowledgable = lodash_1.default.filter(results, (r) => (!r.error || !(0, error_1.isRetriable)(r.error)));
            for (let i = 0; i < aknowledgable.length; i += chunkSize) {
                const chunk = aknowledgable.slice(i, i + chunkSize);
                // Set the parameters
                const params = {
                    QueueUrl: this.config.endpoint,
                    Entries: lodash_1.default.map(chunk, (r) => ({ Id: (0, uuid_1.v4)(), ReceiptHandle: r.messageReceipt })),
                };
                try {
                    yield this.client.send(new client_sqs_1.DeleteMessageBatchCommand(params));
                }
                catch (err) {
                    (0, logger_1.getLogger)(ctx).withError(err).error("Error deleting messages");
                }
            }
        });
        this.id = id;
        this.handler = handler;
        this.config = config;
        this.client = new client_sqs_1.SQSClient({
            credentials: (0, credential_providers_1.fromNodeProviderChain)(),
            region: config.region
        });
        this.failedCount = 0;
        this.middlewares = [];
    }
}
exports.Poller = Poller;
//# sourceMappingURL=poller.js.map