const express = require('express');
// Force server update
const path = require('path');
const fs = require('fs').promises;
const fsSync = require('fs');
const { exec } = require('child_process');
const util = require('util');
const multer = require('multer');
// const bcrypt = require('bcrypt'); // No longer needed for OTP login
const crypto = require('crypto');
const transporter = require('./utils/mailer');
const archiver = require('archiver');
const rateLimit = require('express-rate-limit');
const helmet = require('helmet');
const session = require('express-session');
const FileStore = require('session-file-store')(session);
const winston = require('winston');
const { body, param, validationResult } = require('express-validator');
require('dotenv').config({ quiet: true });

let isScanning = false;

function triggerBackgroundDiskScan(cacheFile, homeDir, quotaKb) {
    isScanning = true;
    exec(`du -s "${homeDir}" 2>/dev/null`, { timeout: 180000 }, (err, stdout) => {
        isScanning = false;
        let usageStr = 'N/A';
        if (stdout) {
            const parts = stdout.trim().split(/\s+/);
            const usedKb = parseInt(parts[0], 10);
            if (!isNaN(usedKb)) {
                const percentage = Math.round((usedKb / quotaKb) * 100);
                const usedGb = (usedKb / (1024 * 1024)).toFixed(2);
                const quotaGb = (quotaKb / (1024 * 1024)).toFixed(2).replace(/\.00$/, '');
                usageStr = `${usedGb} gb / ${quotaGb} gb (${percentage}%)`;
            }
        }
        try {
            fsSync.writeFileSync(cacheFile, usageStr, 'utf8');
        } catch (e) {
            console.error("Error writing disk cache file:", e);
        }
    });
}

// Configure Winston logger for sensitive data redaction
const logger = winston.createLogger({
    level: 'info',
    format: winston.format.combine(
        winston.format.timestamp(),
        winston.format.printf(({ timestamp, level, message }) => {
            let cleanMessage = typeof message === 'string' ? message : JSON.stringify(message);
            // Redact 6-digit numeric OTPs
            cleanMessage = cleanMessage.replace(/\b\d{6}\b/g, '[REDACTED_OTP]');
            // Redact raw email addresses
            cleanMessage = cleanMessage.replace(/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g, '[REDACTED_EMAIL]');
            // Redact credentials
            cleanMessage = cleanMessage.replace(/(email|username|secret|token|pass|password|cookie)=[^\s&"']+/gi, '$1=[REDACTED]');
            return `[${timestamp}] [${level.toUpperCase()}]: ${cleanMessage}`;
        })
    ),
    transports: [
        new winston.transports.Console(),
        new winston.transports.File({ filename: 'production_server.log' })
    ]
});

// Overwrite global console to use Winston for automatic sanitization across the app
console.log = (...args) => logger.info(args.map(arg => typeof arg === 'object' ? JSON.stringify(arg) : arg).join(' '));
console.error = (...args) => logger.error(args.map(arg => typeof arg === 'object' ? JSON.stringify(arg) : arg).join(' '));
console.warn = (...args) => logger.warn(args.map(arg => typeof arg === 'object' ? JSON.stringify(arg) : arg).join(' '));

const app = express();
const port = process.env.PORT || 3010;

app.set('trust proxy', 1);

// Helper middleware for express-validator results
const validateRequest = (req, res, next) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
        console.error(`Validation failed for ${req.method} ${req.originalUrl}:`, errors.array());
        return res.status(400).json({ success: false, errors: errors.array() });
    }
    next();
};

function getUploadPath(req) {
    let uploadPath = 'public/photos/';
    if (req.body.folder) {
        // Basic sanitization to prevent directory traversal
        const folderName = path.basename(req.body.folder);
        uploadPath = path.join('public/photos/', folderName);
    }
    return uploadPath;
}
// Multer setup for file uploads
const storage = multer.diskStorage({
    destination: async function (req, file, cb) {
        let uploadPath = getUploadPath(req);
        try {
            await fs.mkdir(uploadPath, { recursive: true });
        } catch (error) {
            console.error('Error creating upload directory:', error);
            return cb(error);
        }
        cb(null, uploadPath);
    },
    filename: function (req, file, cb) {
        const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
        cb(null, file.fieldname + '-' + uniqueSuffix + path.extname(file.originalname));
    }
});

const upload = multer({ storage: storage });

// Use Helmet to secure HTTP headers (CSP disabled temporarily to prevent breaking external scripts like TinyMCE/YouTube)
app.use(helmet({ contentSecurityPolicy: false }));

// Middleware to parse JSON
app.use(express.json());

