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());
    });
};
import { SQSClient, ReceiveMessageCommand, DeleteMessageBatchCommand } from "@aws-sdk/client-sqs";
import { fromNodeProviderChain } from "@aws-sdk/credential-providers";
import { Deserialize } from "../message";
import { NewHandleResult } from "./handler";
import { v4 as uuidv4 } from 'uuid';
import _ from "lodash";
import { FatalError, isRetriable } from "../error";
import { sqsTracing } from "../tracing";
import { newContext } from "../context";
import { newTracing } from "../tracing";
import { getLogger, LOGGER_LEVEL_ENV, newLogger, withLogger } from "../logger";
export 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 = newLogger({
                level: process.env[LOGGER_LEVEL_ENV],
                service: "charon",
                subject: p.Id(),
                tracingId: newTracing(),
            });
            let ctx = newContext();
            ctx = withLogger(ctx, logger);
            p.start(ctx);
        });
    }
}
export class Poller {
    constructor(id, config, handler) {
        this.Id = () => {
            return `POLLER-${this.id}`;
        };
        this.setMiddlewares = (middlewares) => {
            this.middlewares = middlewares;
        };
        this.start = (ctx) => {
            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 = 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 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 = sqsTracing(newContext(), message);
                for (const middleware of this.middlewares) {
                    ctxx = middleware(ctxx, message);
                }
                const messageInputData = Deserialize(message.Body);
                const receipt = message.ReceiptHandle;
                if (messageInputData instanceof Error) {
                    results.push(NewHandleResult(receipt, undefined, new 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 = _.filter(results, (r) => (!r.error || !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: _.map(chunk, (r) => ({ Id: uuidv4(), ReceiptHandle: r.messageReceipt })),
                };
                try {
                    yield this.client.send(new DeleteMessageBatchCommand(params));
                }
                catch (err) {
                    getLogger(ctx).withError(err).error("Error deleting messages");
                }
            }
        });
        this.id = id;
        this.handler = handler;
        this.config = config;
        this.client = new SQSClient({
            credentials: fromNodeProviderChain(),
            region: config.region
        });
        this.failedCount = 0;
        this.middlewares = [];
    }
}
//# sourceMappingURL=poller.js.map