import express from 'express';
import { CompositeKey, EntityDeleteOptions, LogError, Metadata } from '@memberjunction/core';
import { EntityCRUDHandler } from './EntityCRUDHandler.js';
import { ViewOperationsHandler } from './ViewOperationsHandler.js';
export class RESTEndpointHandler {
    router;
    options;
    constructor(options = {}) {
        this.router = express.Router();
        this.options = options;
        this.setupRoutes();
    }
    isEntityAllowed(entityName) {
        const name = entityName.toLowerCase();
        const md = new Metadata();
        const entity = md.Entities.find(e => e.Name.toLowerCase() === name);
        if (!entity) {
            return false;
        }
        const schemaName = entity.SchemaName.toLowerCase();
        if (this.options.excludeSchemas && this.options.excludeSchemas.length > 0) {
            if (this.options.excludeSchemas.some(schema => schema.toLowerCase() === schemaName)) {
                return false;
            }
        }
        if (this.options.excludeEntities && this.options.excludeEntities.length > 0) {
            if (this.options.excludeEntities.includes(name)) {
                return false;
            }
            for (const pattern of this.options.excludeEntities) {
                if (pattern.includes('*')) {
                    const regex = new RegExp('^' + pattern.toLowerCase().replace(/\*/g, '.*') + '$');
                    if (regex.test(name)) {
                        return false;
                    }
                }
            }
        }
        if (this.options.includeSchemas && this.options.includeSchemas.length > 0) {
            if (!this.options.includeSchemas.some(schema => schema.toLowerCase() === schemaName)) {
                return false;
            }
        }
        if (this.options.includeEntities && this.options.includeEntities.length > 0) {
            if (this.options.includeEntities.includes(name)) {
                return true;
            }
            for (const pattern of this.options.includeEntities) {
                if (pattern.includes('*')) {
                    const regex = new RegExp('^' + pattern.toLowerCase().replace(/\*/g, '.*') + '$');
                    if (regex.test(name)) {
                        return true;
                    }
                }
            }
            return false;
        }
        return true;
    }
    setupRoutes() {
        this.router.use(this.extractMJUser);
        this.router.use('/entities/:entityName', this.checkEntityAccess.bind(this));
        this.router.use('/views/:entityName', this.checkEntityAccess.bind(this));
        this.router.use('/metadata/entities/:entityName', this.checkEntityAccess.bind(this));
        this.router.use('/users/:userId/favorites/:entityName', this.checkEntityAccess.bind(this));
        this.router.get('/entities/:entityName', this.getEntityList.bind(this));
        this.router.post('/entities/:entityName', this.createEntity.bind(this));
        this.router.get('/entities/:entityName/:id', this.getEntity.bind(this));
        this.router.put('/entities/:entityName/:id', this.updateEntity.bind(this));
        this.router.delete('/entities/:entityName/:id', this.deleteEntity.bind(this));
        this.router.get('/entities/:entityName/:id/changes', this.getRecordChanges.bind(this));
        this.router.get('/entities/:entityName/:id/dependencies', this.getRecordDependencies.bind(this));
        this.router.get('/entities/:entityName/:id/name', this.getEntityRecordName.bind(this));
        this.router.post('/views/:entityName', this.runView.bind(this));
        this.router.post('/views/batch', this.runViews.bind(this));
        this.router.get('/views/entity', this.getViewEntity.bind(this));
        this.router.get('/metadata/entities', this.getEntityMetadata.bind(this));
        this.router.get('/metadata/entities/:entityName', this.getEntityFieldMetadata.bind(this));
        this.router.get('/views/:entityName/metadata', this.getViewsMetadata.bind(this));
        this.router.get('/users/current', this.getCurrentUser.bind(this));
        this.router.get('/users/:userId/favorites/:entityName/:id', this.getRecordFavoriteStatus.bind(this));
        this.router.put('/users/:userId/favorites/:entityName/:id', this.setRecordFavoriteStatus.bind(this));
        this.router.delete('/users/:userId/favorites/:entityName/:id', this.removeRecordFavoriteStatus.bind(this));
        this.router.post('/transactions', this.executeTransaction.bind(this));
        this.router.get('/reports/:reportId', this.runReport.bind(this));
        this.router.post('/queries/run', this.runQuery.bind(this));
        this.router.use(this.errorHandler);
    }
    checkEntityAccess(req, res, next) {
        const entityName = req.params.entityName;
        if (!entityName) {
            next();
            return;
        }
        if (!this.isEntityAllowed(entityName)) {
            res.status(403).json({
                error: `Access to entity '${entityName}' is not allowed through the REST API`,
                details: 'This entity is either not included in the allowlist or is explicitly excluded in the REST API configuration'
            });
            return;
        }
        next();
    }
    async extractMJUser(req, res, next) {
        try {
            if (req['user']) {
                const md = new Metadata();
                const userInfo = req['user'];
                req['mjUser'] = userInfo;
                if (!req['mjUser']) {
                    res.status(401).json({ error: 'User not found in MemberJunction' });
                    return;
                }
            }
            else {
                res.status(401).json({ error: 'Authentication required' });
                return;
            }
            next();
        }
        catch (error) {
            next(error);
        }
    }
    errorHandler(err, req, res, next) {
        LogError(err);
        if (err.name === 'UnauthorizedError') {
            res.status(401).json({ error: 'Invalid token' });
            return;
        }
        res.status(500).json({ error: err?.message || 'Internal server error' });
    }
    async getCurrentUser(req, res) {
        try {
            const user = req['mjUser'];
            res.json({
                ID: user.ID,
                Name: user.Name,
                Email: user.Email,
                FirstName: user.FirstName,
                LastName: user.LastName,
                IsAdmin: user.IsAdmin,
                UserRoles: user.UserRoles.map(role => ({
                    ID: role.ID,
                    Name: role.Name,
                    Description: role.Description
                }))
            });
        }
        catch (error) {
            LogError(error);
            res.status(500).json({ error: error?.message || 'Unknown error' });
        }
    }
    async getEntityList(req, res) {
        try {
            const { entityName } = req.params;
            const { filter, orderBy, fields, maxRows, startRow } = req.query;
            const user = req['mjUser'];
            const params = {
                EntityName: entityName,
                ExtraFilter: filter,
                OrderBy: orderBy,
                Fields: fields ? fields.split(',') : undefined,
                MaxRows: maxRows ? parseInt(maxRows) : undefined,
                StartRow: startRow ? parseInt(startRow) : undefined
            };
            const result = await ViewOperationsHandler.listEntities(params, user);
            res.json(result);
        }
        catch (error) {
            LogError(error);
            res.status(500).json({ error: error?.message || 'Unknown error' });
        }
    }
    async getEntity(req, res) {
        try {
            const { entityName, id } = req.params;
            const { include } = req.query;
            const user = req['mjUser'];
            const relatedEntities = include ? include.split(',') : null;
            const result = await EntityCRUDHandler.getEntity(entityName, id, relatedEntities, user);
            if (result.success) {
                res.json(result.entity);
            }
            else {
                res.status(result.error.includes('not found') ? 404 : 400).json({ error: result.error });
            }
        }
        catch (error) {
            LogError(error);
            res.status(500).json({ error: error?.message || 'Unknown error' });
        }
    }
    async createEntity(req, res) {
        try {
            const { entityName } = req.params;
            const entityData = req.body;
            const user = req['mjUser'];
            const result = await EntityCRUDHandler.createEntity(entityName, entityData, user);
            if (result.success) {
                res.status(201).json(result.entity);
            }
            else {
                res.status(400).json({
                    error: result.error,
                    details: result.details
                });
            }
        }
        catch (error) {
            LogError(error);
            res.status(500).json({ error: error?.message || 'Unknown error' });
        }
    }
    async updateEntity(req, res) {
        try {
            const { entityName, id } = req.params;
            const updateData = req.body;
            const user = req['mjUser'];
            const result = await EntityCRUDHandler.updateEntity(entityName, id, updateData, user);
            if (result.success) {
                res.json(result.entity);
            }
            else {
                res.status(result.error.includes('not found') ? 404 : 400).json({
                    error: result.error,
                    details: result.details
                });
            }
        }
        catch (error) {
            LogError(error);
            res.status(500).json({ error: error?.message || 'Unknown error' });
        }
    }
    async deleteEntity(req, res) {
        try {
            const { entityName, id } = req.params;
            const options = req.query.options ? JSON.parse(req.query.options) : {};
            const user = req['mjUser'];
            const deleteOptions = new EntityDeleteOptions();
            if (options.SkipEntityAIActions !== undefined)
                deleteOptions.SkipEntityAIActions = !!options.SkipEntityAIActions;
            if (options.SkipEntityActions !== undefined)
                deleteOptions.SkipEntityActions = !!options.SkipEntityActions;
            if (options.ReplayOnly !== undefined)
                deleteOptions.ReplayOnly = !!options.ReplayOnly;
            const result = await EntityCRUDHandler.deleteEntity(entityName, id, deleteOptions, user);
            if (result.success) {
                res.status(204).send();
            }
            else {
                res.status(result.error.includes('not found') ? 404 : 400).json({ error: result.error });
            }
        }
        catch (error) {
            LogError(error);
            res.status(500).json({ error: error?.message || 'Unknown error' });
        }
    }
    async getRecordChanges(req, res) {
        try {
            const { entityName, id } = req.params;
            const user = req['mjUser'];
            const md = new Metadata();
            const entity = await md.GetEntityObject(entityName, user);
            const compositeKey = this.createCompositeKey(entity.EntityInfo, id);
            const changes = [];
            res.json(changes);
        }
        catch (error) {
            LogError(error);
            res.status(500).json({ error: error?.message || 'Unknown error' });
        }
    }
    async getRecordDependencies(req, res) {
        try {
            const { entityName, id } = req.params;
            const user = req['mjUser'];
            const md = new Metadata();
            const entity = await md.GetEntityObject(entityName, user);
            const compositeKey = this.createCompositeKey(entity.EntityInfo, id);
            const dependencies = [];
            res.json(dependencies);
        }
        catch (error) {
            LogError(error);
            res.status(500).json({ error: error?.message || 'Unknown error' });
        }
    }
    async getEntityRecordName(req, res) {
        try {
            const { entityName, id } = req.params;
            const user = req['mjUser'];
            const md = new Metadata();
            const entity = await md.GetEntityObject(entityName, user);
            const compositeKey = this.createCompositeKey(entity.EntityInfo, id);
            const recordName = "Record Name";
            res.json({ recordName });
        }
        catch (error) {
            LogError(error);
            res.status(500).json({ error: error?.message || 'Unknown error' });
        }
    }
    async runView(req, res) {
        try {
            const { entityName } = req.params;
            const viewParams = req.body;
            const user = req['mjUser'];
            const params = {
                EntityName: entityName,
                ...viewParams
            };
            const result = await ViewOperationsHandler.runView(params, user);
            if (result.success) {
                res.json(result.result);
            }
            else {
                res.status(400).json({ error: result.error });
            }
        }
        catch (error) {
            LogError(error);
            res.status(500).json({ error: error?.message || 'Unknown error' });
        }
    }
    async runViews(req, res) {
        try {
            const { params } = req.body;
            const user = req['mjUser'];
            if (!Array.isArray(params)) {
                res.status(400).json({ error: 'params must be an array of RunViewParams' });
                return;
            }
            const filteredParams = params.filter(p => this.isEntityAllowed(p.EntityName));
            if (filteredParams.length === 0 && params.length > 0) {
                res.status(403).json({
                    error: 'None of the requested entities are allowed through the REST API',
                    details: 'The entities requested are either not included in the allowlist or are explicitly excluded in the REST API configuration'
                });
                return;
            }
            const result = await ViewOperationsHandler.runViews(filteredParams, user);
            if (result.success) {
                res.json(result.results);
            }
            else {
                res.status(400).json({ error: result.error });
            }
        }
        catch (error) {
            LogError(error);
            res.status(500).json({ error: error?.message || 'Unknown error' });
        }
    }
    async getViewEntity(req, res) {
        try {
            const { ViewID, ViewName } = req.query;
            const user = req['mjUser'];
            if (!ViewID && !ViewName) {
                res.status(400).json({ error: 'Either ViewID or ViewName must be provided' });
                return;
            }
            const entityName = "SampleEntity";
            res.json({ entityName });
        }
        catch (error) {
            LogError(error);
            res.status(500).json({ error: error?.message || 'Unknown error' });
        }
    }
    async getEntityMetadata(req, res) {
        try {
            const user = req['mjUser'];
            const md = new Metadata();
            const entities = md.Entities.filter(e => {
                if (!this.isEntityAllowed(e.Name)) {
                    return false;
                }
                const permissions = e.GetUserPermisions(user);
                return permissions.CanRead;
            });
            const result = entities.map(e => ({
                Name: e.Name,
                ClassName: e.ClassName,
                SchemaName: e.SchemaName,
                DisplayName: e.DisplayName,
                Description: e.Description,
                IncludeInAPI: e.IncludeInAPI,
                AllowCreateAPI: e.AllowCreateAPI,
                AllowUpdateAPI: e.AllowUpdateAPI,
                AllowDeleteAPI: e.AllowDeleteAPI
            }));
            res.json(result);
        }
        catch (error) {
            LogError(error);
            res.status(500).json({ error: error?.message || 'Unknown error' });
        }
    }
    async getEntityFieldMetadata(req, res) {
        try {
            const { entityName } = req.params;
            const user = req['mjUser'];
            const md = new Metadata();
            const entity = md.Entities.find(e => e.Name === entityName);
            if (!entity) {
                res.status(404).json({ error: `Entity '${entityName}' not found` });
                return;
            }
            const permissions = entity.GetUserPermisions(user);
            if (!permissions.CanRead) {
                res.status(403).json({ error: 'Permission denied' });
                return;
            }
            const result = entity.Fields.map(f => ({
                Name: f.Name,
                DisplayName: f.DisplayName,
                Description: f.Description,
                Type: f.Type,
                IsRequired: f.AllowsNull === false,
                IsPrimaryKey: f.IsPrimaryKey,
                IsUnique: f.IsUnique,
                MaxLength: f.MaxLength,
                DefaultValue: f.DefaultValue,
                CodeName: f.CodeName,
                TSType: f.TSType
            }));
            res.json(result);
        }
        catch (error) {
            LogError(error);
            res.status(500).json({ error: error?.message || 'Unknown error' });
        }
    }
    async getViewsMetadata(req, res) {
        try {
            const { entityName } = req.params;
            const user = req['mjUser'];
            const views = [];
            res.json(views);
        }
        catch (error) {
            LogError(error);
            res.status(500).json({ error: error?.message || 'Unknown error' });
        }
    }
    async getRecordFavoriteStatus(req, res) {
        try {
            const { userId, entityName, id } = req.params;
            const user = req['mjUser'];
            const md = new Metadata();
            const entity = await md.GetEntityObject(entityName, user);
            const compositeKey = this.createCompositeKey(entity.EntityInfo, id);
            const isFavorite = false;
            res.json({ isFavorite });
        }
        catch (error) {
            LogError(error);
            res.status(500).json({ error: error?.message || 'Unknown error' });
        }
    }
    async setRecordFavoriteStatus(req, res) {
        try {
            const { userId, entityName, id } = req.params;
            const user = req['mjUser'];
            const md = new Metadata();
            const entity = await md.GetEntityObject(entityName, user);
            const compositeKey = this.createCompositeKey(entity.EntityInfo, id);
            res.status(204).send();
        }
        catch (error) {
            LogError(error);
            res.status(500).json({ error: error?.message || 'Unknown error' });
        }
    }
    async removeRecordFavoriteStatus(req, res) {
        try {
            const { userId, entityName, id } = req.params;
            const user = req['mjUser'];
            const md = new Metadata();
            const entity = await md.GetEntityObject(entityName, user);
            const compositeKey = this.createCompositeKey(entity.EntityInfo, id);
            res.status(204).send();
        }
        catch (error) {
            LogError(error);
            res.status(500).json({ error: error?.message || 'Unknown error' });
        }
    }
    async executeTransaction(req, res) {
        try {
            res.status(501).json({ error: 'Not implemented' });
        }
        catch (error) {
            LogError(error);
            res.status(500).json({ error: error?.message || 'Unknown error' });
        }
    }
    async runReport(req, res) {
        try {
            res.status(501).json({ error: 'Not implemented' });
        }
        catch (error) {
            LogError(error);
            res.status(500).json({ error: error?.message || 'Unknown error' });
        }
    }
    async runQuery(req, res) {
        try {
            res.status(501).json({ error: 'Not implemented' });
        }
        catch (error) {
            LogError(error);
            res.status(500).json({ error: error?.message || 'Unknown error' });
        }
    }
    createCompositeKey(entityInfo, id) {
        if (entityInfo.PrimaryKeys.length === 1) {
            const primaryKeyField = entityInfo.PrimaryKeys[0].Name;
            const compositeKey = new CompositeKey();
            compositeKey.KeyValuePairs = [
                { FieldName: primaryKeyField, Value: id }
            ];
            return compositeKey;
        }
        else {
            throw new Error('Composite primary keys are not supported in this implementation');
        }
    }
    getRouter() {
        return this.router;
    }
}
//# sourceMappingURL=RESTEndpointHandler.js.map