// Configure express-session with persistent file store for secure state management
app.use(session({
    store: new FileStore({
        path: path.join(__dirname, 'sessions'),
        ttl: 2 * 60 * 60, // 2 hours in seconds
        retries: 5,
        factor: 1,
        minTimeout: 50,
        maxTimeout: 150
    }),
    secret: process.env.WEBSITE_ADMIN_TOKEN || 'secure-fallback-secret',
    resave: false,
    saveUninitialized: false,
    cookie: {
        secure: process.env.NODE_ENV === 'production' || process.env.NODE_ENV === 'staging', // Requires HTTPS
        httpOnly: true,
        sameSite: 'strict',
        maxAge: 2 * 60 * 60 * 1000 // 2 hours expiry
    }
}));

// Health check
app.get('/ping', (req, res) => {
    res.set('Cache-Control', 'no-store');
    res.send('pong');
});


// Load required environment variables
if (
    !process.env.WEBSITE_ADMIN_TOKEN ||
    !process.env.ADMIN_USERNAME ||
    !process.env.EMAIL_USER ||
    !process.env.EMAIL_PASS) {
    console.error("FATAL ERROR: Missing environment variables (TOKEN, USERNAME, EMAIL_USER, EMAIL_PASS). Check your .env file.");
    process.exit(1);
}

const ADMIN_TOKEN = process.env.WEBSITE_ADMIN_TOKEN;
const ADMIN_USERNAME = process.env.ADMIN_USERNAME;
// const ADMIN_PASSWORD_HASH = process.env.ADMIN_PASSWORD_HASH; // No longer needed for OTP login

let REGISTER_LINK;
let LOGIN_LINK;
let TRAINING_REGISTER_LINK;
let TRAINING_COURSES_LINK;

if (process.env.NODE_ENV === 'staging') {
    REGISTER_LINK = 'https://staging.myconsultation.sevenpointfour.in/register.html';
    LOGIN_LINK = 'https://staging.myconsultation.sevenpointfour.in/login.html';
    TRAINING_REGISTER_LINK = 'https://staging.training.arogyanubhutifoundation.in/register';
    TRAINING_COURSES_LINK = 'https://staging.training.arogyanubhutifoundation.in/courses';
} else if (process.env.NODE_ENV === 'production') {
    REGISTER_LINK = 'https://myconsultation.sevenpointfour.in/register.html';
    LOGIN_LINK = 'https://myconsultation.sevenpointfour.in/login.html';
    TRAINING_REGISTER_LINK = 'https://training.arogyanubhutifoundation.in/register';
    TRAINING_COURSES_LINK = 'https://training.arogyanubhutifoundation.in/courses';
} else {
    REGISTER_LINK = 'http://localhost:3020/register.html';
    LOGIN_LINK = 'http://localhost:3020/login.html';
    TRAINING_REGISTER_LINK = 'http://localhost:3000/register';
    TRAINING_COURSES_LINK = 'http://localhost:3000/courses';
}

// Serve static files from "public" directory
app.use(express.static(path.join(__dirname, 'public')));

// Admin verification middleware
const verifyAdmin = (req, res, next) => {
    if (req.session && req.session.isAdmin) {
        return next();
    }
    console.log(`SERVER: Admin verification failed for ${req.method} ${req.originalUrl}`);
    res.status(403).json({ error: 'Forbidden: Admin access required.' });
};

// interceptor for html files
app.use((req, res, next) => {
    const originalUrl = req.originalUrl;
    if (originalUrl.endsWith('.html')) {
        let name = path.basename(req.url);
        const contentDir = path.join(__dirname, 'content');
        const filePath = path.join(contentDir, name.replace('.html', '.json'));
        // if filePath exists on disk serve content.html
        fs.access(filePath)
            .then(() => {
                res.sendFile(path.join(__dirname, 'public', 'content.html'));
            })
            .catch(() => {
                // If the file does not exist, continue to serve the original HTML file
                next();
            });
        return;
    } else {
        next();
    }
});



