const express = require('express');
const axios = require('axios');
const cors = require('cors');
const helmet = require('helmet');
const rateLimit = require('express-rate-limit');
const winston = require('winston');
const path = require('path');
const session = require('express-session');
const cookieParser = require('cookie-parser');
require('dotenv').config();

// Import geocoding routes
const geocodingRoutes = require('./routes/geocoding');

// Import QR code service (only for local generation, no upload)
const { generateQRCode } = require('./services/qrcode');

// Parse project and table IDs from view URL
function parseNocoDBUrl(url) {
    if (!url) return { projectId: null, tableId: null };
    
    // Pattern to match NocoDB URLs
    const patterns = [
        /#\/nc\/([^\/]+)\/([^\/\?#]+)/,  // matches #/nc/PROJECT_ID/TABLE_ID (dashboard URLs)
        /\/nc\/([^\/]+)\/([^\/\?#]+)/,   // matches /nc/PROJECT_ID/TABLE_ID
        /project\/([^\/]+)\/table\/([^\/\?#]+)/,  // alternative pattern
    ];
    
    for (const pattern of patterns) {
        const match = url.match(pattern);
        if (match) {
            return {
                projectId: match[1],
                tableId: match[2]
            };
        }
    }
    
    return { projectId: null, tableId: null };
}

// Add this helper function near the top of the file after the parseNocoDBUrl function
function syncGeoFields(data) {
    // If we have latitude and longitude but no Geo-Location, create it
    if (data.latitude && data.longitude && !data['Geo-Location']) {
        const lat = parseFloat(data.latitude);
        const lng = parseFloat(data.longitude);
        if (!isNaN(lat) && !isNaN(lng)) {
            data['Geo-Location'] = `${lat};${lng}`;            data.geodata = `${lat};${lng}`;        }
    }
    
    // If we have Geo-Location but no lat/lng, parse it
    else if (data['Geo-Location'] && (!data.latitude || !data.longitude)) {
        const geoLocation = data['Geo-Location'].toString();
        
        // Try semicolon-separated first
        let parts = geoLocation.split(';');
        if (parts.length === 2) {
            const lat = parseFloat(parts[0].trim());
            const lng = parseFloat(parts[1].trim());
            if (!isNaN(lat) && !isNaN(lng)) {
                data.latitude = lat;
                data.longitude = lng;
                data.geodata = `${lat};${lng}`;
                return data;
            }
        }
        
        // Try comma-separated
        parts = geoLocation.split(',');
        if (parts.length === 2) {
            const lat = parseFloat(parts[0].trim());
            const lng = parseFloat(parts[1].trim());
            if (!isNaN(lat) && !isNaN(lng)) {
                data.latitude = lat;
                data.longitude = lng;
                data.geodata = `${lat};${lng}`;
                // Normalize Geo-Location to semicolon format for NocoDB GeoData
                data['Geo-Location'] = `${lat};${lng}`;
            }
        }
    }
    
    return data;
}

// Auto-parse IDs if view URL is provided
if (process.env.NOCODB_VIEW_URL && (!process.env.NOCODB_PROJECT_ID || !process.env.NOCODB_TABLE_ID)) {
    const { projectId, tableId } = parseNocoDBUrl(process.env.NOCODB_VIEW_URL);
    
    if (projectId && tableId) {
        process.env.NOCODB_PROJECT_ID = projectId;
        process.env.NOCODB_TABLE_ID = tableId;
        console.log(`Auto-parsed from URL - Project ID: ${projectId}, Table ID: ${tableId}`);
    }
}

// Auto-parse login sheet ID if URL is provided
let LOGIN_SHEET_ID = null;
if (process.env.NOCODB_LOGIN_SHEET) {
    // Check if it's a URL or just an ID
    if (process.env.NOCODB_LOGIN_SHEET.startsWith('http')) {
        const { projectId, tableId } = parseNocoDBUrl(process.env.NOCODB_LOGIN_SHEET);
        if (projectId && tableId) {
            LOGIN_SHEET_ID = tableId;
            console.log(`Auto-parsed login sheet ID from URL: ${LOGIN_SHEET_ID}`);
        } else {
            console.error('Could not parse login sheet URL');
        }
    } else {
        // Assume it's already just the ID
        LOGIN_SHEET_ID = process.env.NOCODB_LOGIN_SHEET;
        console.log(`Using login sheet ID: ${LOGIN_SHEET_ID}`);
    }
}

// Auto-parse settings sheet ID if URL is provided
let SETTINGS_SHEET_ID = null;
if (process.env.NOCODB_SETTINGS_SHEET) {
    // Check if it's a URL or just an ID
    if (process.env.NOCODB_SETTINGS_SHEET.startsWith('http')) {
        const { projectId, tableId } = parseNocoDBUrl(process.env.NOCODB_SETTINGS_SHEET);
        if (projectId && tableId) {
            SETTINGS_SHEET_ID = tableId;
            console.log(`Auto-parsed settings sheet ID from URL: ${SETTINGS_SHEET_ID}`);
        } else {
            console.error('Could not parse settings sheet URL');
        }
    } else {
        // Assume it's already just the ID
        SETTINGS_SHEET_ID = process.env.NOCODB_SETTINGS_SHEET;
        console.log(`Using settings sheet ID: ${SETTINGS_SHEET_ID}`);
    }
}

// Configure logger
const logger = winston.createLogger({
    level: process.env.NODE_ENV === 'production' ? 'info' : 'debug',
    format: winston.format.combine(
        winston.format.timestamp(),
        winston.format.json()
    ),
    transports: [
        new winston.transports.Console({
            format: winston.format.simple()
        })
    ]
});

// Initialize Express app
const app = express();
const PORT = process.env.PORT || 3000;

// Session configuration
app.use(cookieParser());

// Determine if we should use secure cookies based on environment and request
const isProduction = process.env.NODE_ENV === 'production';

// Cookie configuration function
const getCookieConfig = (req) => {
    const host = req?.get('host') || '';
    const isLocalhost = host.includes('localhost') || host.includes('127.0.0.1') || host.match(/^\d+\.\d+\.\d+\.\d+/);
    
    const config = {
        httpOnly: true,
        maxAge: 24 * 60 * 60 * 1000, // 24 hours
        sameSite: 'lax',
        secure: false, // Default to false
        domain: undefined // Default to no domain restriction
    };
    
    // Only set domain and secure for production non-localhost access
    if (isProduction && !isLocalhost && process.env.COOKIE_DOMAIN) {
        // Check if the request is coming from a subdomain of COOKIE_DOMAIN
        const cookieDomain = process.env.COOKIE_DOMAIN.replace(/^\./, '');        if (host.includes(cookieDomain)) {
            config.domain = process.env.COOKIE_DOMAIN;
            config.secure = true; // Enable secure cookies for production
        }
    }
    
    return config;
};

app.use(session({
    secret: process.env.SESSION_SECRET || 'your-secret-key-change-in-production',
    resave: false,
    saveUninitialized: false,
    cookie: getCookieConfig(),
    name: 'nocodb-map-session',
    genid: (req) => {
        // Use a custom session ID generator to avoid conflicts
        return require('crypto').randomBytes(16).toString('hex');
    }
}));

// 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: ["'self'"]
        }
    }
}));

// 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 = process.env.ALLOWED_ORIGINS?.split(',') || [];
        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']
}));

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

// Rate limiting with Cloudflare support
const limiter = rateLimit({
    windowMs: 15 * 60 * 1000,
    max: 100,
    keyGenerator: (req) => {
        // Use CF-Connecting-IP header if available (Cloudflare)
        return req.headers['cf-connecting-ip'] || 
               req.headers['x-forwarded-for']?.split(',')[0] || 
               req.ip;
    },
    standardHeaders: true,
    legacyHeaders: false,
});

