import { createServer } from 'node:http';
import { createYoga, useReadinessCheck } from 'graphql-yoga';
import pg from 'pg';
import { shutdownTelemetry, startTelemetry } from './telemetry/index.js';
import 'reflect-metadata';
import { useSchema } from '@envelop/core';
import { useGraphQLModules } from '@envelop/graphql-modules';
import { useHive } from '@graphql-hive/yoga';
import { useDeferStream } from '@graphql-yoga/plugin-defer-stream';
import { env } from './environment.js';
import { cleanupExpiredInvitations } from './jobs/cleanup-expired-invitations.js';
import { scheduleDailyAtUtc } from './jobs/utils.js';
import { createGraphQLApp } from './modules-app.js';
import { DBProvider } from './modules/app-providers/db.provider.js';
import { Auth0ManagementProvider } from './modules/auth/providers/auth0-management.provider.js';
import { authPlugin } from './plugins/auth-plugin.js';
import { dbCleanupPlugin } from './plugins/db-cleanup-plugin.js';
const { Pool } = pg;
const DAY_IN_MS = 24 * 60 * 60 * 1000;
const CLEANUP_SCHEDULE_HOUR_UTC = 2;
const CLEANUP_SCHEDULE_MINUTE_UTC = 0;
async function main() {
    await startTelemetry();
    // Create a shared connection pool for the entire application
    const pool = new Pool({
        user: env.postgres.user,
        password: env.postgres.password,
        host: env.postgres.host,
        port: Number(env.postgres.port),
        database: env.postgres.db,
        ssl: env.postgres.ssl ? { rejectUnauthorized: false } : false,
        max: env.postgres.max, // maximum number of clients in the pool
    });
    if (env.auth0) {
        console.log(`Auth0 configured: ${env.auth0.domain}`);
    }
    else {
        console.log('Auth0 not configured');
    }
    const dbProvider = new DBProvider(pool);
    const auth0Provider = new Auth0ManagementProvider(env);
    const logger = {
        info: (message, meta) => {
            if (meta !== undefined) {
                console.log(message, meta);
                return;
            }
            console.log(message);
        },
        error: (message, meta) => {
            if (meta !== undefined) {
                console.error(message, meta);
                return;
            }
            console.error(message);
        },
    };
    const cleanupSchedule = env.auth0
        ? scheduleDailyAtUtc(CLEANUP_SCHEDULE_HOUR_UTC, CLEANUP_SCHEDULE_MINUTE_UTC, DAY_IN_MS, async () => {
            try {
                const result = await cleanupExpiredInvitations(dbProvider, auth0Provider, logger);
                logger.info('Invitation cleanup complete', result);
            }
            catch (error) {
                logger.error('Scheduled invitation cleanup run failed', { error });
            }
        })
        : null;
    if (cleanupSchedule) {
        logger.info('Scheduled invitation cleanup job', {
            cron: `${CLEANUP_SCHEDULE_MINUTE_UTC} ${CLEANUP_SCHEDULE_HOUR_UTC} * * *`,
            timezone: 'UTC',
        });
    }
    const { application, transformedSchema } = await createGraphQLApp(env, pool);
    const yoga = createYoga({
        plugins: [
            dbCleanupPlugin(),
            authPlugin(),
            useGraphQLModules(application),
            useSchema(transformedSchema),
            useDeferStream(),
            useHive({
                enabled: !!env.hive,
                token: env.hive?.hiveToken ?? '',
                usage: !!env.hive,
            }),
            useReadinessCheck({
                check: async () => {
                    const isHealthy = await dbProvider.healthCheck();
                    if (!isHealthy) {
                        throw new Error('Database health check failed');
                    }
                    return isHealthy;
                },
            }),
        ],
        context: (yogaContext) => {
            return {
                ...yogaContext,
                env,
                pool,
            };
        },
    });
    const server = createServer(yoga);
    // Graceful shutdown handler
    let isShuttingDown = false;
    const gracefulShutdown = async (reason, err, exitCode = 1) => {
        if (isShuttingDown)
            return;
        isShuttingDown = true;
        if (err) {
            process.stderr.write(`[shutdown] Reason: ${reason} ${String(err)}\n`);
        }
        else {
            process.stderr.write(`[shutdown] Reason: ${reason}\n`);
        }
        // Stop accepting new connections
        await new Promise(resolve => {
            server.close(() => resolve());
        });
        cleanupSchedule?.cancel();
        // Allow in-flight requests some time to complete, then close the pool
        const FORCE_EXIT_TIMEOUT_MS = 10_000;
        const forceExitTimer = setTimeout(() => {
            process.stderr.write('[shutdown] Force exiting after timeout\n');
            process.exit(exitCode);
        }, FORCE_EXIT_TIMEOUT_MS);
        // Do not keep the event loop alive just for the timer
        forceExitTimer.unref();
        try {
            await pool.end();
        }
        catch (e) {
            process.stderr.write(`[shutdown] Error while closing DB pool ${String(e)}\n`);
        }
        try {
            await shutdownTelemetry();
        }
        catch (e) {
            process.stderr.write(`[shutdown] Error while shutting down telemetry ${String(e)}\n`);
        }
        finally {
            clearTimeout(forceExitTimer);
            process.exit(exitCode);
        }
    };
    // Pool errors: log but don't shutdown on transient errors
    // The pool will attempt to recover from connection issues automatically
    pool.on('error', err => {
        process.stderr.write(`[pool] Unexpected error on idle client: ${String(err)}\n`);
        // Only shutdown on critical errors that indicate the pool is permanently broken
        // For now, log and let the pool attempt recovery
        // If queries start failing consistently, they'll be caught by application error handlers
    });
    // OS signals
    process.on('SIGINT', () => gracefulShutdown('SIGINT', undefined, 0).catch(e => process.stderr.write(`[shutdown] Fatal error during shutdown: ${String(e)}\n`)));
    process.on('SIGTERM', () => gracefulShutdown('SIGTERM', undefined, 0).catch(e => process.stderr.write(`[shutdown] Fatal error during shutdown: ${String(e)}\n`)));
    // Unhandled errors
    process.on('unhandledRejection', reason => gracefulShutdown('unhandledRejection', reason, 1).catch(e => process.stderr.write(`[shutdown] Fatal error during shutdown: ${String(e)}\n`)));
    process.on('uncaughtException', err => gracefulShutdown('uncaughtException', err, 1).catch(e => process.stderr.write(`[shutdown] Fatal error during shutdown: ${String(e)}\n`)));
    server.listen({
        port: 4000,
    }, () => {
        process.stdout.write('GraphQL API located at http://localhost:4000/graphql\n');
    });
}
main();
//# sourceMappingURL=index.js.map