// At the very top of the file, before any requires
const startTime = Date.now();

// Load environment variables first - use the .env file in the map directory
require('dotenv').config({ path: require('path').join(__dirname, '..', '.env') });

// Use a more robust check for duplicate execution
if (global.__serverInitialized) {
    console.log(`[INIT] Server already initialized - EXITING`);
    return;
}
global.__serverInitialized = true;

// Prevent duplicate execution
if (require.main !== module) {
    console.log('[INIT] Server.js being imported, not executed directly - EXITING');
    return;
}

const express = require('express');
const helmet = require('helmet');
const cors = require('cors');
const session = require('express-session');
const cookieParser = require('cookie-parser');
const crypto = require('crypto');
const fetch = require('node-fetch');

// Import configuration and utilities
const config = require('./config');
const logger = require('./utils/logger');
const { getCookieConfig } = require('./utils/helpers');
const { apiLimiter } = require('./middleware/rateLimiter');
const { cacheBusting } = require('./utils/cacheBusting');
const { initializeEmailService } = require('./services/email');
const listmonkService = require('./services/listmonk');

// Initialize Express app - only create once
if (global.__expressApp) {
    console.log('[INIT] Express app already created - EXITING');
    return;
}

const app = express();
global.__expressApp = app;

// Trust proxy for Cloudflare
app.set('trust proxy', true);

// Cookie parser
app.use(cookieParser());

// Session configuration - only initialize once
const sessionMiddleware = session({
    secret: config.session.secret,
    resave: false,
    saveUninitialized: false,
    cookie: getCookieConfig(),
    name: 'nocodb-map-session',
    genid: (req) => {
        // Use a custom session ID generator to avoid conflicts
        return crypto.randomBytes(16).toString('hex');
    }
});

app.use(sessionMiddleware);

// Build dynamic CSP configuration
const buildConnectSrc = () => {
    const sources = ["'self'"];
    
    // Add NocoDB API URL
    if (config.nocodb.apiUrl) {
        try {
            const nocodbUrl = new URL(config.nocodb.apiUrl);
            sources.push(`${nocodbUrl.protocol}//${nocodbUrl.host}`);
        } catch (e) {
            // Invalid URL, skip
        }
    }
    
    // Add Edmonton Open Data Portal
    sources.push('https://data.edmonton.ca');
    
    // Add Nominatim for geocoding
    sources.push('https://nominatim.openstreetmap.org');
    
    // Add OpenStreetMap tile servers for dom-to-image map capture
    sources.push('https://*.tile.openstreetmap.org');
    
    // Add localhost for development
    if (!config.isProduction) {
        sources.push('http://localhost:*');
        sources.push('ws://localhost:*');
    }
    
    return sources;
};

// Security middleware
app.use(helmet({
    contentSecurityPolicy: {
        directives: {
            defaultSrc: ["'self'"],
            styleSrc: ["'self'", "'unsafe-inline'", "https://unpkg.com"],
            scriptSrc: ["'self'", "'unsafe-inline'", "https://unpkg.com", "https://cdn.jsdelivr.net"],
            imgSrc: ["'self'", "data:", "blob:", "https://*.tile.openstreetmap.org", "https://tiles.stadiamaps.com", "https://unpkg.com"],
            connectSrc: buildConnectSrc()
        }
    }
}));

// CORS configuration
app.use(cors({
    origin: function(origin, callback) {
        // Allow requests with no origin (like Postman or server-to-server)
        if (!origin) return callback(null, true);
        
        // In production, be more restrictive
        if (config.isProduction) {
            const allowedOrigins = [
                `https://${config.domain}`,
                `https://map.${config.domain}`,
                config.mkdocs.url, // Use configured MkDocs URL instead of hardcoded subdomain
                `https://admin.${config.domain}`,
                `http://localhost:${config.port}`, // Allow localhost with configured port
                `http://localhost:3000` // Allow default port 3000 as well
            ];
            
            if (allowedOrigins.includes(origin)) {
                callback(null, true);
            } else {
                callback(new Error('Not allowed by CORS'));
            }
        } else {
            // In development, allow localhost
            callback(null, true);
        }
    },
    credentials: true,
    methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
    allowedHeaders: ['Content-Type', 'Authorization']
}));

// Body parser middleware
app.use(express.json({ limit: '10mb' }));

// Add cache busting middleware for HTML content
app.use(cacheBusting.htmlMiddleware());

// Add cache headers middleware for static files
app.use(cacheBusting.staticCacheMiddleware());

// Serve static files with proper cache headers
app.use(express.static('public'));

// Cache busting version endpoint (before rate limiting)
app.get('/api/version', (req, res) => {
    res.json({ 
        version: cacheBusting.getVersion(),
        timestamp: new Date().toISOString()
    });
});

// Apply rate limiting to API routes
app.use('/api/', apiLimiter);

// Proxy endpoint for MkDocs search
app.get('/api/docs-search', async (req, res) => {
    try {
        const docsUrl = config.isProduction ? 
            `${config.mkdocs.searchUrl}/search/search_index.json` : 
            'http://localhost:8000/search/search_index.json';
        
        const response = await fetch(docsUrl);
        const data = await response.json();
        res.json(data);
    } catch (error) {
        logger.error('Failed to fetch docs search index:', error);
        res.status(500).json({ error: 'Failed to fetch search index' });
    }
});