const strictLimiter = rateLimit({
    windowMs: 15 * 60 * 1000,
    max: 20,
    keyGenerator: (req) => {
        return req.headers['cf-connecting-ip'] || 
               req.headers['x-forwarded-for']?.split(',')[0] || 
               req.ip;
    }
});

const authLimiter = rateLimit({
    windowMs: 15 * 60 * 1000,
    max: process.env.NODE_ENV === 'production' ? 10 : 50, // Increase limit slightly
    message: 'Too many login attempts, please try again later.',
    keyGenerator: (req) => {
        return req.headers['cf-connecting-ip'] || 
               req.headers['x-forwarded-for']?.split(',')[0] || 
               req.ip;
    },
    standardHeaders: true,
    legacyHeaders: false,
});

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

// Authentication middleware
const requireAuth = (req, res, next) => {
    if (req.session && req.session.authenticated) {
        next();
    } else {
        if (req.xhr || req.headers.accept?.indexOf('json') > -1) {
            res.status(401).json({ success: false, error: 'Authentication required' });
        } else {
            res.redirect('/login.html');
        }
    }
};

// Admin middleware
const requireAdmin = (req, res, next) => {
    if (req.session && req.session.authenticated && req.session.isAdmin) {
        next();
    } else {
        if (req.xhr || req.headers.accept?.indexOf('json') > -1) {
            res.status(403).json({ success: false, error: 'Admin access required' });
        } else {
            res.redirect('/login.html');
        }
    }
};

// Serve login page without authentication
app.get('/login.html', (req, res) => {
    res.sendFile(path.join(__dirname, 'public', 'login.html'));
});

// Auth routes (no authentication required)
app.post('/api/auth/login', authLimiter, async (req, res) => {
    try {
        // Log request details for debugging
        logger.info('Login attempt:', {
            email: req.body.email,
            ip: req.ip,
            cfIp: req.headers['cf-connecting-ip'],
            forwardedFor: req.headers['x-forwarded-for'],
            userAgent: req.headers['user-agent']
        });
        
        const { email, password } = req.body;
        
        if (!email || !password) {
            return res.status(400).json({
                success: false,
                error: 'Email and password are required'
            });
        }
        
        // Validate email format
        const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
        if (!emailRegex.test(email)) {
            return res.status(400).json({
                success: false,
                error: 'Invalid email format'
            });
        }
        
        // Check if login sheet is configured
        if (!LOGIN_SHEET_ID) {
            logger.error('NOCODB_LOGIN_SHEET not configured or could not be parsed');
            return res.status(500).json({
                success: false,
                error: 'Authentication system not properly configured'
            });
        }
        
        // Fetch user from NocoDB
        const url = `${process.env.NOCODB_API_URL}/db/data/v1/${process.env.NOCODB_PROJECT_ID}/${LOGIN_SHEET_ID}`;
        
        logger.info(`Checking authentication for email: ${email}`);
        logger.debug(`Using login sheet API: ${url}`);
        
        const response = await axios.get(url, {
            headers: {
                'xc-token': process.env.NOCODB_API_TOKEN,
                'Content-Type': 'application/json'
            },
            params: {
                where: `(Email,eq,${email})`,
                limit: 1
            }
        });
        
        const users = response.data.list || [];
        
        if (users.length === 0) {
            logger.warn(`No user found with email: ${email}`);
            return res.status(401).json({
                success: false,
                error: 'Invalid email or password'
            });
        }
        
        const user = users[0];
        
        // Check password (plain text comparison for now)
        if (user.Password !== password && user.password !== password) {
            logger.warn(`Invalid password for email: ${email}`);
            return res.status(401).json({
                success: false,
                error: 'Invalid email or password'
            });
        }
        
        // Update last login time
        try {
            const updateUrl = `${process.env.NOCODB_API_URL}/db/data/v1/${process.env.NOCODB_PROJECT_ID}/${LOGIN_SHEET_ID}/${user.Id || user.id || user.ID}`;
            await axios.patch(updateUrl, {
                'Last Login': new Date().toISOString(),
                last_login: new Date().toISOString()
            }, {
                headers: {
                    'xc-token': process.env.NOCODB_API_TOKEN,
                    'Content-Type': 'application/json'
                }
            });
        } catch (updateError) {
            logger.warn('Failed to update last login time:', updateError.message);
            // Don't fail the login if we can't update last login time
        }
        
        // Set session including admin status
        req.session.authenticated = true;
        req.session.userEmail = email;
        req.session.userName = user.Name || user.name || email;
        req.session.isAdmin = user.Admin === true || user.Admin === 1 || user.admin === true || user.admin === 1;
        req.session.userId = user.Id || user.id || user.ID;
        
        // Force session save before sending response
        req.session.save((err) => {
            if (err) {
                logger.error('Session save error:', err);
                return res.status(500).json({
                    success: false,
                    error: 'Session error. Please try again.'
                });
            }
            
            logger.info(`User authenticated: ${email}, Admin: ${req.session.isAdmin}`);
            
            res.json({
                success: true,
                message: 'Login successful',
                user: {
                    email: email,
                    name: req.session.userName,
                    isAdmin: req.session.isAdmin
                }
            });
        });
        
    } catch (error) {
        logger.error('Login error:', error.message);
        res.status(500).json({
            success: false,
            error: 'Authentication service error. Please try again later.'
        });
    }
});

app.get('/api/auth/check', (req, res) => {
    res.json({
        authenticated: req.session?.authenticated || false,
        user: req.session?.authenticated ? {
            email: req.session.userEmail,
            name: req.session.userName,
            isAdmin: req.session.isAdmin || false
        } : null
    });
});

app.post('/api/auth/logout', (req, res) => {
    req.session.destroy((err) => {
        if (err) {
            logger.error('Logout error:', err);
            return res.status(500).json({
                success: false,
                error: 'Logout failed'
            });
        }
        res.json({
            success: true,
            message: 'Logged out successfully'
        });
    });
});

// Admin routes
// Serve admin page (protected)
app.get('/admin.html', requireAdmin, (req, res) => {
    res.sendFile(path.join(__dirname, 'public', 'admin.html'));
});

