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');

// Initialize Express app
const app = express();

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

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

// Session configuration
app.use(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');
    }
}));

// Build dynamic CSP configuration
const buildConnectSrc = () => {
    const sources = ["'self'"];
    
    // Add MkDocs URLs from config
    if (config.mkdocs?.url) {
        sources.push(config.mkdocs.url);
    }
    
    // Add localhost ports from environment
    const mkdocsPort = process.env.MKDOCS_PORT || '4000';
    const mkdocsSitePort = process.env.MKDOCS_SITE_SERVER_PORT || '4002';
    
    sources.push(`http://localhost:${mkdocsPort}`);
    sources.push(`http://localhost:${mkdocsSitePort}`);
    
    // Add production domains if in production
    if (config.isProduction || process.env.NODE_ENV === 'production') {
        // Add the main domain from environment
        const mainDomain = process.env.DOMAIN || 'cmlite.org';
        sources.push(`https://${mainDomain}`);
        sources.push('https://cmlite.org'); // Fallback
    }
    
    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:", "https://*.tile.openstreetmap.org", "https://unpkg.com"],
            connectSrc: buildConnectSrc()
        }
    }
}));

// CORS configuration
app.use(cors({
    origin: function(origin, callback) {
        // Allow requests with no origin (like mobile apps or curl requests)
        if (!origin) return callback(null, true);
        
        const allowedOrigins = config.cors.allowedOrigins;
        if (allowedOrigins.includes(origin) || allowedOrigins.includes('*')) {
            callback(null, true);
        } else {
            callback(new Error('Not allowed by CORS'));
        }
    },
    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'));

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

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

// Proxy endpoint for MkDocs search
app.get('/api/docs-search', async (req, res) => {
    try {
        const mkdocsUrl = config.mkdocs?.url || `http://localhost:${process.env.MKDOCS_SITE_SERVER_PORT || '4002'}`;
        logger.info(`Fetching search index from: ${mkdocsUrl}/search/search_index.json`);
        
        const response = await fetch(`${mkdocsUrl}/search/search_index.json`);
        
        if (!response.ok) {
            throw new Error(`Failed to fetch search index: ${response.status}`);
        }
        
        const data = await response.json();
        res.json(data);
    } catch (error) {
        logger.error('Error fetching search index:', error);
        res.status(500).json({ error: 'Failed to fetch search index' });
    }
});

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

// Error handling middleware
app.use((err, req, res, next) => {
    logger.error('Unhandled error:', err);
    
    // Don't leak error details in production
    const message = config.isProduction ? 
        'Internal server error' : 
        err.message || 'Internal server error';
    
    res.status(err.status || 500).json({
        success: false,
        error: message
    });
});

// Start server
const server = app.listen(config.port, () => {
    logger.info(`
    ╔════════════════════════════════════════╗
    ║     BNKops Map Server                  ║
    ╠════════════════════════════════════════╣
    ║  Status: Running                       ║
    ║  Port: ${config.port}                            ║
    ║  Environment: ${config.nodeEnv}          ║
    ║  Project ID: ${config.nocodb.projectId}             ║
    ║  Table ID: ${config.nocodb.tableId}               ║
    ║  Login Sheet: ${config.nocodb.loginSheetId || 'Not Configured'}              ║
    ║  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;