// GET page content
app.get('/api/page-content/:pageName',
    param('pageName').trim().matches(/^[a-zA-Z0-9\-_]+$/).withMessage('Invalid page name format.'),
    validateRequest,
    async (req, res) => {
        const { pageName } = req.params;

        // Dynamic link detection based on request host
        const host = req.get('host');
        let dynamicRegisterLink, dynamicLoginLink, dynamicTrainingRegisterLink, dynamicTrainingCoursesLink;

        if (host && host.includes('staging')) {
            dynamicRegisterLink = 'https://staging.myconsultation.sevenpointfour.in/register.html';
            dynamicLoginLink = 'https://staging.myconsultation.sevenpointfour.in/login.html';
            dynamicTrainingRegisterLink = 'https://staging.training.arogyanubhutifoundation.in/register';
            dynamicTrainingCoursesLink = 'https://staging.training.arogyanubhutifoundation.in/courses';
        } else if (host && (host.includes('localhost') || host.includes('127.0.0.1'))) {
            dynamicRegisterLink = 'http://localhost:3020/register.html';
            dynamicLoginLink = 'http://localhost:3020/login.html';
            dynamicTrainingRegisterLink = 'http://localhost:3000/register';
            dynamicTrainingCoursesLink = 'http://localhost:3000/courses';
        } else {
            dynamicRegisterLink = 'https://myconsultation.sevenpointfour.in/register.html';
            dynamicLoginLink = 'https://myconsultation.sevenpointfour.in/login.html';
            dynamicTrainingRegisterLink = 'https://training.arogyanubhutifoundation.in/register';
            dynamicTrainingCoursesLink = 'https://training.arogyanubhutifoundation.in/courses';
        }

        const contentDir = path.join(__dirname, 'content');
        const filePath = path.join(contentDir, `${pageName}.json`);

        try {
            let data = await fs.readFile(filePath, 'utf8');
            // Replace any placeholders with environment-specific values
            data = data.replace(/{{REGISTER_LINK}}/g, dynamicRegisterLink);
            data = data.replace(/{{LOGIN_LINK}}/g, dynamicLoginLink);
            data = data.replace(/{{TRAINING_REGISTER_LINK}}/g, dynamicTrainingRegisterLink);
            data = data.replace(/{{TRAINING_COURSES_LINK}}/g, dynamicTrainingCoursesLink);
            res.setHeader('Cache-Control', 'no-store');
            res.json(JSON.parse(data));
        } catch (error) {
            if (error.code === 'ENOENT') {
                res.json({ content: '' }); // No content yet
            } else {
                console.error(`Error reading content for ${pageName}:`, error);
                res.status(500).json({ error: 'Failed to load content.' });
            }
        }
    }
);

// Save page content (admin only)
app.post('/api/page-content/:pageName',
    verifyAdmin,
    param('pageName').trim().matches(/^[a-zA-Z0-9\-_]+$/).withMessage('Invalid page name format.'),
    body('content').optional(),
    body('items').optional().isArray().withMessage('Items must be an array.'),
    validateRequest,
    async (req, res) => {
        // Enforce: text content edits are only allowed on localhost
        if (process.env.NODE_ENV !== 'development') {
            console.warn(`SERVER: Blocked content update attempt for '${req.params.pageName}' on non-development environment.`);
            return res.status(403).json({ error: 'Page content updates are disabled on the server. Please edit locally and deploy.' });
        }

        const { pageName } = req.params;
        const { content, items } = req.body;

        const contentDir = path.join(__dirname, 'content');
        const filePath = path.join(contentDir, `${pageName}.json`);

        try {
            await fs.mkdir(contentDir, { recursive: true });
            await fs.writeFile(filePath, JSON.stringify({ content, items }, null, 2));
            res.json({ success: true, message: `Content for ${pageName} saved successfully.` });
        } catch (error) {
            console.error(`Error saving content for ${pageName}:`, error);
            res.status(500).json({ error: 'Failed to save content.' });
        }
    }
);

app.delete('/api/page-content/:pageName',
    verifyAdmin,
    param('pageName').trim().matches(/^[a-zA-Z0-9\-_]+$/).withMessage('Invalid page name format.'),
    validateRequest,
    async (req, res) => {
        // Enforce: page deletions are only allowed on localhost
        if (process.env.NODE_ENV !== 'development') {
            console.warn(`SERVER: Blocked page deletion attempt for '${req.params.pageName}' on non-development environment.`);
            return res.status(403).json({ error: 'Page deletion is disabled on the server. Please delete locally and deploy.' });
        }

        const { pageName } = req.params;

        const contentDir = path.join(__dirname, 'content');
        const filePath = path.join(contentDir, `${pageName}.json`);

        try {
            await fs.unlink(filePath);
            res.json({ success: true, message: `Content for ${pageName} deleted successfully.` });
        } catch (error) {
            if (error.code === 'ENOENT') {
                return res.status(404).json({ error: 'Content not found.' });
            }
            console.error(`Error deleting content for ${pageName}:`, error);
            res.status(500).json({ error: 'Failed to delete content.' });
        }
    }
);

// Rate limiter for OTP requests and verification to prevent brute-force attacks
const loginLimiter = rateLimit({
    windowMs: 15 * 60 * 1000, // 15 minutes
    max: 5, // Limit each IP to 5 requests per windowMs
    message: { success: false, error: 'Too many requests from this IP, please try again after 15 minutes.' }
});

