import { v4 as uuid } from 'uuid';
import { getMobiusSocketInstance } from '../../mobius-socket';
import log from '../../Logger';
import { deriveMobiusSocketMessageType, isSupplementaryServiceMessageType, } from './mobiusSocketMapper';
import { MOBIUS_SOCKET_MESSAGE_TYPE } from './constants';
import { isMobiusWssEnabled } from './wsFeatureFlag';
import { CALLING_USER_AGENT, METHODS, REQUEST_FILE } from '../constants';
import { getMetricManager } from '../../Metrics';
import { METRIC_EVENT, METRIC_TYPE, MOBIUS_SOCKET_ACTION } from '../../Metrics/types';
function normalizeWsResponse(wsResponse) {
    return {
        statusCode: wsResponse.statusCode,
        body: wsResponse.data ?? undefined,
        headers: {
            trackingid: wsResponse.trackingId,
            ...(wsResponse.metadata ?? {}),
        },
    };
}
function normalizeWsError(err) {
    const wsErr = err;
    return {
        statusCode: wsErr.statusCode,
        body: wsErr.response?.data ?? undefined,
        headers: {
            trackingid: wsErr.trackingId ?? wsErr.response?.trackingId ?? '',
            ...(wsErr.response?.metadata ?? {}),
        },
    };
}
export class APIRequest {
    static instance;
    isMobiusSocketEnabled;
    webex;
    mobiusSocket;
    metricManager;
    static getInstance(config) {
        if (!APIRequest.instance) {
            APIRequest.instance = new APIRequest(config);
        }
        return APIRequest.instance;
    }
    static resetInstance() {
        APIRequest.instance = undefined;
    }
    constructor(config) {
        const logContext = {
            file: REQUEST_FILE,
            method: METHODS.CONSTRUCTOR,
        };
        if (!config.webex) {
            log.error('APIRequest instantiation failed: WebexSDK instance is required', logContext);
            throw new Error('WebexSDK instance is required');
        }
        this.webex = config.webex;
        this.isMobiusSocketEnabled = isMobiusWssEnabled(config.webex) || false;
        this.mobiusSocket = getMobiusSocketInstance(this.webex);
        this.metricManager = getMetricManager(this.webex);
        log.info(`APIRequest initialized with transport: ${this.isMobiusSocketEnabled ? 'WSS' : 'HTTP'}`, logContext);
    }
    isSocketEnabled() {
        return this.isMobiusSocketEnabled;
    }
    async connectToMobiusSocket(wssUrl) {
        const logContext = {
            file: REQUEST_FILE,
            method: METHODS.CONNECT_TO_MOBIUS_SOCKET,
        };
        if (this.mobiusSocket.isConnected()) {
            log.info('Mobius WebSocket already connected', logContext);
            return this.mobiusSocket.getConnectedWebSocketUrl();
        }
        log.info('Mobius WebSocket not connected, initiating connection', logContext);
        try {
            await this.mobiusSocket.connect(wssUrl);
            log.log('Mobius WebSocket connected successfully', logContext);
            this.metricManager?.submitMobiusSocketMetric(METRIC_EVENT.MOBIUS_SOCKET, MOBIUS_SOCKET_ACTION.CONNECT, METRIC_TYPE.BEHAVIORAL, wssUrl);
            return wssUrl;
        }
        catch (err) {
            log.warn(`Mobius WebSocket connection failed: ${String(err)}`, logContext);
            this.metricManager?.submitMobiusSocketMetric(METRIC_EVENT.MOBIUS_SOCKET_ERROR, MOBIUS_SOCKET_ACTION.CONNECT, METRIC_TYPE.BEHAVIORAL, wssUrl, undefined, String(err));
            throw normalizeWsError(err);
        }
    }
    getConnectedWebSocketUrl() {
        return this.mobiusSocket.getConnectedWebSocketUrl();
    }
    async disconnectFromMobiusSocket(options) {
        const logContext = {
            file: REQUEST_FILE,
            method: METHODS.DISCONNECT_FROM_MOBIUS_SOCKET,
        };
        log.info('Disconnecting from Mobius WebSocket', logContext);
        const wssUrl = this.mobiusSocket.getConnectedWebSocketUrl();
        try {
            await this.mobiusSocket.disconnect(options);
            log.log('Mobius WebSocket disconnected successfully', logContext);
            this.metricManager?.submitMobiusSocketMetric(METRIC_EVENT.MOBIUS_SOCKET, MOBIUS_SOCKET_ACTION.DISCONNECT, METRIC_TYPE.BEHAVIORAL, wssUrl);
        }
        catch (err) {
            log.warn(`Mobius WebSocket disconnection failed: ${String(err)}`, logContext);
            this.metricManager?.submitMobiusSocketMetric(METRIC_EVENT.MOBIUS_SOCKET_ERROR, MOBIUS_SOCKET_ACTION.DISCONNECT, METRIC_TYPE.BEHAVIORAL, wssUrl, undefined, String(err));
        }
    }
    async makeRequest(request) {
        const logContext = {
            file: REQUEST_FILE,
            method: METHODS.MAKE_REQUEST,
        };
        log.info(`Dispatching request via ${this.isMobiusSocketEnabled ? 'WSS' : 'HTTP'} `, logContext);
        if (this.isMobiusSocketEnabled) {
            const trackingId = `webex-js-sdk_${uuid()}`;
            const socketType = deriveMobiusSocketMessageType(request.uri, request.method);
            if (socketType === MOBIUS_SOCKET_MESSAGE_TYPE.UNKNOWN) {
                log.error(`Unknown Mobius Socket message type for uri: ${request.uri}, httpMethod: ${request.method}`, logContext);
                throw new Error(`Unknown Mobius Socket message type: ${socketType}`);
            }
            const isSupplementaryService = isSupplementaryServiceMessageType(socketType);
            try {
                const wsResponse = await this.mobiusSocket.sendWssRequest({
                    type: socketType,
                    trackingId,
                    metadata: {
                        ...request.headers,
                        userAgent: CALLING_USER_AGENT,
                        authorization: `${(isSupplementaryService && (await this.webex.credentials.getUserToken())) || ''}`,
                    },
                    data: request.body,
                });
                log.log(`WSS request succeeded - socketType: ${socketType}, trackingId: ${trackingId}, statusCode: ${wsResponse.statusCode}`, logContext);
                return normalizeWsResponse(wsResponse);
            }
            catch (err) {
                log.error(`WSS request failed - socketType: ${socketType}, trackingId: ${trackingId}, error: ${String(err)}`, logContext);
                throw normalizeWsError(err);
            }
        }
        return this.webex.request(request);
    }
    registerMobiusSocketListener(cb) {
        const logContext = {
            file: REQUEST_FILE,
            method: METHODS.REGISTER_MOBIUS_SOCKET_LISTENER,
        };
        log.info('Attaching Mobius async event listener', logContext);
        this.mobiusSocket.on('event:async_event', (data) => {
            log.trace(`Mobius async event received - eventType: ${data?.data?.eventType ?? 'unknown'}`, logContext);
            cb(data);
        });
        log.log('Mobius async event listener attached', logContext);
        this.metricManager?.submitMobiusSocketMetric(METRIC_EVENT.MOBIUS_SOCKET, MOBIUS_SOCKET_ACTION.LISTENER_REGISTERED, METRIC_TYPE.BEHAVIORAL);
    }
    unregisterMobiusSocketListener() {
        const logContext = {
            file: REQUEST_FILE,
            method: METHODS.UNREGISTER_MOBIUS_SOCKET_LISTENER,
        };
        log.info('Detaching Mobius async event listener', logContext);
        this.mobiusSocket.off('event:async_event');
        log.log('Mobius async event listener detached', logContext);
        this.metricManager?.submitMobiusSocketMetric(METRIC_EVENT.MOBIUS_SOCKET, MOBIUS_SOCKET_ACTION.LISTENER_UNREGISTERED, METRIC_TYPE.BEHAVIORAL);
    }
}
export const createAPIRequest = (config) => APIRequest.getInstance(config);