// Add admin API endpoint to update start location
app.post('/api/admin/start-location', requireAdmin, async (req, res) => {
    try {
        const { latitude, longitude, zoom } = req.body;
        
        // Validate input
        if (!latitude || !longitude) {
            return res.status(400).json({
                success: false,
                error: 'Latitude and longitude are required'
            });
        }
        
        const lat = parseFloat(latitude);
        const lng = parseFloat(longitude);
        const mapZoom = parseInt(zoom) || 11;
        
        if (isNaN(lat) || isNaN(lng) || lat < -90 || lat > 90 || lng < -180 || lng > 180) {
            return res.status(400).json({
                success: false,
                error: 'Invalid coordinates'
            });
        }
        
        if (!SETTINGS_SHEET_ID) {
            return res.status(500).json({
                success: false,
                error: 'Settings sheet not configured'
            });
        }
        
        // Get the most recent settings to preserve ALL fields
        let currentConfig = {};
        try {
            const response = await axios.get(
                `${process.env.NOCODB_API_URL}/db/data/v1/${process.env.NOCODB_PROJECT_ID}/${SETTINGS_SHEET_ID}`,
                {
                    headers: {
                        'xc-token': process.env.NOCODB_API_TOKEN,
                        'Content-Type': 'application/json'
                    },
                    params: {
                        sort: '-created_at',
                        limit: 1
                    }
                }
            );
            
            if (response.data?.list && response.data.list.length > 0) {
                currentConfig = response.data.list[0];
                logger.info('Loaded existing settings for preservation');
            }
        } catch (e) {
            logger.warn('Could not load existing settings, using defaults:', e.message);
        }
        
        // Create new settings row with updated location but preserve everything else
        const settingData = {
            created_at: new Date().toISOString(),
            created_by: req.session.userEmail,
            // Map location fields (what we're updating)
            'Geo-Location': `${lat};${lng}`,
            latitude: lat,
            longitude: lng,
            zoom: mapZoom,
            // Preserve all walk sheet fields
            walk_sheet_title: currentConfig.walk_sheet_title || 'Campaign Walk Sheet',
            walk_sheet_subtitle: currentConfig.walk_sheet_subtitle || 'Door-to-Door Canvassing Form',
            walk_sheet_footer: currentConfig.walk_sheet_footer || 'Thank you for your support!',
            qr_code_1_url: currentConfig.qr_code_1_url || '',
            qr_code_1_label: currentConfig.qr_code_1_label || '',
            qr_code_2_url: currentConfig.qr_code_2_url || '',
            qr_code_2_label: currentConfig.qr_code_2_label || '',
            qr_code_3_url: currentConfig.qr_code_3_url || '',
            qr_code_3_label: currentConfig.qr_code_3_label || ''
        };
        
        const createUrl = `${process.env.NOCODB_API_URL}/db/data/v1/${process.env.NOCODB_PROJECT_ID}/${SETTINGS_SHEET_ID}`;
        
        const createResponse = await axios.post(createUrl, settingData, {
            headers: {
                'xc-token': process.env.NOCODB_API_TOKEN,
                'Content-Type': 'application/json'
            }
        });
        
        logger.info('Created new settings row with start location');
        
        res.json({
            success: true,
            message: 'Start location saved successfully',
            location: { latitude: lat, longitude: lng, zoom: mapZoom },
            settingsId: createResponse.data.id || createResponse.data.Id || createResponse.data.ID
        });
        
    } catch (error) {
        logger.error('Error updating start location:', error);
        res.status(500).json({
            success: false,
            error: error.message || 'Failed to update start location'
        });
    }
});

// Get current start location (fetch most recent)
app.get('/api/admin/start-location', requireAdmin, async (req, res) => {
    try {
        // First try to get from database
        if (SETTINGS_SHEET_ID) {
            const url = `${process.env.NOCODB_API_URL}/db/data/v1/${process.env.NOCODB_PROJECT_ID}/${SETTINGS_SHEET_ID}`;
            
            const response = await axios.get(url, {
                headers: {
                    'xc-token': process.env.NOCODB_API_TOKEN
                },
                params: {
                    sort: '-created_at', // Get most recent
                    limit: 1
                }
            });
            
            const settings = response.data.list || [];
            
            if (settings.length > 0) {
                const setting = settings[0];
                
                // Try to extract coordinates
                let lat, lng, zoom;
                
                if (setting['Geo-Location']) {
                    const parts = setting['Geo-Location'].split(';');
                    if (parts.length === 2) {
                        lat = parseFloat(parts[0]);
                        lng = parseFloat(parts[1]);
                    }
                } else if (setting.latitude && setting.longitude) {
                    lat = parseFloat(setting.latitude);
                    lng = parseFloat(setting.longitude);
                }
                
                zoom = parseInt(setting.zoom) || 11;
                
                if (lat && lng && !isNaN(lat) && !isNaN(lng)) {
                    return res.json({
                        success: true,
                        location: {
                            latitude: lat,
                            longitude: lng,
                            zoom: zoom
                        },
                        source: 'database',
                        settingsId: setting.id || setting.Id || setting.ID,
                        lastUpdated: setting.created_at
                    });
                }
            }
        }
        
        // Fallback to environment variables
        res.json({
            success: true,
            location: {
                latitude: 53.5461,
                longitude: -113.4938,
                zoom: 11
            },
            source: 'defaults'
        });
        
    } catch (error) {
        logger.error('Error fetching start location:', error);
        
        // Return defaults on error
        res.json({
            success: true,
            location: {
                latitude: 53.5461,
                longitude: -113.4938,
                zoom: 11
            },
            source: 'defaults'
        });
    }
});

// Update the public config endpoint similarly
app.get('/api/config/start-location', async (req, res) => {
    try {
        // Try to get from database first
        if (SETTINGS_SHEET_ID) {
            const url = `${process.env.NOCODB_API_URL}/db/data/v1/${process.env.NOCODB_PROJECT_ID}/${SETTINGS_SHEET_ID}`;
            
            logger.info(`Fetching start location from settings sheet: ${SETTINGS_SHEET_ID}`);
            
            const response = await axios.get(url, {
                headers: {
                    'xc-token': process.env.NOCODB_API_TOKEN
                },
                params: {
                    sort: '-created_at', // Get most recent
                    limit: 1
                }
            });
            
            const settings = response.data.list || [];
            
            if (settings.length > 0) {
                const setting = settings[0];
                logger.info('Found settings row:', {
                    id: setting.id || setting.Id || setting.ID,
                    hasGeoLocation: !!setting['Geo-Location'],
                    hasLatLng: !!(setting.latitude && setting.longitude)
                });
                
                // Try to extract coordinates
                let lat, lng, zoom;
                
                if (setting['Geo-Location']) {
                    const parts = setting['Geo-Location'].split(';');
                    if (parts.length === 2) {
                        lat = parseFloat(parts[0]);
                        lng = parseFloat(parts[1]);
                    }
                } else if (setting.latitude && setting.longitude) {
                    lat = parseFloat(setting.latitude);
                    lng = parseFloat(setting.longitude);
                }
                
                zoom = parseInt(setting.zoom) || 11;
                
                if (lat && lng && !isNaN(lat) && !isNaN(lng)) {
                    logger.info(`Returning location from database: ${lat}, ${lng}, zoom: ${zoom}`);
                    return res.json({
                        latitude: lat,
                        longitude: lng,
                        zoom: zoom
                    });
                }
            } else {
                logger.info('No settings found in database');
            }
        } else {
            logger.info('Settings sheet not configured, using defaults');
        }
    } catch (error) {
        logger.error('Error fetching config start location:', error);
    }
    
    // Return defaults
    const defaultLat = parseFloat(process.env.DEFAULT_LAT) || 53.5461;
    const defaultLng = parseFloat(process.env.DEFAULT_LNG) || -113.4938;
    const defaultZoom = parseInt(process.env.DEFAULT_ZOOM) || 11;
    
    logger.info(`Using default start location: ${defaultLat}, ${defaultLng}, zoom: ${defaultZoom}`);
    
    res.json({
        latitude: defaultLat,
        longitude: defaultLng,
        zoom: defaultZoom
    });
});