// Admin login
app.post('/api/admin/login',
    loginLimiter,
    body('username').trim().isEmail().withMessage('Username must be a valid email address.'),
    body('password').trim().notEmpty().withMessage('Password is required.'),
    validateRequest,
    async (req, res) => {
        const { username, password } = req.body;
        if (username === ADMIN_USERNAME) {
            // Retrieve secure hash from environment variables
            const storedHashData = process.env.ADMIN_PASSWORD_HASH;
            if (!storedHashData) {
                console.error('SERVER: ADMIN_PASSWORD_HASH is not defined in environment variables.');
                return res.status(500).json({ success: false, error: 'Authentication is not configured on the server.' });
            }

            const [salt, storedHash] = storedHashData.split(':');
            if (!salt || !storedHash) {
                console.error('SERVER: ADMIN_PASSWORD_HASH format is invalid. Expected salt:hash');
                return res.status(500).json({ success: false, error: 'Authentication configuration is invalid.' });
            }

            // Verify password using PBKDF2
            const inputHash = crypto.pbkdf2Sync(password, salt, 100000, 64, 'sha512').toString('hex');
            
            let isMatch = false;
            try {
                if (inputHash.length === storedHash.length) {
                    isMatch = crypto.timingSafeEqual(Buffer.from(inputHash, 'hex'), Buffer.from(storedHash, 'hex'));
                }
            } catch (err) {
                console.error('Password verification comparison error:', err);
            }

            if (isMatch) {
                // Regenerate session ID to mitigate session fixation attacks
                req.session.regenerate(async (err) => {
                    if (err) {
                        console.error('Session regeneration failed during login:', err);
                        return res.status(500).json({ success: false, error: 'Internal server error.' });
                    }
                    
                    try {
                        // Log privacy policy consent for DPDP compliance
                        const ip = req.headers['x-forwarded-for'] || req.socket.remoteAddress;
                        const consentLog = `[${new Date().toISOString()}] IP: ${ip} | User: ${username} | Action: Privacy Policy Consent | Version: 1.0\n`;
                        await fs.appendFile(path.join(__dirname, 'consent_audit.log'), consentLog);
                        
                        req.session.isAdmin = true;
                        req.session.user = username;
                        console.log('SERVER: Admin login successful');
                        return res.json({ success: true });
                    } catch (error) {
                        console.error('Error post-login setup:', error);
                        return res.status(500).json({ success: false, error: 'Internal server error.' });
                    }
                });
            } else {
                console.log('SERVER: Invalid password attempt');
                res.status(401).json({ success: false, error: 'Invalid username or password.' });
            }
        } else {
            // Generic error message for security to prevent username enumeration
            console.log('SERVER: Invalid username attempt');
            res.status(401).json({ success: false, error: 'Invalid username or password.' });
        }
    }
);

// New endpoint to verify OTP and complete login
app.post('/api/admin/verify-otp',
    loginLimiter,
    body('username').trim().isEmail().withMessage('Username must be a valid email address.'),
    body('otp').trim().isNumeric().isLength({ min: 6, max: 6 }).withMessage('OTP must be a 6-digit number.'),
    validateRequest,
    (req, res) => {
        const { username, otp } = req.body;
        const storedOtpData = req.session.otpData;

        if (storedOtpData && storedOtpData.username === username && storedOtpData.otp === otp && Date.now() < storedOtpData.expiry) {
            console.log('SERVER: OTP verification successful');
            delete req.session.otpData; // OTP is single-use, delete it

            // Regenerate session to prevent session fixation attacks
            req.session.regenerate((err) => {
                if (err) return res.status(500).json({ success: false, error: 'Internal server error.' });
                req.session.isAdmin = true;
                req.session.user = username;
                res.json({ success: true }); // No token sent back; secure cookie handles state automatically
            });
        } else {
            console.log('SERVER: OTP verification failed');
            res.status(401).json({ success: false, error: 'Invalid or expired code.' });
        }
    }
);

// Verify admin token
app.get('/api/admin/verify', (req, res) => {
    if (req.session && req.session.isAdmin) {
        return res.json({ isAdmin: true });
    }
    res.json({ isAdmin: false });
});

// Logout endpoint to securely destroy the session
app.post('/api/admin/logout', (req, res) => {
    req.session.destroy((err) => {
        if (err) {
            return res.status(500).json({ success: false, error: 'Could not log out.' });
        }
        res.clearCookie('connect.sid'); // Default express-session cookie name
        res.json({ success: true, message: 'Logged out successfully.' });
    });
});

// Export user's personal data (DPDP compliance)
app.get('/api/admin/export-data', verifyAdmin, async (req, res) => {
    const username = req.session.user;
    const logPath = path.join(__dirname, 'consent_audit.log');
    let consentRecords = [];
    
    try {
        const data = await fs.readFile(logPath, 'utf8');
        const lines = data.split('\n');
        for (const line of lines) {
            if (line.includes(`User: ${username}`)) {
                consentRecords.push(line.trim());
            }
        }
    } catch (error) {
        console.error('Error reading consent audit log for data export:', error);
    }

    res.json({
        success: true,
        user_identity: {
            username: username,
            role: 'Administrator'
        },
        collected_data_purposes: [
            { field: 'Email Address', purpose: 'Admin identification and OTP delivery' },
            { field: 'One-Time Password (OTP)', purpose: 'Secure session verification' }
        ],
        consent_history: consentRecords,
        active_session: {
            cookie_settings: req.session.cookie,
            session_id_active: true
        }
    });
});

