import dotenv from 'dotenv';
dotenv.config();
import { expressMiddleware } from '@apollo/server/express4';
import { mergeSchemas } from '@graphql-tools/schema';
import { Metadata } from '@memberjunction/core';
import { setupSQLServerClient, SQLServerProviderConfigData } from '@memberjunction/sqlserver-dataprovider';
import { extendConnectionPoolWithQuery } from './util.js';
import { default as BodyParser } from 'body-parser';
import compression from 'compression';
import cors from 'cors';
import express from 'express';
import { default as fg } from 'fast-glob';
import { useServer } from 'graphql-ws/lib/use/ws';
import { createServer } from 'node:http';
import { fileURLToPath } from 'node:url';
import { sep } from 'node:path';
import 'reflect-metadata';
import { ReplaySubject } from 'rxjs';
import { buildSchemaSync, GraphQLTimestamp } from 'type-graphql';
import sql from 'mssql';
import { WebSocketServer } from 'ws';
import buildApolloServer from './apolloServer/index.js';
import { configInfo, dbDatabase, dbHost, dbPort, dbUsername, graphqlPort, graphqlRootPath, mj_core_schema, websiteRunFromPackage } from './config.js';
import { contextFunction, getUserPayload } from './context.js';
import { requireSystemUserDirective, publicDirective } from './directives/index.js';
import createMSSQLConfig from './orm.js';
import { setupRESTEndpoints } from './rest/setupRESTEndpoints.js';
import { LoadAllCoreActions } from '@memberjunction/core-actions';
LoadAllCoreActions();
import { LoadApolloAccountsEnrichmentAction, LoadApolloContactsEnrichmentAction } from '@memberjunction/actions-apollo';
LoadApolloAccountsEnrichmentAction();
LoadApolloContactsEnrichmentAction();
import { LoadCoreEntitiesServerSubClasses } from '@memberjunction/core-entities-server';
LoadCoreEntitiesServerSubClasses();
import { LoadAgentManagementActions } from '@memberjunction/ai-agent-manager-actions';
LoadAgentManagementActions();
import { LoadAgentManagerCore } from '@memberjunction/ai-agent-manager';
LoadAgentManagerCore();
import { LoadSchedulingEngine } from '@memberjunction/scheduling-engine';
LoadSchedulingEngine();
import { LoadAllSchedulingActions } from '@memberjunction/scheduling-actions';
LoadAllSchedulingActions();
import { GetTypeformResponsesAction } from '@memberjunction/actions-bizapps-formbuilders';
const x = GetTypeformResponsesAction;
import { resolve } from 'node:path';
import { DataSourceInfo, raiseEvent } from './types.js';
import { LoadAIEngine } from '@memberjunction/aiengine';
import { LoadAIProviders } from '@memberjunction/ai-provider-bundle';
LoadAIEngine();
LoadAIProviders();
import { LoadMSGraphProvider } from '@memberjunction/communication-ms-graph';
import { LoadProvider as LoadSendGridProvider } from '@memberjunction/communication-sendgrid';
LoadMSGraphProvider();
LoadSendGridProvider();
import { ScheduledJobsService } from './services/ScheduledJobsService.js';
import { LocalCacheManager, TelemetryManager } from '@memberjunction/core';
const cacheRefreshInterval = configInfo.databaseSettings.metadataCacheRefreshInterval;
export { MaxLength } from 'class-validator';
export * from 'type-graphql';
export { NewUserBase } from './auth/newUsers.js';
export { configInfo } from './config.js';
export * from './directives/index.js';
export * from './entitySubclasses/entityPermissions.server.js';
export * from './types.js';
export { TokenExpiredError, getSystemUser } from './auth/index.js';
export * from './generic/PushStatusResolver.js';
export * from './generic/ResolverBase.js';
export * from './generic/RunViewResolver.js';
export * from './resolvers/RunTemplateResolver.js';
export * from './resolvers/RunAIPromptResolver.js';
export * from './resolvers/RunAIAgentResolver.js';
export * from './resolvers/TaskResolver.js';
export * from './generic/KeyValuePairInput.js';
export * from './generic/KeyInputOutputTypes.js';
export * from './generic/DeleteOptionsInput.js';
export * from './agents/skip-agent.js';
export * from './agents/skip-sdk.js';
export * from './resolvers/AskSkipResolver.js';
export * from './resolvers/ColorResolver.js';
export * from './resolvers/ComponentRegistryResolver.js';
export * from './resolvers/DatasetResolver.js';
export * from './resolvers/EntityRecordNameResolver.js';
export * from './resolvers/MergeRecordsResolver.js';
export * from './resolvers/ReportResolver.js';
export * from './resolvers/QueryResolver.js';
export * from './resolvers/SqlLoggingConfigResolver.js';
export * from './resolvers/SyncRolesUsersResolver.js';
export * from './resolvers/SyncDataResolver.js';
export * from './resolvers/GetDataResolver.js';
export * from './resolvers/GetDataContextDataResolver.js';
export * from './resolvers/TransactionGroupResolver.js';
export * from './resolvers/CreateQueryResolver.js';
export * from './resolvers/TelemetryResolver.js';
export { GetReadOnlyDataSource, GetReadWriteDataSource, GetReadWriteProvider, GetReadOnlyProvider } from './util.js';
export * from './generated/generated.js';
const localPath = (p) => {
    const dirname = fileURLToPath(new URL('.', import.meta.url));
    const resolvedPath = resolve(dirname, p);
    return resolvedPath;
};
export const createApp = () => express();
export const serve = async (resolverPaths, app = createApp(), options) => {
    const localResolverPaths = ['resolvers/**/*Resolver.{js,ts}', 'generic/*Resolver.{js,ts}', 'generated/generated.{js,ts}'].map(localPath);
    const combinedResolverPaths = [...resolverPaths, ...localResolverPaths];
    const isWindows = sep === '\\';
    const globs = combinedResolverPaths.flatMap((path) => (isWindows ? path.replace(/\\/g, '/') : path));
    const paths = fg.globSync(globs);
    if (paths.length === 0) {
        console.warn(`No resolvers found in ${combinedResolverPaths.join(', ')}`);
        console.log({ combinedResolverPaths, paths, cwd: process.cwd() });
    }
    const pool = new sql.ConnectionPool(createMSSQLConfig());
    const setupComplete$ = new ReplaySubject(1);
    await pool.connect();
    const dataSources = [new DataSourceInfo({ dataSource: pool, type: 'Read-Write', host: dbHost, port: dbPort, database: dbDatabase, userName: dbUsername })];
    let readOnlyPool = null;
    if (configInfo.dbReadOnlyUsername && configInfo.dbReadOnlyPassword) {
        const readOnlyConfig = {
            ...createMSSQLConfig(),
            user: configInfo.dbReadOnlyUsername,
            password: configInfo.dbReadOnlyPassword,
        };
        readOnlyPool = new sql.ConnectionPool(readOnlyConfig);
        await readOnlyPool.connect();
        dataSources.push(new DataSourceInfo({ dataSource: readOnlyPool, type: 'Read-Only', host: dbHost, port: dbPort, database: dbDatabase, userName: configInfo.dbReadOnlyUsername }));
        console.log('Read-only Connection Pool has been initialized.');
    }
    const config = new SQLServerProviderConfigData(pool, mj_core_schema, cacheRefreshInterval);
    await setupSQLServerClient(config);
    const md = new Metadata();
    console.log(`Data Source has been initialized. ${md?.Entities ? md.Entities.length : 0} entities loaded.`);
    const tm = TelemetryManager.Instance;
    if (configInfo.telemetry?.enabled) {
        tm.SetEnabled(true);
        if (configInfo.telemetry?.level) {
            tm.UpdateSettings({ level: configInfo.telemetry.level });
        }
        console.log(`Server telemetry enabled with level: ${configInfo.telemetry.level || 'standard'}`);
    }
    else {
        tm.SetEnabled(false);
        console.log('Server telemetry disabled');
    }
    await LocalCacheManager.Instance.Initialize(Metadata.Provider.LocalStorageProvider);
    console.log('LocalCacheManager initialized');
    setupComplete$.next(true);
    raiseEvent('setupComplete', dataSources, null, this);
    const dynamicModules = await Promise.all(paths.map((modulePath) => {
        try {
            const module = import(isWindows ? `file://${modulePath}` : modulePath);
            return module;
        }
        catch (e) {
            console.error(`Error loading dynamic module at '${modulePath}'`, e);
            throw e;
        }
    }));
    const resolvers = dynamicModules.flatMap((module) => Object.values(module).filter((value) => typeof value === 'function'));
    let schema = mergeSchemas({
        schemas: [
            buildSchemaSync({
                resolvers,
                validate: false,
                scalarsMap: [{ type: Date, scalar: GraphQLTimestamp }],
                emitSchemaFile: websiteRunFromPackage !== 1,
            }),
        ],
        typeDefs: [requireSystemUserDirective.typeDefs, publicDirective.typeDefs],
    });
    schema = requireSystemUserDirective.transformer(schema);
    schema = publicDirective.transformer(schema);
    const httpServer = createServer(app);
    const webSocketServer = new WebSocketServer({ server: httpServer, path: graphqlRootPath });
    const serverCleanup = useServer({
        schema,
        context: async ({ connectionParams }) => {
            const userPayload = await getUserPayload(String(connectionParams?.Authorization), undefined, dataSources);
            return { userPayload };
        },
        onError: (ctx, message, errors) => {
            const isTokenExpired = errors.some(err => err.extensions?.code === 'JWT_EXPIRED' ||
                err.message?.includes('token has expired'));
            if (isTokenExpired) {
                console.warn('WebSocket connection token expired - client should reconnect with refreshed token');
            }
            else {
                console.error('WebSocket error:', errors);
            }
        },
    }, webSocketServer);
    const apolloServer = buildApolloServer({ schema }, { httpServer, serverCleanup });
    await apolloServer.start();
    app.use(compression({
        threshold: 1024,
        filter: (req, res) => {
            if (req.headers['content-type']) {
                const contentType = req.headers['content-type'];
                if (contentType.includes('image/') ||
                    contentType.includes('video/') ||
                    contentType.includes('audio/') ||
                    contentType.includes('application/octet-stream')) {
                    return false;
                }
            }
            return compression.filter(req, res);
        },
        level: 6
    }));
    app.use(graphqlRootPath, cors(), BodyParser.json({ limit: '50mb' }), expressMiddleware(apolloServer, {
        context: contextFunction({
            setupComplete$,
            dataSource: extendConnectionPoolWithQuery(pool),
            dataSources
        }),
    }));
    const authMiddleware = async (req, res, next) => {
        try {
            const sessionIdRaw = req.headers['x-session-id'];
            const requestDomain = new URL(req.headers.origin || '').hostname;
            const sessionId = sessionIdRaw ? sessionIdRaw.toString() : '';
            const bearerToken = req.headers.authorization ?? '';
            const apiKey = String(req.headers['x-mj-api-key']);
            const userPayload = await getUserPayload(bearerToken, sessionId, dataSources, requestDomain, apiKey);
            if (!userPayload) {
                return res.status(401).json({ error: 'Invalid token' });
            }
            req.user = userPayload;
            next();
        }
        catch (error) {
            console.error('Auth error:', error);
            return res.status(401).json({ error: 'Authentication failed' });
        }
    };
    const restApiConfig = {
        enabled: configInfo.restApiOptions?.enabled ?? true,
        includeEntities: configInfo.restApiOptions?.includeEntities,
        excludeEntities: configInfo.restApiOptions?.excludeEntities
    };
    if (options?.restApiOptions) {
        Object.assign(restApiConfig, options.restApiOptions);
    }
    if (process.env.MJ_REST_API_ENABLED !== undefined) {
        restApiConfig.enabled = process.env.MJ_REST_API_ENABLED === 'true';
        if (restApiConfig.enabled) {
            console.log('REST API is enabled via environment variable');
        }
    }
    if (process.env.MJ_REST_API_INCLUDE_ENTITIES) {
        restApiConfig.includeEntities = process.env.MJ_REST_API_INCLUDE_ENTITIES.split(',').map(e => e.trim());
    }
    if (process.env.MJ_REST_API_EXCLUDE_ENTITIES) {
        restApiConfig.excludeEntities = process.env.MJ_REST_API_EXCLUDE_ENTITIES.split(',').map(e => e.trim());
    }
    setupRESTEndpoints(app, restApiConfig, authMiddleware);
    let scheduledJobsService = null;
    if (configInfo.scheduledJobs?.enabled) {
        try {
            scheduledJobsService = new ScheduledJobsService(configInfo.scheduledJobs);
            await scheduledJobsService.Initialize();
            await scheduledJobsService.Start();
        }
        catch (error) {
            console.error('❌ Failed to start scheduled jobs service:', error);
        }
    }
    if (options?.onBeforeServe) {
        await Promise.resolve(options.onBeforeServe());
    }
    await new Promise((resolve) => httpServer.listen({ port: graphqlPort }, resolve));
    console.log(`📦 Connected to database: ${dbHost}:${dbPort}/${dbDatabase}`);
    console.log(`🚀 Server ready at http://localhost:${graphqlPort}/`);
    const gracefulShutdown = async (signal) => {
        console.log(`\n${signal} received, shutting down gracefully...`);
        if (scheduledJobsService?.IsRunning) {
            try {
                await scheduledJobsService.Stop();
                console.log('✅ Scheduled jobs service stopped');
            }
            catch (error) {
                console.error('❌ Error stopping scheduled jobs service:', error);
            }
        }
        httpServer.close(() => {
            console.log('✅ HTTP server closed');
            process.exit(0);
        });
        setTimeout(() => {
            console.error('⚠️  Forced shutdown after timeout');
            process.exit(1);
        }, 10000);
    };
    process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
    process.on('SIGINT', () => gracefulShutdown('SIGINT'));
};
//# sourceMappingURL=index.js.map