// Get walk sheet configuration (load most recent)
app.get('/api/admin/walk-sheet-config', requireAdmin, async (req, res) => {
    try {
        // Default configuration
        const defaultConfig = {
            walk_sheet_title: 'Campaign Walk Sheet',
            walk_sheet_subtitle: 'Door-to-Door Canvassing Form',
            walk_sheet_footer: 'Thank you for your support!',
            qr_code_1_url: '',
            qr_code_1_label: '',
            qr_code_2_url: '',
            qr_code_2_label: '',
            qr_code_3_url: '',
            qr_code_3_label: ''
        };

        if (!SETTINGS_SHEET_ID) {
            logger.warn('SETTINGS_SHEET_ID not configured, returning defaults');
            return res.json({
                success: true,
                config: defaultConfig,
                source: 'defaults',
                message: 'Settings sheet not configured, using defaults'
            });
        }
        
        // Get ALL settings rows and find the most recent one with walk sheet data
        const response = await axios.get(
            `${process.env.NOCODB_API_URL}/db/data/v1/${process.env.NOCODB_PROJECT_ID}/${SETTINGS_SHEET_ID}`,
            {
                headers: {
                    'xc-token': process.env.NOCODB_API_TOKEN,
                    'Content-Type': 'application/json'
                },
                params: {
                    sort: '-created_at', // Sort by created_at descending
                    limit: 20 // Get more records to find one with walk sheet data
                }
            }
        );
        
        logger.debug('GET Settings response structure:', JSON.stringify(response.data, null, 2));
        
        if (!response.data?.list || response.data.list.length === 0) {
            logger.info('No settings found in database, returning defaults');
            return res.json({
                success: true,
                config: defaultConfig,
                source: 'defaults',
                message: 'No settings found in database'
            });
        }

        // Find the first row that has walk sheet configuration (not just location data)
        const settingsRow = response.data.list.find(row => 
            row.walk_sheet_title || 
            row.walk_sheet_subtitle || 
            row.walk_sheet_footer ||
            row.qr_code_1_url ||
            row.qr_code_2_url ||
            row.qr_code_3_url
        ) || response.data.list[0]; // Fallback to most recent if none have walk sheet data

        const walkSheetConfig = {
            walk_sheet_title: settingsRow.walk_sheet_title || settingsRow['Walk Sheet Title'] || defaultConfig.walk_sheet_title,
            walk_sheet_subtitle: settingsRow.walk_sheet_subtitle || settingsRow['Walk Sheet Subtitle'] || defaultConfig.walk_sheet_subtitle,
            walk_sheet_footer: settingsRow.walk_sheet_footer || settingsRow['Walk Sheet Footer'] || defaultConfig.walk_sheet_footer,
            qr_code_1_url: settingsRow.qr_code_1_url || settingsRow['QR Code 1 URL'] || defaultConfig.qr_code_1_url,
            qr_code_1_label: settingsRow.qr_code_1_label || settingsRow['QR Code 1 Label'] || defaultConfig.qr_code_1_label,
            qr_code_2_url: settingsRow.qr_code_2_url || settingsRow['QR Code 2 URL'] || defaultConfig.qr_code_2_url,
            qr_code_2_label: settingsRow.qr_code_2_label || settingsRow['QR Code 2 Label'] || defaultConfig.qr_code_2_label,
            qr_code_3_url: settingsRow.qr_code_3_url || settingsRow['QR Code 3 URL'] || defaultConfig.qr_code_3_url,
            qr_code_3_label: settingsRow.qr_code_3_label || settingsRow['QR Code 3 Label'] || defaultConfig.qr_code_3_label
        };
        
        logger.info(`Retrieved walk sheet config from database (ID: ${settingsRow.Id || settingsRow.id})`);
        res.json({
            success: true,
            config: walkSheetConfig,
            source: 'database',
            settingsId: settingsRow.id || settingsRow.Id || settingsRow.ID,
            lastUpdated: settingsRow.created_at || settingsRow.updated_at
        });
        
    } catch (error) {
        logger.error('Failed to get walk sheet config:', error);
        logger.error('Error details:', error.response?.data || error.message);
        
        // Return defaults if there's an error
        res.json({
            success: true,
            config: {
                walk_sheet_title: 'Campaign Walk Sheet',
                walk_sheet_subtitle: 'Door-to-Door Canvassing Form',
                walk_sheet_footer: 'Thank you for your support!',
                qr_code_1_url: '',
                qr_code_1_label: '',
                qr_code_2_url: '',
                qr_code_2_label: '',
                qr_code_3_url: '',
                qr_code_3_label: ''
            },
            source: 'defaults',
            message: 'Error retrieving from database, using defaults',
            error: error.message
        });
    }
});

// Save walk sheet configuration (always create new row)
app.post('/api/admin/walk-sheet-config', requireAdmin, async (req, res) => {
    try {
        if (!SETTINGS_SHEET_ID) {
            return res.status(500).json({
                success: false,
                error: 'Settings sheet not configured'
            });
        }
        
        logger.info('Using SETTINGS_SHEET_ID:', SETTINGS_SHEET_ID);
        
        const config = req.body;
        logger.info('Received walk sheet config:', JSON.stringify(config, null, 2));
        
        // Validate input
        if (!config || typeof config !== 'object') {
            return res.status(400).json({
                success: false,
                error: 'Invalid configuration data'
            });
        }
        
        // Get the most recent settings to preserve ALL fields
        let currentConfig = {};
        try {
            const response = await axios.get(
                `${process.env.NOCODB_API_URL}/db/data/v1/${process.env.NOCODB_PROJECT_ID}/${SETTINGS_SHEET_ID}`,
                {
                    headers: {
                        'xc-token': process.env.NOCODB_API_TOKEN,
                        'Content-Type': 'application/json'
                    },
                    params: {
                        sort: '-created_at',
                        limit: 1
                    }
                }
            );
            
            if (response.data?.list && response.data.list.length > 0) {
                currentConfig = response.data.list[0];
                logger.info('Loaded existing settings for preservation');
            }
        } catch (e) {
            logger.warn('Could not load existing settings, using defaults:', e.message);
        }
        
        const userEmail = req.session.userEmail;
        const timestamp = new Date().toISOString();
        
        // Prepare data for saving - include ALL fields
        const walkSheetData = {
            created_at: timestamp,
            created_by: userEmail,
            // Preserve map location fields from last saved config
            'Geo-Location': currentConfig['Geo-Location'] || currentConfig.geodata || '53.5461;-113.4938',
            latitude: currentConfig.latitude || 53.5461,
            longitude: currentConfig.longitude || -113.4938,
            zoom: currentConfig.zoom || 11,
            // Walk sheet fields (what we're updating)
            walk_sheet_title: (config.walk_sheet_title || '').toString().trim(),
            walk_sheet_subtitle: (config.walk_sheet_subtitle || '').toString().trim(),
            walk_sheet_footer: (config.walk_sheet_footer || '').toString().trim(),
            'Walk Sheet Title': (config.walk_sheet_title || '').toString().trim(),
            'Walk Sheet Subtitle': (config.walk_sheet_subtitle || '').toString().trim(),
            'Walk Sheet Footer': (config.walk_sheet_footer || '').toString().trim(),
            qr_code_1_url: validateUrl(config.qr_code_1_url),
            qr_code_1_label: (config.qr_code_1_label || '').toString().trim(),
            qr_code_2_url: validateUrl(config.qr_code_2_url),
            qr_code_2_label: (config.qr_code_2_label || '').toString().trim(),
            qr_code_3_url: validateUrl(config.qr_code_3_url),
            qr_code_3_label: (config.qr_code_3_label || '').toString().trim(),
            'QR Code 1 URL': validateUrl(config.qr_code_1_url),
            'QR Code 1 Label': (config.qr_code_1_label || '').toString().trim(),
            'QR Code 2 URL': validateUrl(config.qr_code_2_url),
            'QR Code 2 Label': (config.qr_code_2_label || '').toString().trim(),
            'QR Code 3 URL': validateUrl(config.qr_code_3_url),
            'QR Code 3 Label': (config.qr_code_3_label || '').toString().trim()
        };
        
        logger.info('Prepared walk sheet data for saving:', JSON.stringify(walkSheetData, null, 2));
        
        // Create new settings row
        const response = await axios.post(
            `${process.env.NOCODB_API_URL}/db/data/v1/${process.env.NOCODB_PROJECT_ID}/${SETTINGS_SHEET_ID}`,
            walkSheetData,
            {
                headers: {
                    'xc-token': process.env.NOCODB_API_TOKEN,
                    'Content-Type': 'application/json'
                }
            }
        );
        
        logger.info('NocoDB create response:', JSON.stringify(response.data, null, 2));
        
        const newId = response.data.id || response.data.Id || response.data.ID;
        
        res.json({
            success: true,
            message: 'Walk sheet configuration saved successfully',
            config: walkSheetData,
            settingsId: newId,
            timestamp: timestamp
        });
        
    } catch (error) {
        logger.error('Failed to save walk sheet config:', error);
        logger.error('Error response:', error.response?.data);
        logger.error('Request URL:', error.config?.url);
        
        // Provide more detailed error information
        let errorMessage = 'Failed to save walk sheet configuration';
        let errorDetails = null;
        
        if (error.response?.data) {
            if (error.response.data.message) {
                errorMessage = error.response.data.message;
            }
            if (error.response.data.errors) {
                errorDetails = error.response.data.errors;
            }
        }
        
        res.status(500).json({
            success: false,
            error: errorMessage,
            details: errorDetails,
            timestamp: new Date().toISOString()
        });
    }
});