// Purge user's personal data / Delete account identity (DPDP compliance)
app.post('/api/admin/delete-account', verifyAdmin, async (req, res) => {
    const username = req.session.user;
    const logPath = path.join(__dirname, 'consent_audit.log');

    try {
        // Redact personal email from the consent log
        const data = await fs.readFile(logPath, 'utf8');
        const lines = data.split('\n');
        const updatedLines = lines.map(line => {
            if (line.includes(`User: ${username}`)) {
                return line.replace(new RegExp(`User: ${username}`, 'g'), 'User: [ANONYMIZED_DELETED_USER]');
            }
            return line;
        });
        await fs.writeFile(logPath, updatedLines.join('\n'), 'utf8');
        console.log(`SERVER: Consent logs redacted for deleted user.`);
    } catch (error) {
        console.error('Error redacting consent logs during account deletion:', error);
        return res.status(500).json({ success: false, error: 'Failed to redact identity logs.' });
    }

    // Destroy active session
    req.session.destroy((err) => {
        if (err) {
            console.error('Error destroying session during deletion:', err);
            return res.status(500).json({ success: false, error: 'Account identity redacted, but failed to destroy active session.' });
        }
        res.clearCookie('connect.sid');
        res.json({ success: true, message: 'Your personal data and consent history have been successfully purged, and session terminated.' });
    });
});

// Image upload endpoint (admin only)
app.post('/api/admin/upload-image', verifyAdmin, upload.single('file'), (req, res) => {
    if (!req.file) {
        return res.status(400).send({ error: 'No file uploaded.' });
    }
    const folder = req.body.folder && req.body.folder !== 'root' ? `${req.body.folder}/` : '';
    res.json({ location: `/photos/${folder}${req.file.filename}` });
});

// Recursive function to get all image files in a directory and its subdirectories, grouped by folder
async function walk(dir, baseDir) {
    let files = await fs.readdir(dir, { withFileTypes: true });
    let imagesByFolder = {};

    for (let file of files) {
        const fullPath = path.join(dir, file.name);
        const relativePath = path.relative(baseDir, fullPath);

        if (file.isDirectory()) {
            const subfolderImages = await walk(fullPath, baseDir);
            for (const folder in subfolderImages) {
                if (imagesByFolder[folder]) {
                    imagesByFolder[folder].push(...subfolderImages[folder]);
                } else {
                    imagesByFolder[folder] = subfolderImages[folder];
                }
            }
        } else if (file.isFile() && ['.jpg', '.jpeg', '.png', '.gif'].includes(path.extname(file.name).toLowerCase())) {
            const folderName = path.dirname(relativePath);
            const displayFolderName = folderName === '.' ? 'root' : folderName.split(path.sep)[0]; // Get top-level folder name or 'root'

            if (!imagesByFolder[displayFolderName]) {
                imagesByFolder[displayFolderName] = [];
            }
            imagesByFolder[displayFolderName].push(relativePath);
        }
    }
    return imagesByFolder;
}

app.get('/api/nav-items.json', async (req, res) => {
    const contentDir = path.join(__dirname, 'content');
    const filePath = path.join(contentDir, 'nav-items.json');
    res.sendFile(filePath);
});

app.post('/api/nav-items.json',
    verifyAdmin,
    body().custom(value => {
        if (typeof value !== 'object' || value === null) {
            throw new Error('Invalid JSON payload.');
        }
        return true;
    }),
    validateRequest,
    async (req, res) => {
        // Enforce: navigation menu edits are only allowed on localhost
        if (process.env.NODE_ENV !== 'development') {
            console.warn('SERVER: Blocked navigation menu update attempt on non-development environment.');
            return res.status(403).json({ error: 'Navigation updates are disabled on the server. Please edit locally and deploy.' });
        }

        const contentDir = path.join(__dirname, 'content');
        const filePath = path.join(contentDir, 'nav-items.json');
        await fs.writeFile(filePath, JSON.stringify(req.body, null, 2));
        res.send('ok');
    }
);

// handler to get list of images in the public/photos folder, grouped by subfolder
app.get('/api/images.json', async (req, res) => {
    const photosDir = path.join(__dirname, 'public', 'photos');
    try {
        const imagesByFolder = await walk(photosDir, photosDir);
        // Sort images within each folder and sort folder names
        for (const folder in imagesByFolder) {
            imagesByFolder[folder].sort();
        }
        const sortedFolders = Object.keys(imagesByFolder).sort();
        const sortedImagesByFolder = {};
        sortedFolders.forEach(folder => {
            sortedImagesByFolder[folder] = imagesByFolder[folder];
        });

        res.json({ imagesByFolder: sortedImagesByFolder });
    } catch (error) {
        console.error('Error reading images directory:', error);
        res.status(500).json({ error: 'Failed to retrieve images.' });
    }
});

