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);
};
import IO from "../Infrastructure/SocketIO";
import logger from "./Logger";
import BadDataException from "../../Types/Exception/BadDataException";
import ObjectID from "../../Types/ObjectID";
import RealtimeUtil from "../../Utils/Realtime";
import JSONWebToken from "./JsonWebToken";
import { getModelTypeByName } from "../../Models/DatabaseModels/Index";
import { getModelTypeByName as getAnalyticsModelTypeByname } from "../../Models/AnalyticsModels/Index";
import ModelPermission from "../../Types/BaseDatabase/ModelPermission";
import ModelEventType from "../../Types/Realtime/ModelEventType";
import EventName from "../../Types/Realtime/EventName";
import CookieUtil from "./Cookie";
import UserPermissionUtil from "./UserPermission/UserPermission";
import CaptureSpan from "./Telemetry/CaptureSpan";
class Realtime {
    static isInitialized() {
        logger.debug("Checking if socket server is initialized");
        const isInitialized = this.socketServer !== null;
        logger.debug(`Socket server is initialized: ${isInitialized}`);
        return isInitialized;
    }
    static async init() {
        if (!this.socketServer) {
            logger.debug("Initializing socket server");
            this.socketServer = IO.getSocketServer();
            logger.debug("Realtime socket server initialized");
            this.socketServer.on("connection", (socket) => {
                logger.debug("New socket connection established");
                socket.on(EventName.ListenToModalEvent, async (data) => {
                    var _a;
                    logger.debug("Received ListenToModalEvent with data:");
                    logger.debug(data);
                    const socketLogAttributes = {
                        projectId: (_a = data["tenantId"]) === null || _a === void 0 ? void 0 : _a.toString(),
                    };
                    if (typeof data["eventType"] !== "string") {
                        logger.error("eventType is not a string", socketLogAttributes);
                        throw new BadDataException("eventType is not a string");
                    }
                    if (typeof data["modelType"] !== "string") {
                        logger.error("modelType is not a string", socketLogAttributes);
                        throw new BadDataException("modelType is not a string");
                    }
                    if (typeof data["modelName"] !== "string") {
                        logger.error("modelName is not a string", socketLogAttributes);
                        throw new BadDataException("modelName is not a string");
                    }
                    if (typeof data["tenantId"] !== "string") {
                        logger.error("tenantId is not a string", socketLogAttributes);
                        throw new BadDataException("tenantId is not a string");
                    }
                    await Realtime.listenToModelEvent(socket, {
                        eventType: data["eventType"],
                        modelType: data["modelType"],
                        modelName: data["modelName"],
                        tenantId: data["tenantId"],
                    });
                });
            });
        }
        return this.socketServer;
    }
    static async listenToModelEvent(socket, data) {
        var _a;
        const listenLogAttributes = {
            projectId: (_a = data.tenantId) === null || _a === void 0 ? void 0 : _a.toString(),
        };
        logger.debug("Listening to model event with data:", listenLogAttributes);
        logger.debug(data, listenLogAttributes);
        if (!this.socketServer) {
            logger.debug("Socket server not initialized, initializing now", listenLogAttributes);
            await this.init();
        }
        /*
         * before joining room check the user token and check if the user has access to this tenant
         * and to this model and to this event type
         */
        logger.debug("Extracting user access token from socket", listenLogAttributes);
        const userAccessToken = this.getAccessTokenFromSocket(socket);
        if (!userAccessToken) {
            logger.debug("User access token not found in socket, aborting joining room", listenLogAttributes);
            return;
        }
        logger.debug("Decoding user access token", listenLogAttributes);
        const userAuthorizationData = JSONWebToken.decode(userAccessToken);
        if (!userAuthorizationData) {
            logger.debug("User authorization data not found in socket, aborting joining room", listenLogAttributes);
            return;
        }
        if (!userAuthorizationData.userId) {
            logger.debug("User ID not found in socket, aborting joining room", listenLogAttributes);
            return;
        }
        logger.debug("Checking user access permissions", listenLogAttributes);
        let hasAccess = false;
        if (userAuthorizationData.isMasterAdmin) {
            logger.debug("User is a master admin, granting access", listenLogAttributes);
            hasAccess = true;
        }
        logger.debug("Fetching user global access permissions", listenLogAttributes);
        const userGlobalAccessPermission = await UserPermissionUtil.getUserGlobalAccessPermissionFromCache(userAuthorizationData.userId);
        // check if the user has access to this tenant
        if (userGlobalAccessPermission && !hasAccess) {
            logger.debug("Checking if user has access to the tenant", listenLogAttributes);
            const hasAccessToProjectId = userGlobalAccessPermission.projectIds.some((projectId) => {
                return projectId.toString() === data.tenantId.toString();
            });
            if (!hasAccessToProjectId) {
                logger.debug("User does not have access to this tenant, aborting joining room", listenLogAttributes);
                return;
            }
            logger.debug("User has access to the tenant, checking model access", listenLogAttributes);
            const userId = new ObjectID(userAuthorizationData.userId.toString());
            const projectId = new ObjectID(data.tenantId.toString());
            // if it has the access to the tenant, check if it has access to the model
            const userTenantAccessPermission = await UserPermissionUtil.getUserTenantAccessPermissionFromCache(userId, projectId);
            // check if the user has access to this model
            if (userTenantAccessPermission &&
                this.hasPermissionsByModelName(userTenantAccessPermission, data.modelName)) {
                logger.debug("User has access to the model, granting access", listenLogAttributes);
                hasAccess = true;
            }
        }
        if (!hasAccess) {
            logger.debug("User does not have access to this tenant, aborting joining room", listenLogAttributes);
            return;
        }
        if (data.modelId) {
            const modelRoomId = RealtimeUtil.getRoomId(data.tenantId, data.modelName, ModelEventType.Create, data.modelId);
            logger.debug(`Joining room with ID: ${modelRoomId}`, listenLogAttributes);
            // join the room.
            await socket.join(modelRoomId);
        }
        else {
            const roomId = RealtimeUtil.getRoomId(data.tenantId, data.modelName, data.eventType);
            logger.debug(`Joining room with ID: ${roomId}`, listenLogAttributes);
            // join the room.
            await socket.join(roomId);
        }
    }
    static async stopListeningToModelEvent(socket, data) {
        var _a;
        const stopLogAttributes = {
            projectId: (_a = data.tenantId) === null || _a === void 0 ? void 0 : _a.toString(),
        };
        logger.debug("Stopping listening to model event with data:", stopLogAttributes);
        logger.debug(data, stopLogAttributes);
        if (!this.socketServer) {
            logger.debug("Socket server not initialized, initializing now", stopLogAttributes);
            await this.init();
        }
        const roomId = RealtimeUtil.getRoomId(data.tenantId, data.modelName, data.eventType, data.modelId);
        logger.debug(`Leaving room with ID: ${roomId}`, stopLogAttributes);
        // leave this room.
        await socket.leave(roomId);
    }
    static async emitModelEvent(data) {
        var _a;
        const emitLogAttributes = {
            projectId: (_a = data.tenantId) === null || _a === void 0 ? void 0 : _a.toString(),
        };
        logger.debug("Emitting model event with data:", emitLogAttributes);
        logger.debug(`Tenant ID: ${data.tenantId}`, emitLogAttributes);
        logger.debug(`Event Type: ${data.eventType}`, emitLogAttributes);
        logger.debug(`Model ID: ${data.modelId}`, emitLogAttributes);
        if (!this.socketServer) {
            logger.debug("Socket server not initialized, initializing now", emitLogAttributes);
            await this.init();
        }
        const jsonObject = {
            modelId: data.modelId.toString(),
        };
        const model = new data.modelType();
        if (!model.tableName) {
            logger.warn("Model does not have a tableName, aborting emit", emitLogAttributes);
            return;
        }
        const roomId = RealtimeUtil.getRoomId(data.tenantId, model.tableName, data.eventType);
        const modelRoomId = RealtimeUtil.getRoomId(data.tenantId, model.tableName, ModelEventType.Create, data.modelId);
        logger.debug(`Emitting event to room with ID: ${roomId}`, emitLogAttributes);
        logger.debug(jsonObject, emitLogAttributes);
        this.socketServer.to(roomId).emit(roomId, jsonObject);
        this.socketServer.to(modelRoomId).emit(modelRoomId, jsonObject);
    }
    static hasPermissionsByModelName(userProjectPermissions, modelName) {
        let modelPermissions = [];
        let modelType = getModelTypeByName(modelName);
        if (!modelType) {
            // check if it is an analytics model
            modelType = getAnalyticsModelTypeByname(modelName);
            if (!modelType) {
                return false;
            }
        }
        modelPermissions = new modelType().getReadPermissions();
        return ModelPermission.hasPermissions(userProjectPermissions, modelPermissions);
    }
    static getAccessTokenFromSocket(socket) {
        let accessToken = undefined;
        if (socket.handshake.headers.cookie) {
            const cookies = CookieUtil.getCookiesFromCookieString(socket.handshake.headers.cookie);
            if (cookies[CookieUtil.getUserTokenKey()]) {
                accessToken = cookies[CookieUtil.getUserTokenKey()];
            }
        }
        return accessToken;
    }
}
Realtime.socketServer = null;
export default Realtime;
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", []),
    __metadata("design:returntype", Boolean)
], Realtime, "isInitialized", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", []),
    __metadata("design:returntype", Promise)
], Realtime, "init", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object, Object]),
    __metadata("design:returntype", Promise)
], Realtime, "listenToModelEvent", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object, Object]),
    __metadata("design:returntype", Promise)
], Realtime, "stopListeningToModelEvent", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], Realtime, "emitModelEvent", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object, String]),
    __metadata("design:returntype", Boolean)
], Realtime, "hasPermissionsByModelName", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Object)
], Realtime, "getAccessTokenFromSocket", null);
//# sourceMappingURL=Realtime.js.map