// Helper function to validate URLs
function validateUrl(url) {
    if (!url || typeof url !== 'string') {
        return '';
    }
    
    const trimmed = url.trim();
    if (!trimmed) {
        return '';
    }
    
    // Basic URL validation
    try {
        new URL(trimmed);
        return trimmed;
    } catch (e) {
        // If not a valid URL, check if it's a relative path or missing protocol
        if (trimmed.startsWith('/') || !trimmed.includes('://')) {
            // For relative paths or missing protocol, return as-is
            return trimmed;
        }
        logger.warn('Invalid URL provided:', trimmed);
        return '';
    }
}

// Debug session endpoint
app.get('/api/debug/session', (req, res) => {
    res.json({
        sessionID: req.sessionID,
        session: req.session,
        cookies: req.cookies,
        authenticated: req.session?.authenticated || false
    });
});

// Serve static files with authentication for main app
app.use(express.static(path.join(__dirname, 'public'), {
    index: false // Don't serve index.html automatically
}));

// Protect main app routes
app.get('/', requireAuth, (req, res) => {
    res.sendFile(path.join(__dirname, 'public', 'index.html'));
});

// Add geocoding routes (protected)
app.use('/api/geocode', requireAuth, geocodingRoutes);

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

// Health check endpoint (no auth required)
app.get('/health', (req, res) => {
    res.json({ 
        status: 'healthy', 
        timestamp: new Date().toISOString(),
        version: process.env.npm_package_version || '1.0.0'
    });
});

// Configuration validation endpoint (protected)
app.get('/api/config-check', requireAuth, (req, res) => {
    const config = {
        hasApiUrl: !!process.env.NOCODB_API_URL,
        hasApiToken: !!process.env.NOCODB_API_TOKEN,
        hasProjectId: !!process.env.NOCODB_PROJECT_ID,
        hasTableId: !!process.env.NOCODB_TABLE_ID,
        hasLoginSheet: !!LOGIN_SHEET_ID,
        hasSettingsSheet: !!SETTINGS_SHEET_ID,
        projectId: process.env.NOCODB_PROJECT_ID,
        tableId: process.env.NOCODB_TABLE_ID,
        loginSheet: LOGIN_SHEET_ID,
        loginSheetConfigured: process.env.NOCODB_LOGIN_SHEET,
        settingsSheet: SETTINGS_SHEET_ID,
        settingsSheetConfigured: process.env.NOCODB_SETTINGS_SHEET,
        nodeEnv: process.env.NODE_ENV
    };
    
    const isConfigured = config.hasApiUrl && config.hasApiToken && config.hasProjectId && config.hasTableId;
    
    res.json({
        configured: isConfigured,
        ...config
    });
});

// All other API routes require authentication
app.use('/api/*', requireAuth);

// Get all locations from NocoDB
app.get('/api/locations', async (req, res) => {
    try {
        const { limit = 1000, offset = 0, where } = req.query;
        
        const url = `${process.env.NOCODB_API_URL}/db/data/v1/${process.env.NOCODB_PROJECT_ID}/${process.env.NOCODB_TABLE_ID}`;
        
        const params = new URLSearchParams({
            limit,
            offset
        });
        
        if (where) {
            params.append('where', where);
        }
        
        logger.info(`Fetching locations from NocoDB: ${url}`);
        
        const response = await axios.get(`${url}?${params}`, {
            headers: {
                'xc-token': process.env.NOCODB_API_TOKEN,
                'Content-Type': 'application/json'
            },
            timeout: 10000 // 10 second timeout
        });
        
        // Process locations to ensure they have required fields
        const locations = response.data.list || [];
        
        // Log the structure of the first location to debug ID field name
        if (locations.length > 0) {
            const sampleLocation = locations[0];
            logger.info('Sample location structure:', {
                keys: Object.keys(sampleLocation),
                idFields: {
                    'Id': sampleLocation.Id,
                    'id': sampleLocation.id,
                    'ID': sampleLocation.ID,
                    '_id': sampleLocation._id
                }
            });
        }
        
        const validLocations = locations.filter(loc => {
            // Apply geo field synchronization to each location
            loc = syncGeoFields(loc);
            
            // Check if location has valid coordinates
            if (loc.latitude && loc.longitude) {
                return true;
            }
            
            // Try to parse from geodata column (semicolon-separated)
            if (loc.geodata && typeof loc.geodata === 'string') {
                const parts = loc.geodata.split(';');
                if (parts.length === 2) {
                    loc.latitude = parseFloat(parts[0]);
                    loc.longitude = parseFloat(parts[1]);
                    return !isNaN(loc.latitude) && !isNaN(loc.longitude);
                }
            }
            
            return false;
        });
        
        logger.info(`Retrieved ${validLocations.length} valid locations out of ${locations.length} total`);
        
        res.json({
            success: true,
            count: validLocations.length,
            total: response.data.pageInfo?.totalRows || validLocations.length,
            locations: validLocations
        });
        
    } catch (error) {
        logger.error('Error fetching locations:', error.message);
        
        if (error.response) {
            // NocoDB API error
            res.status(error.response.status).json({
                success: false,
                error: 'Failed to fetch data from NocoDB',
                details: error.response.data
            });
        } else if (error.code === 'ECONNABORTED') {
            // Timeout
            res.status(504).json({
                success: false,
                error: 'Request timeout'
            });
        } else {
            // Other errors
            res.status(500).json({
                success: false,
                error: 'Internal server error'
            });
        }
    }
});

// Get single location by ID
app.get('/api/locations/:id', async (req, res) => {
    try {
        const url = `${process.env.NOCODB_API_URL}/db/data/v1/${process.env.NOCODB_PROJECT_ID}/${process.env.NOCODB_TABLE_ID}/${req.params.id}`;
        
        const response = await axios.get(url, {
            headers: {
                'xc-token': process.env.NOCODB_API_TOKEN
            }
        });
        
        res.json({
            success: true,
            location: response.data
        });
        
    } catch (error) {
        logger.error(`Error fetching location ${req.params.id}:`, error.message);
        res.status(error.response?.status || 500).json({
            success: false,
            error: 'Failed to fetch location'
        });
    }
});