// Initialize email service - only once
if (!global.__emailInitialized) {
    initializeEmailService();
    global.__emailInitialized = true;
}

// Initialize Listmonk service
if (!global.__listmonkInitialized) {
    global.__listmonkInitialized = true;
    
    // Initialize Listmonk in background (don't block server startup)
    setImmediate(async () => {
        try {
            if (listmonkService.syncEnabled) {
                logger.info('🔄 Initializing Listmonk integration...');
                
                const initialized = await listmonkService.initializeLists();
                
                if (initialized) {
                    logger.info('✅ Listmonk integration initialized successfully');
                    
                    // Optional initial sync (only if explicitly enabled)
                    if (process.env.LISTMONK_INITIAL_SYNC === 'true') {
                        logger.info('🔄 Performing initial Listmonk sync...');
                        
                        // Use setTimeout to further delay initial sync
                        setTimeout(async () => {
                            try {
                                const nocodbService = require('./services/nocodb');
                                
                                // Sync existing locations
                                try {
                                    const locationData = await nocodbService.getLocations();
                                    const locations = locationData?.list || [];
                                    console.log('🔍 Initial sync - fetched locations:', locations?.length || 0);
                                    if (locations && locations.length > 0) {
                                        const locationResults = await listmonkService.bulkSync(locations, 'location');
                                        logger.info(`📍 Initial location sync: ${locationResults.success} succeeded, ${locationResults.failed} failed`);
                                    } else {
                                        logger.warn('No locations found for initial sync');
                                    }
                                } catch (locError) {
                                    logger.warn('Initial location sync failed:', {
                                        message: locError.message,
                                        stack: locError.stack,
                                        error: locError.toString()
                                    });
                                }
                                
                                                                // Sync existing users
                                try {
                                    const userData = await nocodbService.getAllPaginated(config.nocodb.loginSheetId);
                                    const users = userData?.list || [];
                                    console.log('🔍 Initial sync - fetched users:', users?.length || 0);
                                    if (users && users.length > 0) {
                                        const userResults = await listmonkService.bulkSync(users, 'user');
                                        logger.info(`👤 Initial user sync: ${userResults.success} succeeded, ${userResults.failed} failed`);
                                    } else {
                                        logger.warn('No users found for initial sync');
                                    }
                                } catch (userError) {
                                    logger.warn('Initial user sync failed:', {
                                        message: userError.message,
                                        stack: userError.stack,
                                        error: userError.toString()
                                    });
                                }
                                
                                logger.info('✅ Initial Listmonk sync completed');
                            } catch (syncError) {
                                logger.error('Initial sync failed:', syncError.message);
                            }
                        }, 5000); // Wait 5 seconds after startup
                    }
                } else {
                    logger.error('❌ Listmonk integration failed to initialize');
                    logger.error(`Last error: ${listmonkService.lastError}`);
                }
            } else {
                logger.info('📧 Listmonk sync is disabled via configuration');
            }
        } catch (error) {
            logger.error('Listmonk initialization error:', error.message);
            // Don't crash the app, just disable sync
            listmonkService.syncEnabled = false;
        }
    });
}

// Import and setup routes
require('./routes')(app);

// Error handling middleware
app.use((err, req, res, next) => {
    logger.error('Unhandled error:', err);
    res.status(500).json({ 
        error: 'Internal server error',
        message: config.isProduction ? 'An error occurred' : err.message 
    });
});

// Add a simple health check endpoint early in the middleware stack (before other middlewares)
app.get('/health', (req, res) => {
    res.status(200).json({ 
        status: 'healthy', 
        timestamp: new Date().toISOString(),
        uptime: process.uptime()
    });
});

// Only start server if not already started
if (!global.__serverStarted) {
    global.__serverStarted = true;
    
    const server = app.listen(config.port, () => {
        logger.info(`
    ╔════════════════════════════════════════╗
    ║     BNKops Map Server                  ║
    ╠════════════════════════════════════════╣
    ║  Status: Running                       ║
    ║  Port: ${config.port}                            ║
    ║  Environment: ${config.isProduction ? 'production' : 'development'}          ║
    ║  Project ID: ${config.nocodb.projectId}             ║
    ║  Table ID: ${config.nocodb.tableId}               ║
    ║  Login Sheet: ${config.nocodb.loginSheetId}              ║
    ║  PID: ${process.pid}                                ║
    ║  Time: ${new Date().toISOString()}    ║
    ╚════════════════════════════════════════╝
    `);
    });

    // Graceful shutdown
    process.on('SIGTERM', () => {
        logger.info('SIGTERM signal received: closing HTTP server');
        server.close(() => {
            logger.info('HTTP server closed');
            process.exit(0);
        });
    });

    process.on('SIGINT', () => {
        logger.info('SIGINT signal received: closing HTTP server');
        server.close(() => {
            logger.info('HTTP server closed');
            process.exit(0);
        });
    });

    // Handle uncaught exceptions
    process.on('uncaughtException', (err) => {
        logger.error('Uncaught Exception:', err);
        process.exit(1);
    });

    // Handle unhandled promise rejections
    process.on('unhandledRejection', (reason, promise) => {
        logger.error('Unhandled Rejection at:', promise, 'reason:', reason);
        process.exit(1);
    });
}

module.exports = app;