"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.makeStartSession = void 0;
const abort_controller_1 = require("abort-controller");
const buffer_1 = require("buffer");
const tunnel_client_1 = require("@applitools/tunnel-client");
const utils = __importStar(require("@applitools/utils"));
const SERVER_URLS = {
    'us-west': 'https://exec-wus.applitools.com',
    australia: 'https://exec-au.applitools.com',
};
const RETRY_BACKOFF = [
    ...Array(5).fill(2000),
    ...Array(4).fill(5000),
    10000, // all next tries with delay 10s
];
function makeStartSession({ settings, req, tunnels }) {
    const queues = new Map();
    return async function createSession({ request, response, logger, }) {
        var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
        const requestBody = await utils.streams.toJSON(request);
        logger.log(`Request was intercepted with body:`, requestBody);
        let capabilities = {};
        if (!utils.types.isEmpty(requestBody.desiredCapabilities)) {
            capabilities = requestBody.desiredCapabilities;
        }
        else if (!utils.types.isEmpty((_a = requestBody.capabilities) === null || _a === void 0 ? void 0 : _a.alwaysMatch)) {
            capabilities = requestBody.capabilities.alwaysMatch;
        }
        else if (!utils.types.isEmpty((_c = (_b = requestBody.capabilities) === null || _b === void 0 ? void 0 : _b.firstMatch) === null || _c === void 0 ? void 0 : _c[0])) {
            capabilities = (_e = (_d = requestBody.capabilities) === null || _d === void 0 ? void 0 : _d.firstMatch) === null || _e === void 0 ? void 0 : _e[0];
        }
        const options = {
            ...settings.options,
            ...capabilities === null || capabilities === void 0 ? void 0 : capabilities['applitools:options'],
            ...(capabilities &&
                Object.fromEntries(Object.entries(capabilities).map(([key, value]) => [key.replace(/^applitools:/, ''), value]))),
        };
        const session = {
            ecServerUrl: settings.ecServerUrl,
            proxy: settings.proxy,
            credentials: { eyesServerUrl: options.eyesServerUrl, apiKey: options.apiKey },
            options,
        };
        if (options.region) {
            if (SERVER_URLS[options.region])
                session.ecServerUrl = SERVER_URLS[options.region];
            else
                throw new Error(`Failed to create session in unknown region ${options.region}`);
        }
        if (options.tunnel && tunnels) {
            // TODO should be removed once tunnel spawning issue is solved
            await (0, tunnel_client_1.prepareTunnelEnvironment)({ settings: { tunnelServerUrl: session.ecServerUrl }, logger });
            session.tunnels = await tunnels.acquire({ ...session.credentials, tunnelServerUrl: session.ecServerUrl });
        }
        const applitoolsCapabilities = Object.fromEntries([
            ...Object.entries(options).map(([key, value]) => [`applitools:${key}`, value]),
            ...((_g = (_f = session.tunnels) === null || _f === void 0 ? void 0 : _f.map((tunnel, index) => [`applitools:x-tunnel-id-${index}`, tunnel.tunnelId])) !== null && _g !== void 0 ? _g : []),
        ]);
        if (!utils.types.isEmpty(requestBody.desiredCapabilities)) {
            requestBody.desiredCapabilities = { ...requestBody.desiredCapabilities, ...applitoolsCapabilities };
        }
        else if (!utils.types.isEmpty((_h = requestBody.capabilities) === null || _h === void 0 ? void 0 : _h.alwaysMatch)) {
            requestBody.capabilities.alwaysMatch = { ...(_j = requestBody.capabilities) === null || _j === void 0 ? void 0 : _j.alwaysMatch, ...applitoolsCapabilities };
        }
        else if (!utils.types.isEmpty((_l = (_k = requestBody.capabilities) === null || _k === void 0 ? void 0 : _k.firstMatch) === null || _l === void 0 ? void 0 : _l[0])) {
            requestBody.capabilities.firstMatch = [{ ...requestBody.capabilities.firstMatch[0], ...applitoolsCapabilities }];
        }
        else {
            requestBody.desiredCapabilities = { ...applitoolsCapabilities };
        }
        logger.log('Request body has modified:', requestBody);
        const queueKey = JSON.stringify(session.credentials);
        let queue = queues.get(queueKey);
        if (!queue) {
            queue = utils.queues.makeCorkableQueue({
                makeAbortController: () => new abort_controller_1.AbortController(),
            });
            queues.set(queueKey, queue);
        }
        request.socket.on('close', () => queue.cancel(task));
        return queue.run(task);
        async function task(signal, attempt = 1) {
            var _a, _b, _c;
            var _d;
            // do not start the task if it is already aborted
            if (signal.aborted)
                return queue.pause;
            const proxyResponse = await req(request.url, {
                baseUrl: session.ecServerUrl,
                body: requestBody,
                io: { request, response, handle: false },
                // TODO uncomment when we can throw different abort reasons for task cancelation and timeout abortion
                // signal,
                logger,
            });
            const responseBody = await proxyResponse.json();
            logger.log(`Response was intercepted with body:`, responseBody);
            if (['CONCURRENCY_LIMIT_REACHED', 'NO_AVAILABLE_DRIVER_POD'].includes((_b = (_a = responseBody.value) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b.appliErrorCode)) {
                queue.cork();
                // after query is corked the task might be aborted
                if (signal.aborted)
                    return queue.pause;
                await utils.general.sleep(RETRY_BACKOFF[Math.min(attempt, RETRY_BACKOFF.length - 1)]);
                logger.log(`Attempt (${attempt}) to create session was failed with applitools status code:`, responseBody.value.data.appliErrorCode);
                return task(signal, attempt + 1);
            }
            else {
                queue.uncork();
                if (responseBody.value) {
                    (_c = (_d = responseBody.value).capabilities) !== null && _c !== void 0 ? _c : (_d.capabilities = {});
                    responseBody.value.capabilities['applitools:isECClient'] = true;
                    if (proxyResponse.headers.has('content-length')) {
                        proxyResponse.headers.set('content-length', buffer_1.Buffer.byteLength(JSON.stringify(responseBody)).toString());
                    }
                    session.sessionId = responseBody.value.sessionId;
                    session.capabilities = responseBody.value.capabilities;
                }
                response
                    .writeHead(proxyResponse.status, Object.fromEntries(proxyResponse.headers.entries()))
                    .end(JSON.stringify(responseBody));
                return session;
            }
        }
    };
}
exports.makeStartSession = makeStartSession;