// Create new location
app.post('/api/locations', strictLimiter, async (req, res) => {
    try {
        let locationData = { ...req.body };
        
        // Sync geo fields before validation
        locationData = syncGeoFields(locationData);
        
        const { latitude, longitude, ...additionalData } = locationData;
        
        // Validate coordinates
        if (!latitude || !longitude) {
            return res.status(400).json({
                success: false,
                error: 'Latitude and longitude are required'
            });
        }
        
        const lat = parseFloat(latitude);
        const lng = parseFloat(longitude);
        
        if (isNaN(lat) || isNaN(lng)) {
            return res.status(400).json({
                success: false,
                error: 'Invalid coordinate values'
            });
        }
        
        if (lat < -90 || lat > 90) {
            return res.status(400).json({
                success: false,
                error: 'Latitude must be between -90 and 90'
            });
        }
        
        if (lng < -180 || lng > 180) {
            return res.status(400).json({
                success: false,
                error: 'Longitude must be between -180 and 180'
            });
        }
        
        // Check bounds if configured
        if (process.env.BOUND_NORTH) {
            const bounds = {
                north: parseFloat(process.env.BOUND_NORTH),
                south: parseFloat(process.env.BOUND_SOUTH),
                east: parseFloat(process.env.BOUND_EAST),
                west: parseFloat(process.env.BOUND_WEST)
            };
            
            if (lat > bounds.north || lat < bounds.south || 
                lng > bounds.east || lng < bounds.west) {
                return res.status(400).json({
                    success: false,
                    error: 'Location is outside allowed bounds'
                });
            }
        }
        
        // Format geodata in both formats for compatibility
        const geodata = `${lat};${lng}`;
        const geoLocation = `${lat};${lng}`; // Use semicolon format for NocoDB GeoData column
        
        // Prepare data for NocoDB
        const finalData = {
            geodata,
            'Geo-Location': geoLocation,
            latitude: lat,
            longitude: lng,
            ...additionalData,
            created_at: new Date().toISOString(),
            created_by: req.session.userEmail // Track who created the location
        };
        
        const url = `${process.env.NOCODB_API_URL}/db/data/v1/${process.env.NOCODB_PROJECT_ID}/${process.env.NOCODB_TABLE_ID}`;
        
        logger.info('Creating new location:', { lat, lng });
        
        const response = await axios.post(url, finalData, {
            headers: {
                'xc-token': process.env.NOCODB_API_TOKEN,
                'Content-Type': 'application/json'
            }
        });
        
        logger.info('Location created successfully:', response.data.id);
        
        res.status(201).json({
            success: true,
            location: response.data
        });
        
    } catch (error) {
        logger.error('Error creating location:', error.message);
        
        if (error.response) {
            res.status(error.response.status).json({
                success: false,
                error: 'Failed to save location to NocoDB',
                details: error.response.data
            });
        } else {
            res.status(500).json({
                success: false,
                error: 'Internal server error'
            });
        }
    }
});

// Update location
app.put('/api/locations/:id', strictLimiter, async (req, res) => {
    try {
        const locationId = req.params.id;
        
        // Validate ID
        if (!locationId || locationId === 'undefined' || locationId === 'null') {
            return res.status(400).json({
                success: false,
                error: 'Invalid location ID'
            });
        }
        
        let updateData = { ...req.body };
        
        // Remove ID from update data to avoid conflicts - use the correct field name
        delete updateData.ID;
        delete updateData.Id;
        delete updateData.id;
        delete updateData._id;
        
        // Sync geo fields
        updateData = syncGeoFields(updateData);
        
        updateData.last_updated_at = new Date().toISOString();
        updateData.last_updated_by = req.session.userEmail; // Track who updated
        
        const url = `${process.env.NOCODB_API_URL}/db/data/v1/${process.env.NOCODB_PROJECT_ID}/${process.env.NOCODB_TABLE_ID}/${locationId}`;
        
        logger.info(`Updating location ${locationId} by ${req.session.userEmail}`);
        logger.debug('Update data:', updateData);
        
        const response = await axios.patch(url, updateData, {
            headers: {
                'xc-token': process.env.NOCODB_API_TOKEN,
                'Content-Type': 'application/json'
            }
        });
        
        res.json({
            success: true,
            location: response.data
        });
        
    } catch (error) {
        logger.error(`Error updating location ${req.params.id}:`, error.message);
        if (error.response) {
            logger.error('Error response:', error.response.data);
        }
        
        res.status(error.response?.status || 500).json({
            success: false,
            error: 'Failed to update location',
            details: error.response?.data?.message || error.message
        });
    }
});

// Delete location
app.delete('/api/locations/:id', strictLimiter, async (req, res) => {
    try {
        const locationId = req.params.id;
        
        // Validate ID
        if (!locationId || locationId === 'undefined' || locationId === 'null') {
            return res.status(400).json({
                success: false,
                error: 'Invalid location ID'
            });
        }
        
        const url = `${process.env.NOCODB_API_URL}/db/data/v1/${process.env.NOCODB_PROJECT_ID}/${process.env.NOCODB_TABLE_ID}/${locationId}`;
        
        await axios.delete(url, {
            headers: {
                'xc-token': process.env.NOCODB_API_TOKEN
            }
        });
        
        logger.info(`Location ${locationId} deleted by ${req.session.userEmail}`);
        
        res.json({
            success: true,
            message: 'Location deleted successfully'
        });
        
    } catch (error) {
        logger.error(`Error deleting location ${req.params.id}:`, error.message);
        res.status(error.response?.status || 500).json({
            success: false,
            error: 'Failed to delete location'
        });
    }
});

// Add a debug endpoint to check table structure
app.get('/api/debug/table-structure', requireAdmin, async (req, res) => {
    try {
        const url = `${process.env.NOCODB_API_URL}/db/data/v1/${process.env.NOCODB_PROJECT_ID}/${process.env.NOCODB_TABLE_ID}`;
        
        const response = await axios.get(url, {
            headers: {
                'xc-token': process.env.NOCODB_API_TOKEN
            },
            params: {
                limit: 1
            }
        });
        
        const sample = response.data.list?.[0] || {};
        
        res.json({
            success: true,
            fields: Object.keys(sample),
            sampleRecord: sample,
            idField: sample.ID ? 'ID' : (sample.Id ? 'Id' : (sample.id ? 'id' : 'unknown'))
        });
        
    } catch (error) {
        logger.error('Error checking table structure:', error);
        res.status(500).json({
            success: false,
            error: 'Failed to check table structure'
        });
    }
});

// QR code generation test endpoint (local only, no upload)
app.get('/api/debug/test-qr', requireAdmin, async (req, res) => {
    try {
        const testUrl = req.query.url || 'https://example.com/test';
        const testSize = parseInt(req.query.size) || 200;
        
        logger.info('Testing local QR code generation...');
        
        const qrOptions = {
            type: 'png',
            width: testSize,
            margin: 1,
            color: {
                dark: '#000000',
                light: '#FFFFFF'
            },
            errorCorrectionLevel: 'M'
        };
        
        const buffer = await generateQRCode(testUrl, qrOptions);
        
        res.set({
            'Content-Type': 'image/png',
            'Content-Length': buffer.length
        });
        
        res.send(buffer);
        
    } catch (error) {
        logger.error('QR code test failed:', error);
        res.status(500).json({
            success: false,
            error: error.message
        });
    }
});

// Local QR code generation endpoint
app.get('/api/qr', async (req, res) => {
    try {
        const { text, size = 200 } = req.query;
        
        if (!text) {
            return res.status(400).json({
                success: false,
                error: 'Text parameter is required'
            });
        }
        
        const { generateQRCode } = require('./services/qrcode');
        
        const qrOptions = {
            type: 'png',
            width: parseInt(size),
            margin: 1,
            color: {
                dark: '#000000',
                light: '#FFFFFF'
            },
            errorCorrectionLevel: 'M'
        };
        
        const buffer = await generateQRCode(text, qrOptions);
        
        res.set({
            'Content-Type': 'image/png',
            'Content-Length': buffer.length,
            'Cache-Control': 'public, max-age=3600' // Cache for 1 hour
        });
        
        res.send(buffer);
        
    } catch (error) {
        logger.error('QR code generation error:', error);
        res.status(500).json({
            success: false,
            error: 'Failed to generate QR code'
        });
    }
});

