import {join} from 'path';
import {getApplication, startAPI, WebApiSetup} from '@event-driven-io/emmett-expressjs';
import {glob} from "glob";
import express, {Application, Request, Response} from 'express';
import {jsonBigIntReplacer} from './src/util/sanitize';
import {requireUser} from "./src/supabase/requireUser";
import {getKnexInstance, closeDb} from "./src/common/db";
import swaggerUi from 'swagger-ui-express'
import {specs} from './src/swagger';
import cors from 'cors';
import {findEventstore} from "./src/common/loadPostgresEventstore";
import {PostgresEventStore} from "@event-driven-io/emmett-postgresql";

async function startServer() {

    const eventStore = await findEventstore()
    const slicesBase = join(__dirname, 'dist/src/slices');
    const routesPattern = join(slicesBase, '**/routes{,-*}.js');

    const routeFiles = await glob(routesPattern, {nodir: true, windowsPathsNoEscape: true});
    console.log('Found route files:', routeFiles);

    const processorPattern = join(slicesBase, '**/processor{,-*}.js');
    const processorFiles = await glob(processorPattern, {nodir: true, windowsPathsNoEscape: true});
    console.log('Found processor files:', processorFiles);

    // Common routes (e.g. projection replay) are privileged/operational and are
    // only mounted when explicitly enabled via env.
    const commonRoutesEnabled = process.env.COMMON_ROUTES_ENABLED === 'true';
    const commonPattern = join(__dirname, 'src/common/routes{,-*}.@(ts|js)');
    const commonRouteFiles = commonRoutesEnabled
        ? await glob(commonPattern, {nodir: true, windowsPathsNoEscape: true})
        : [];
    console.log(commonRoutesEnabled
        ? `Found common route files: ${commonRouteFiles}`
        : 'Common routes disabled (set COMMON_ROUTES_ENABLED=true to enable)');


    const rootApp: Application = express();
    rootApp.set('json replacer', jsonBigIntReplacer);

    const corsOrigins = process.env.CORS_ORIGINS?.split(',').map(o => o.trim()) ?? ['http://localhost:3000', 'http://localhost:3001'];
    rootApp.use(cors({
        origin: corsOrigins,
        credentials: true,
        methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
        allowedHeaders: ['Content-Type', 'Content-Encoding',  'accept-encoding', 'Authorization','x-user-id','x-causation-id','x-correlation-id']
    }));

    const webApis: WebApiSetup[] = [];

    for (const file of routeFiles.concat(commonRouteFiles)) {
        const webApiModule: { api: () => WebApiSetup } = await import(file);
        if (typeof webApiModule.api == 'function') {
            var module = webApiModule.api()
            webApis.push(module);
        } else {
            console.error(`Expected api function to be defined in ${file}`);
        }
    }

    const startedProcessors: Array<{ stop: () => Promise<void> }> = [];

    for (const processorFile of processorFiles) {
        const processor: { processor: { start: (eventStore: PostgresEventStore) => Promise<void>; stop: () => Promise<void> } } = await import(processorFile);
        if (typeof processor.processor.start == "function") {
            console.log(`starting processor ${processorFile}`)
            processor.processor.start(eventStore).catch(err => console.error(`Processor ${processorFile} failed:`, err));
            startedProcessors.push(processor.processor);
        }
    }

    const shutdown = async (signal: string) => {
        console.log(`${signal} received, shutting down processors...`);
        await Promise.allSettled(startedProcessors.map(p => p.stop()));
        await eventStore.close();
        await closeDb();
        console.log('shutdown complete');
        process.exit(0);
    };

    process.on('SIGINT', () => shutdown('SIGINT'));
    process.on('SIGTERM', () => shutdown('SIGTERM'));

    // Get the main application from emmett
    const childApp: Application = getApplication({
        apis: webApis,
        disableJsonMiddleware: false,
        enableDefaultExpressEtag: true,
    });
    childApp.set('json replacer', jsonBigIntReplacer);

    // Protected user info endpoint - requires JWT token in Authorization header
    childApp.get('/api/user', async (req: Request, res: Response) => {
        console.log('API user route hit'); // Debug log
        try {
            const result = await requireUser(req, res, false)
            if (result.error) {
                // Response already sent by requireUser if sendUnauthorized=true
                if (!res.headersSent) {
                    res.status(401).json({error: result.error})
                }
            } else {
                res.status(200).json({
                    user_id: result.user.id,
                    email: result.user.email,
                    metadata: result.user.user_metadata
                })
            }
        } catch (error) {
            console.error('Error in /api/user:', error);
            if (!res.headersSent) {
                res.status(500).json({error: 'Internal server error'});
            }
        }
    });

    // Swagger UI endpoints
    childApp.use('/api-docs', swaggerUi.serve);
    childApp.get('/api-docs', swaggerUi.setup(specs, {
        swaggerOptions: {
            urls: [
                {
                    url: '/swagger.json',
                    name: 'JSON',
                },
            ],
        },
    }));

    // OpenAPI spec endpoint
    childApp.get('/swagger.json', (req: Request, res: Response) => {
        res.setHeader('Content-Type', 'application/json');
        res.send(specs);
    });

    const port = parseInt(process.env.PORT || '3000', 10);
    console.log(`> Ready on port ${port}`);

    rootApp.use((req: Request, _res: Response, next) => {
        console.log(`[${req.method}] ${req.path}`);
        next();
    });

    rootApp.use(express.json());

    // Closed-by-default auth gate: every route requires a valid Supabase JWT
    // except the explicitly public paths below. This guarantees new slice
    // routes are protected even if a handler forgets to call requireUser.
    const PUBLIC_PATHS = ['/api-docs', '/swagger.json', '/health'];
    const isPublicPath = (p: string): boolean =>
        PUBLIC_PATHS.some(pub => p === pub || p.startsWith(pub + '/'));

    rootApp.use(async (req: Request, res: Response, next) => {
        if (req.method === 'OPTIONS') return next();      // CORS preflight
        if (isPublicPath(req.path)) return next();        // docs / health
        const {error} = await requireUser(req, res);      // sends 401 on failure
        if (error) return;                                // response already sent
        next();
    });

    rootApp.use(childApp)
    // Start the main application
    startAPI(rootApp, {port: port});

    process.on('unhandledRejection', (reason, promise) => {
        console.error('⛔ Unhandled Rejection:', reason);
        if (reason instanceof Error && reason.stack) {
            console.error('Stack trace:\n', reason.stack);
        }
    });
}

startServer().catch(error => {
    console.error('Failed to start server:', error);
    process.exit(1);
});