app.delete(/\/api\/images\/(.*)/,
    verifyAdmin,
    (req, res, next) => {
        const imagePath = req.params[0];
        if (!imagePath) {
            return res.status(400).json({ error: 'Image path is required.' });
        }
        // Strict file path check to ensure extension matches images and no traversal exists
        if (imagePath.includes('..') || !/^[a-zA-Z0-9_\-\/]+\.(jpg|jpeg|png|gif)$/i.test(imagePath)) {
            return res.status(400).json({ error: 'Invalid image path.' });
        }
        next();
    },
    async (req, res) => {
        const imagePath = req.params[0];
        const fullImagePath = path.join(__dirname, 'public', 'photos', imagePath);

        try {
            await fs.unlink(fullImagePath);
            res.json({ success: true, message: `Image ${imagePath} deleted successfully.` });
        } catch (error) {
            if (error.code === 'ENOENT') {
                return res.status(404).json({ error: 'Image not found.' });
            }
            console.error(`Error deleting image ${imagePath}:`, error);
            res.status(500).json({ error: 'Failed to delete image.' });
        }
    }
);

// New endpoint to download content directory as a zip file
app.get('/api/admin/download-content', verifyAdmin, async (req, res) => {
    const contentDir = path.join(__dirname, 'content');
    const now = new Date();
    const year = now.getFullYear();
    const month = String(now.getMonth() + 1).padStart(2, '0');
    const day = String(now.getDate()).padStart(2, '0');
    const hours = String(now.getHours()).padStart(2, '0');
    const minutes = String(now.getMinutes()).padStart(2, '0');
    const seconds = String(now.getSeconds()).padStart(2, '0');
    const timestamp = `${day}-${month}-${year}`;
    const archiveName = `sfwebsite_backup_${timestamp}.zip`;
    const outputFilePath = path.join(__dirname, archiveName);
    const output = fsSync.createWriteStream(outputFilePath);

    res.writeHead(200, {
        'Content-Type': 'application/zip',
        'Content-Disposition': `attachment; filename="${archiveName}"`
    });

    const archive = archiver('zip', {
        zlib: { level: 9 } // Sets the compression level.
    });

    archive.on('error', function (err) {
        console.error('Archive error:', err);
        res.status(500).send({ error: err.message });
    });

    archive.pipe(output); // Save to server disk
    archive.pipe(res);    // Stream to client browser

    try {
        // 1. Add content directory (must exist)
        archive.directory(contentDir, 'content');

        // 2. Add entire live public directory (HTML templates, navbar, CSS, JS, photos, audio)
        const publicDir = path.join(__dirname, 'public');
        if (fsSync.existsSync(publicDir)) {
            archive.directory(publicDir, 'public');
        }
        
        // 3. Append consent_audit.log to the archive root if it exists
        const logPath = path.join(__dirname, 'consent_audit.log');
        if (fsSync.existsSync(logPath)) {
            archive.file(logPath, { name: 'consent_audit.log' });
        }

        // 4. Append core application code & package manifest for 100% self-contained Disaster Recovery
        const serverJsPath = path.join(__dirname, 'server.js');
        if (fsSync.existsSync(serverJsPath)) {
            archive.file(serverJsPath, { name: 'server.js' });
        }

        const packageJsonPath = path.join(__dirname, 'package.json');
        if (fsSync.existsSync(packageJsonPath)) {
            archive.file(packageJsonPath, { name: 'package.json' });

        const utilsDir = path.join(__dirname, 'utils');
        if (fsSync.existsSync(utilsDir)) {
            archive.directory(utilsDir, 'utils');
        }
        }
        await archive.finalize(); // Finalize the archive (ie we are done adding files but streams may still be flowing)
    } catch (error) {
        console.error('Error during backup generation:', error);
        if (!res.headersSent) {
            res.status(500).send({ error: 'Failed to create content archive.' });
        } else {
            res.end(); // End the stream cleanly to prevent browser from hanging or getting corrupt zip
        }
    }
});