// Simple QR test page
app.get('/test-qr', (req, res) => {
    res.send(`
<!DOCTYPE html>
<html>
<head>
    <title>QR Code Test</title>
    <style>
        body { font-family: Arial, sans-serif; padding: 20px; }
        .test-section { margin: 20px 0; padding: 20px; border: 1px solid #ddd; }
        input { padding: 10px; width: 300px; margin-right: 10px; }
        button { padding: 10px 20px; }
        #qr-result { margin-top: 20px; }
    </style>
</head>
<body>
    <h1>QR Code Generation Test</h1>
    
    <div class="test-section">
        <h3>Test 1: Direct API Call</h3>
        <p>Try accessing these URLs directly:</p>
        <ul>
            <li><a href="/api/qr?text=Hello World" target="_blank">/api/qr?text=Hello World</a></li>
            <li><a href="/api/qr?text=https://example.com&size=200" target="_blank">/api/qr?text=https://example.com&size=200</a></li>
        </ul>
    </div>
    
    <div class="test-section">
        <h3>Test 2: Dynamic Generation</h3>
        <input type="text" id="qr-text" placeholder="Enter text or URL" value="https://map.cmlite.org">
        <button onclick="generateQR()">Generate QR Code</button>
        <div id="qr-result"></div>
    </div>

    <div class="test-section">
        <h3>Test 3: Using QRCode Library (like admin panel)</h3>
        <input type="text" id="qr-text-canvas" placeholder="Enter text or URL" value="Test QR Code">
        <button onclick="generateQRCanvas()">Generate QR Code (Canvas)</button>
        <div id="qr-canvas-result"></div>
    </div>

    <script>
        // Test direct image loading
        function generateQR() {
            const text = document.getElementById('qr-text').value;
            const resultDiv = document.getElementById('qr-result');
            
            if (!text) {
                resultDiv.innerHTML = '<p style="color: red;">Please enter some text</p>';
                return;
            }
            
            const qrUrl = \`/api/qr?text=\${encodeURIComponent(text)}&size=200\`;
            resultDiv.innerHTML = \`
                <p>QR Code for: <strong>\${text}</strong></p>
                <img src="\${qrUrl}" alt="QR Code" style="border: 1px solid #ddd;">
                <p><small>URL: <code>\${qrUrl}</code></small></p>
            \`;
        }
        
        // Test using our QRCode library implementation
        function generateQRCanvas() {
            const text = document.getElementById('qr-text-canvas').value;
            const resultDiv = document.getElementById('qr-canvas-result');
            
            if (!text) {
                resultDiv.innerHTML = '<p style="color: red;">Please enter some text</p>';
                return;
            }
            
            // Create canvas element
            const canvas = document.createElement('canvas');
            resultDiv.innerHTML = \`<p>QR Code for: <strong>\${text}</strong></p>\`;
            resultDiv.appendChild(canvas);
            
            // Use our QRCode implementation (same as admin panel)
            window.QRCode = {
                toCanvas: function(canvas, text, options, callback) {
                    if (typeof options === 'function') {
                        callback = options;
                        options = {};
                    }
                    
                    const size = options.width || 200;
                    const qrUrl = \`/api/qr?text=\${encodeURIComponent(text)}&size=\${size}\`;
                    
                    const img = new Image();
                    img.crossOrigin = 'anonymous';
                    
                    img.onload = function() {
                        canvas.width = size;
                        canvas.height = size;
                        
                        const ctx = canvas.getContext('2d');
                        ctx.drawImage(img, 0, 0, size, size);
                        
                        if (callback) callback(null);
                    };
                    
                    img.onerror = function() {
                        console.error('Failed to load QR code from server');
                        
                        // Fallback: draw a simple placeholder
                        canvas.width = size;
                        canvas.height = size;
                        
                        const ctx = canvas.getContext('2d');
                        ctx.fillStyle = '#ffffff';
                        ctx.fillRect(0, 0, size, size);
                        
                        ctx.fillStyle = '#000000';
                        ctx.font = '12px Arial';
                        ctx.textAlign = 'center';
                        ctx.fillText('QR Code', size/2, size/2 - 10);
                        ctx.fillText('(Failed)', size/2, size/2 + 10);
                        
                        if (callback) callback(new Error('Failed to load QR code'));
                    };
                    
                    img.src = qrUrl;
                }
            };
            
            QRCode.toCanvas(canvas, text, { width: 200 }, function(error) {
                if (error) {
                    console.error('QR generation error:', error);
                    resultDiv.innerHTML += '<p style="color: red;">Failed to generate QR code</p>';
                } else {
                    console.log('QR code generated successfully');
                }
            });
        }
        
        // Auto-generate on page load
        generateQR();
    </script>
</body>
</html>
    `);
});

// Debug walk sheet configuration endpoint
app.get('/api/debug/walk-sheet-config', requireAdmin, async (req, res) => {
    try {
        const debugInfo = {
            settingsSheetId: SETTINGS_SHEET_ID,
            settingsSheetConfigured: process.env.NOCODB_SETTINGS_SHEET,
            hasSettingsSheet: !!SETTINGS_SHEET_ID,
            timestamp: new Date().toISOString()
        };

        if (!SETTINGS_SHEET_ID) {
            return res.json({
                success: true,
                debug: debugInfo,
                message: 'Settings sheet not configured'
            });
        }

        // Test connection to settings sheet
        const testUrl = `${process.env.NOCODB_API_URL}/db/data/v1/${process.env.NOCODB_PROJECT_ID}/${SETTINGS_SHEET_ID}`;
        
        const response = await axios.get(testUrl, {
            headers: {
                'xc-token': process.env.NOCODB_API_TOKEN,
                'Content-Type': 'application/json'
            },
            params: {
                limit: 5,
                sort: '-created_at'
            }
        });

        const records = response.data.list || [];
        const sampleRecord = records[0] || {};

        res.json({
            success: true,
            debug: {
                ...debugInfo,
                connectionTest: 'success',
                recordCount: records.length,
                availableFields: Object.keys(sampleRecord),
                sampleRecord: sampleRecord,
                recentRecords: records.slice(0, 3).map(r => ({
                    id: r.id || r.Id || r.ID,
                    created_at: r.created_at,
                    walk_sheet_title: r.walk_sheet_title,
                    hasQrCodes: !!(r.qr_code_1_url || r.qr_code_2_url || r.qr_code_3_url)
                }))
            }
        });

    } catch (error) {
        logger.error('Error debugging walk sheet config:', error);
        res.json({
            success: false,
            debug: {
                settingsSheetId: SETTINGS_SHEET_ID,
                settingsSheetConfigured: process.env.NOCODB_SETTINGS_SHEET,
                hasSettingsSheet: !!SETTINGS_SHEET_ID,
                timestamp: new Date().toISOString(),
                error: error.message,
                errorDetails: error.response?.data
            }
        });
    }
});

// Test walk sheet configuration endpoint
app.post('/api/debug/test-walk-sheet-save', requireAdmin, async (req, res) => {
    try {
        const testConfig = {
            walk_sheet_title: 'Test Walk Sheet',
            walk_sheet_subtitle: 'Test Subtitle',
            walk_sheet_footer: 'Test Footer',
            qr_code_1_url: 'https://example.com/test1',
            qr_code_1_label: 'Test QR 1',
            qr_code_2_url: 'https://example.com/test2',
            qr_code_2_label: 'Test QR 2',
            qr_code_3_url: 'https://example.com/test3',
            qr_code_3_label: 'Test QR 3'
        };

        logger.info('Testing walk sheet configuration save...');

        // Create a test request object
        const testReq = {
            body: testConfig,
            session: {
                userEmail: req.session.userEmail,
                authenticated: true,
                isAdmin: true
            }
        };

        // Create a test response object
        let testResult = null;
        let testError = null;
        
        const testRes = {
            json: (data) => { testResult = data; },
            status: (code) => ({ 
                json: (data) => { 
                    testResult = data; 
                    testResult.statusCode = code; 
                } 
            })
        };

        // Test the save functionality
        if (!SETTINGS_SHEET_ID) {
            return res.json({
                success: false,
                test: 'failed',
                error: 'Settings sheet not configured',
                config: testConfig
            });
        }

        const userEmail = req.session.userEmail;
        const timestamp = new Date().toISOString();
        
        const walkSheetData = {
            created_at: timestamp,
            created_by: userEmail,
            walk_sheet_title: testConfig.walk_sheet_title,
            walk_sheet_subtitle: testConfig.walk_sheet_subtitle,
            walk_sheet_footer: testConfig.walk_sheet_footer,
            qr_code_1_url: testConfig.qr_code_1_url,
            qr_code_1_label: testConfig.qr_code_1_label,
            qr_code_2_url: testConfig.qr_code_2_url,
            qr_code_2_label: testConfig.qr_code_2_label,
            qr_code_3_url: testConfig.qr_code_3_url,
            qr_code_3_label: testConfig.qr_code_3_label
        };

        const response = await axios.post(
            `${process.env.NOCODB_API_URL}/db/data/v1/${process.env.NOCODB_PROJECT_ID}/${SETTINGS_SHEET_ID}`,
            walkSheetData,
            {
                headers: {
                    'xc-token': process.env.NOCODB_API_TOKEN,
                    'Content-Type': 'application/json'
                }
            }
        );

        res.json({
            success: true,
            test: 'passed',
            message: 'Test walk sheet configuration saved successfully',
            testData: walkSheetData,
            saveResponse: response.data,
            settingsId: response.data.id || response.data.Id || response.data.ID
        });

    } catch (error) {
        logger.error('Test walk sheet save failed:', error);
        res.json({
            success: false,
            test: 'failed',
            error: error.message,
            errorDetails: error.response?.data,
            timestamp: new Date().toISOString()
        });
    }
});

// Debug endpoint to see raw walk sheet data
app.get('/api/debug/walk-sheet-raw', requireAdmin, async (req, res) => {
    try {
        if (!SETTINGS_SHEET_ID) {
            return res.json({ error: 'No settings sheet ID configured' });
        }

        const response = await axios.get(
            `${process.env.NOCODB_API_URL}/db/data/v1/${process.env.NOCODB_PROJECT_ID}/${SETTINGS_SHEET_ID}`,
            {
                headers: {
                    'xc-token': process.env.NOCODB_API_TOKEN
                },
                params: {
                    sort: '-created_at',
                    limit: 5
                }
            }
        );

        return res.json({
            success: true,
            tableId: SETTINGS_SHEET_ID,
            records: response.data?.list || [],
            count: response.data?.list?.length || 0
        });
    } catch (error) {
        logger.error('Error fetching raw walk sheet data:', error);
        return res.status(500).json({
            success: false,
            error: error.message
        });
    }
});

// Admin user management endpoints
app.get('/api/admin/users', requireAdmin, async (req, res) => {
    try {
        if (!LOGIN_SHEET_ID) {
            return res.status(500).json({
                success: false,
                error: 'Login sheet not configured'
            });
        }
        
        const url = `${process.env.NOCODB_API_URL}/db/data/v1/${process.env.NOCODB_PROJECT_ID}/${LOGIN_SHEET_ID}`;
        
        const response = await axios.get(url, {
            headers: {
                'xc-token': process.env.NOCODB_API_TOKEN,
                'Content-Type': 'application/json'
            },
            params: {
                limit: 100,
                sort: '-created_at'
            }
        });
        
        const users = response.data.list || [];
        
        // Remove password field from response for security
        const safeUsers = users.map(user => {
            const { Password, password, ...safeUser } = user;
            return safeUser;
        });
        
        res.json({
            success: true,
            users: safeUsers
        });
        
    } catch (error) {
        logger.error('Error fetching users:', error);
        res.status(500).json({
            success: false,
            error: 'Failed to fetch users'
        });
    }
});

app.post('/api/admin/users', requireAdmin, async (req, res) => {
    try {
        const { email, password, name, admin } = req.body;
        
        if (!email || !password) {
            return res.status(400).json({
                success: false,
                error: 'Email and password are required'
            });
        }
        
        if (!LOGIN_SHEET_ID) {
            return res.status(500).json({
                success: false,
                error: 'Login sheet not configured'
            });
        }
        
        // Check if user already exists
        const checkUrl = `${process.env.NOCODB_API_URL}/db/data/v1/${process.env.NOCODB_PROJECT_ID}/${LOGIN_SHEET_ID}`;
        
        const checkResponse = await axios.get(checkUrl, {
            headers: {
                'xc-token': process.env.NOCODB_API_TOKEN,
                'Content-Type': 'application/json'
            },
            params: {
                where: `(Email,eq,${email})`,
                limit: 1
            }
        });
        
        if (checkResponse.data.list && checkResponse.data.list.length > 0) {
            return res.status(400).json({
                success: false,
                error: 'User with this email already exists'
            });
        }
        
        // Create new user
        const userData = {
            Email: email,
            email: email,
            Password: password,
            password: password,
            Name: name || '',
            name: name || '',
            Admin: admin === true,
            admin: admin === true,
            'Created At': new Date().toISOString(),
            created_at: new Date().toISOString()
        };
        
        const response = await axios.post(checkUrl, userData, {
            headers: {
                'xc-token': process.env.NOCODB_API_TOKEN,
                'Content-Type': 'application/json'
            }
        });
        
        res.status(201).json({
            success: true,
            message: 'User created successfully',
            user: {
                id: response.data.Id || response.data.id || response.data.ID,
                email: email,
                name: name,
                admin: admin
            }
        });
        
    } catch (error) {
        logger.error('Error creating user:', error);
        res.status(500).json({
            success: false,
            error: 'Failed to create user'
        });
    }
});

app.delete('/api/admin/users/:id', requireAdmin, async (req, res) => {
    try {
        const userId = req.params.id;
        
        if (!LOGIN_SHEET_ID) {
            return res.status(500).json({
                success: false,
                error: 'Login sheet not configured'
            });
        }
        
        // Don't allow admins to delete themselves
        if (userId === req.session.userId) {
            return res.status(400).json({
                success: false,
                error: 'Cannot delete your own account'
            });
        }
        
        const url = `${process.env.NOCODB_API_URL}/db/data/v1/${process.env.NOCODB_PROJECT_ID}/${LOGIN_SHEET_ID}/${userId}`;
        
        await axios.delete(url, {
            headers: {
                'xc-token': process.env.NOCODB_API_TOKEN
            }
        });
        
        res.json({
            success: true,
            message: 'User deleted successfully'
        });
        
    } catch (error) {
        logger.error('Error deleting user:', error);
        res.status(500).json({
            success: false,
            error: 'Failed to delete user'
        });
    }
});

// Error handling middleware
app.use((err, req, res, next) => {
    logger.error('Unhandled error:', err);
    res.status(500).json({
        success: false,
        error: 'Internal server error'
    });
});

// Start server
app.listen(PORT, () => {
    logger.info(`
    ╔════════════════════════════════════════╗
    ║     BNKops Map Server                  ║
    ╠════════════════════════════════════════╣
    ║  Status: Running                       ║
    ║  Port: ${PORT}                            ║
    ║  Environment: ${process.env.NODE_ENV || 'development'}          ║
    ║  Project ID: ${process.env.NOCODB_PROJECT_ID}             ║
    ║  Table ID: ${process.env.NOCODB_TABLE_ID}               ║
    ║  Login Sheet: ${LOGIN_SHEET_ID || 'Not Configured'}              ║
    ║  Time: ${new Date().toISOString()}    ║
    ╚════════════════════════════════════════╝
    `);
});

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