// Secure Maintenance Agent Backup trigger endpoint
app.get('/maintenance-agent/backup', async (req, res) => {
    // Validate Authorization token
    if (process.env.WEBSITE_ADMIN_TOKEN) {
        const authHeader = req.headers['authorization'];
        const token = authHeader && authHeader.split(' ')[1];
        if (!token || token !== process.env.WEBSITE_ADMIN_TOKEN) {
            return res.status(403).json({ error: 'Unauthorized: Invalid token' });
        }
    }

    const contentDir = path.join(__dirname, 'content');
    const now = new Date();
    const year = now.getFullYear();
    const month = String(now.getMonth() + 1).padStart(2, '0');
    const day = String(now.getDate()).padStart(2, '0');
    const timestamp = `${day}-${month}-${year}`;
    const archiveName = `sfwebsite_backup_${timestamp}.zip`;
    const outputFilePath = path.join(__dirname, archiveName);
    const output = fsSync.createWriteStream(outputFilePath);

    res.writeHead(200, {
        'Content-Type': 'application/zip',
        'Content-Disposition': `attachment; filename="${archiveName}"`
    });

    const archive = archiver('zip', {
        zlib: { level: 9 } // Sets the compression level.
    });

    archive.on('error', function (err) {
        console.error('Archive error during agent backup:', err);
        if (!res.headersSent) {
            res.status(500).send({ error: err.message });
        }
    });

    archive.pipe(output); // Save to server disk
    archive.pipe(res);    // Stream to client response

    try {
        // 1. Add content directory (must exist)
        archive.directory(contentDir, 'content');

        // 2. Add entire live public directory (HTML templates, navbar, CSS, JS, photos, audio)
        const publicDir = path.join(__dirname, 'public');
        if (fsSync.existsSync(publicDir)) {
            archive.directory(publicDir, 'public');
        }
        
        // 3. Append consent_audit.log to the archive root if it exists
        const logPath = path.join(__dirname, 'consent_audit.log');
        if (fsSync.existsSync(logPath)) {
            archive.file(logPath, { name: 'consent_audit.log' });
        }

        // 4. Append core application code & package manifest for 100% self-contained Disaster Recovery
        const serverJsPath = path.join(__dirname, 'server.js');
        if (fsSync.existsSync(serverJsPath)) {
            archive.file(serverJsPath, { name: 'server.js' });
        }

        const packageJsonPath = path.join(__dirname, 'package.json');
        if (fsSync.existsSync(packageJsonPath)) {
            archive.file(packageJsonPath, { name: 'package.json' });
        }
        await archive.finalize(); // Finalize the archive
    } catch (error) {
        console.error('Error during agent backup generation:', error);
        if (!res.headersSent) {
            res.status(500).send({ error: 'Failed to create backup archive.' });
        } else {
            res.end();
        }
    }
});

function getProcessCount() {
    return new Promise((resolve) => {
        exec('ps -u $(whoami) -L --no-headers | wc -l', { timeout: 2000 }, (err, stdout) => {
            if (err) return resolve('N/A');
            const count = parseInt(stdout.trim(), 10);
            resolve(isNaN(count) ? 'N/A' : count);
        });
    });
}

// Secure Maintenance Agent endpoint
app.get('/maintenance-agent', async (req, res) => {
    // Validate Authorization token
    if (process.env.WEBSITE_ADMIN_TOKEN) {
        const authHeader = req.headers['authorization'];
        const token = authHeader && authHeader.split(' ')[1];
        if (!token || token !== process.env.WEBSITE_ADMIN_TOKEN) {
            return res.status(403).json({ error: 'Unauthorized: Invalid token' });
        }
    }

    let deployedSha = null;
    try {
        const versionPath = path.join(__dirname, 'version.json');
        if (fsSync.existsSync(versionPath)) {
            const versionData = JSON.parse(fsSync.readFileSync(versionPath, 'utf8'));
            deployedSha = versionData.commit || null;
        }
    } catch (e) {
        console.error('Failed to read version.json:', e.message);
    }

    const status = { 
        backup: 'Not Found', 
        disk: 'Unknown', 
        audit: 'OK', 
        gitStatus: deployedSha ? { deployedSha } : {}, 
        markdownDocs: [] 
    };
    
    // Get active processes count
    status.processes = await getProcessCount();

    // 1. Check Disk Usage with Persistent File Cache
    const homeDir = '/home/sevenpoi';
    const quotaMb = parseFloat(process.env.DISK_QUOTA_MB) || 1845; // Default to 1.8 GB if not configured
    const quotaKb = quotaMb * 1024;

    let diskResult = 'Calculating...';
    const cacheFile = path.join(__dirname, 'disk_usage.txt');
    try {
        if (fsSync.existsSync(cacheFile)) {
            diskResult = fsSync.readFileSync(cacheFile, 'utf8').trim();
            const stats = fsSync.statSync(cacheFile);
            if (Date.now() - stats.mtime.getTime() > 21600000 && !isScanning) {
                triggerBackgroundDiskScan(cacheFile, homeDir, quotaKb);
            }
        } else if (!isScanning) {
            triggerBackgroundDiskScan(cacheFile, homeDir, quotaKb);
        }
    } catch (e) {
        diskResult = 'N/A';
    }

    status.disk = diskResult;

    // 2. Check Backup files in project root
    try {
        const files = fsSync.readdirSync(__dirname);
        const backupFiles = files.filter(f => f.startsWith('backup-') || f.startsWith('dr.sfwebsite') || f.startsWith('sfwebsite_code') || (f.startsWith('sfwebsite_backup_') && f.endsWith('.zip')));
        
        // Get stats and sort by modification time (newest first)
        const sortedBackups = backupFiles
            .map(name => ({ name, mtime: fsSync.statSync(path.join(__dirname, name)).mtime }))
            .sort((a, b) => b.mtime - a.mtime)
            .map(f => f.name);

        status.backup = sortedBackups.slice(0, 3);

        // Markdown Documents are now fetched exclusively via GitHub API
        status.markdownDocs = [];

        // --- Active Production Security Audit Checks ---
        const securityAlerts = [];

        // 1. Exposed Backups & Archives Check
        try {
            const publicPath = path.join(__dirname, 'public');
            if (fsSync.existsSync(publicPath)) {
                const publicFiles = fsSync.readdirSync(publicPath);
                const exposedFiles = publicFiles.filter(f => f.endsWith('.zip') || f.endsWith('.sql'));
                if (exposedFiles.length > 0) {
                    securityAlerts.push(`Exposed public file(s): ${exposedFiles.join(', ')}`);
                }
            }
        } catch (e) {
            console.error('Security Audit: Failed to check public directory:', e.message);
        }

        // 2. Environment Mode Check
        if (process.env.NODE_ENV !== 'production') {
            securityAlerts.push('Application not running in production mode (NODE_ENV)');
        }

        // 3. Configuration File Permissions Check (.env)
        try {
            const envFilePath = path.join(__dirname, '.env');
            if (fsSync.existsSync(envFilePath)) {
                const stats = fsSync.statSync(envFilePath);
                // Check if "others read" or "others write" permission bit is set
                const isLoose = (stats.mode & 0o006) !== 0; 
                if (isLoose) {
                    try {
                        fsSync.chmodSync(envFilePath, 0o600);
                        console.log(`Security Audit: Auto-fixed loose permissions on ${envFilePath}`);
                    } catch (chmodErr) {
                        securityAlerts.push('.env configuration file has loose permissions (auto-fix failed)');
                        console.error('Security Audit: Failed to auto-fix file permissions:', chmodErr.message);
                    }
                }
            }
        } catch (e) {
            console.error('Security Audit: Failed to check file permissions:', e.message);
        }

        // 4. SSL Enforce Check
        const isSecure = req.secure || req.headers['x-forwarded-proto'] === 'https';
        if (!isSecure) {
            securityAlerts.push('Insecure connection (HTTP)');
        }

        // Set final audit status
        if (securityAlerts.length > 0) {
            status.audit = `Vulnerable: ${securityAlerts.join('; ')}`;
        } else {
            status.audit = 'OK';
        }

        return res.json(status);
    } catch (readErr) {
        status.backup = [];
        status.audit = 'Error reading directory';
        res.json(status);
    }
});

// Secure endpoint to fetch specific Markdown document content
app.get('/maintenance-agent/docs', (req, res) => {
    // Validate Authorization token
    if (process.env.WEBSITE_ADMIN_TOKEN) {
        const authHeader = req.headers['authorization'];
        const token = authHeader && authHeader.split(' ')[1];
        if (!token || token !== process.env.WEBSITE_ADMIN_TOKEN) {
            return res.status(403).json({ error: 'Unauthorized: Invalid token' });
        }
    }

    // Documentation is now managed via GitHub API
    return res.status(404).json({ error: 'Document not found (Legacy local docs removed)' });
});

// Fallback for client-side routing
app.get(/(.*)/, (req, res) => {
    if (path.extname(req.path).length > 0) {
        return res.status(404).send('Resource not found');
    }
    res.sendFile(path.join(__dirname, 'public', 'index.html'));
});

async function startServer() {
    // Automatically create symbolic link for safe photos on server
    if (process.env.NODE_ENV === 'staging' || process.env.NODE_ENV === 'production') {
        const possiblePaths = [
            path.resolve(__dirname, '../safe_uploads/production_photos'),
            path.resolve(__dirname, '../safe_uploads/photos'),
            path.resolve(__dirname, '../../safe_uploads/production_photos'),
            path.resolve(__dirname, '../../safe_uploads/photos'),
            path.resolve(__dirname, '../../../safe_uploads/production_photos'),
            path.resolve(__dirname, '../../../safe_uploads/photos')
        ];
        
        let sourcePath = null;
        for (const p of possiblePaths) {
            try {
                await fs.access(p);
                sourcePath = p;
                break;
            } catch (e) {
                // Path does not exist or is not readable, try next
            }
        }

        const targetPath = path.join(__dirname, 'public', 'photos');

        if (sourcePath) {
            try {
                // Try unlinking/removing the old target symlink first to prevent collision
                try {
                    await fs.unlink(targetPath);
                } catch (e) {
                    try {
                        await fs.rm(targetPath, { recursive: true, force: true });
                    } catch (rmError) {
                        // Ignore
                    }
                }
                await fs.symlink(sourcePath, targetPath, 'dir');
                console.log(`SERVER: Safe photos link created pointing to: ${sourcePath}`);
            } catch (error) {
                if (error.code !== 'EEXIST') {
                    console.warn(`SERVER: Symbolic link check completed with status: ${error.message}`);
                }
            }
        } else {
            console.warn(`SERVER: Could not find a valid safe_uploads source directory. Tried: ${possiblePaths.join(', ')}`);
        }
    }
    app.listen(port, () => {
        const nodeEnv = process.env.NODE_ENV || 'development';
        console.log(`Server running in ${nodeEnv} mode at http://localhost:${port}`);
        console.log(`Serving static files from: ${path.join(__dirname, 'public')}`);
    });
}
startServer();
