const crypto = require('crypto');
const fs = require('fs');
const path = require('path');

/**
 * Cache busting utility for preventing browser caching of static assets
 */
class CacheBusting {
    constructor() {
        // Generate a unique version identifier on server start
        this.version = this.generateVersion();
        this.fileHashes = new Map();
    }

    /**
     * Generate a version identifier based on server start time and random data
     * @returns {string} Version string
     */
    generateVersion() {
        const timestamp = Date.now();
        const random = crypto.randomBytes(4).toString('hex');
        return `${timestamp}-${random}`;
    }

    /**
     * Get cache busting version for the application
     * @returns {string} Version string
     */
    getVersion() {
        return this.version;
    }

    /**
     * Generate a file hash for specific file-based cache busting
     * @param {string} filePath - Path to the file
     * @returns {string} File hash or version fallback
     */
    getFileHash(filePath) {
        try {
            if (this.fileHashes.has(filePath)) {
                return this.fileHashes.get(filePath);
            }

            const fullPath = path.join(__dirname, '..', 'public', filePath);
            if (fs.existsSync(fullPath)) {
                const content = fs.readFileSync(fullPath);
                const hash = crypto.createHash('md5').update(content).digest('hex').substring(0, 8);
                this.fileHashes.set(filePath, hash);
                return hash;
            }
        } catch (error) {
            console.warn(`Cache busting: Could not hash file ${filePath}:`, error.message);
        }

        // Fallback to version
        return this.version;
    }

    /**
     * Add cache busting parameter to a URL
     * @param {string} url - Original URL
     * @param {boolean} useFileHash - Whether to use file-specific hash
     * @returns {string} URL with cache busting parameter
     */
    bustCache(url, useFileHash = false) {
        if (!url) return url;

        const separator = url.includes('?') ? '&' : '?';
        const version = useFileHash ? this.getFileHash(url) : this.version;
        
        return `${url}${separator}v=${version}`;
    }

    /**
     * Get cache headers for static assets
     * @param {boolean} longTerm - Whether to use long-term caching
     * @returns {object} Cache headers
     */
    getCacheHeaders(longTerm = false) {
        if (longTerm) {
            // Long-term caching for versioned assets
            return {
                'Cache-Control': 'public, max-age=31536000, immutable', // 1 year
                'ETag': this.version
            };
        } else {
            // Short-term caching for frequently changing content
            return {
                'Cache-Control': 'public, max-age=300', // 5 minutes
                'ETag': this.version
            };
        }
    }

    /**
     * Get no-cache headers for dynamic content
     * @returns {object} No-cache headers
     */
    getNoCacheHeaders() {
        return {
            'Cache-Control': 'no-cache, no-store, must-revalidate',
            'Pragma': 'no-cache',
            'Expires': '0'
        };
    }

    /**
     * Middleware to add cache busting to HTML responses
     * @returns {function} Express middleware
     */
    htmlMiddleware() {
        return (req, res, next) => {
            // Store original send method
            const originalSend = res.send;

            // Override send method to modify HTML content
            res.send = (body) => {
                if (typeof body === 'string' && res.get('Content-Type')?.includes('text/html')) {
                    // Add cache busting to CSS and JS files
                    body = body.replace(
                        /(href|src)=["']([^"']+\.(css|js))["']/g,
                        (match, attr, url, ext) => {
                            // Skip external URLs
                            if (url.startsWith('http') || url.startsWith('//')) {
                                return match;
                            }
                            
                            const bustedUrl = this.bustCache(url, true);
                            return `${attr}="${bustedUrl}"`;
                        }
                    );

                    // Add version info to HTML for debugging
                    body = body.replace(
                        '</head>',
                        `    <meta name="app-version" content="${this.version}">\n</head>`
                    );
                }

                // Call original send method
                originalSend.call(res, body);
            };

            next();
        };
    }

    /**
     * Middleware to set cache headers for static files
     * @returns {function} Express middleware
     */
    staticCacheMiddleware() {
        return (req, res, next) => {
            const ext = path.extname(req.path).toLowerCase();
            const isVersioned = req.query.v !== undefined;

            // Set cache headers based on file type and versioning
            if (['.css', '.js', '.png', '.jpg', '.jpeg', '.gif', '.ico', '.svg'].includes(ext)) {
                const headers = isVersioned ? 
                    this.getCacheHeaders(true) : 
                    this.getCacheHeaders(false);
                
                Object.keys(headers).forEach(key => {
                    res.set(key, headers[key]);
                });
            }

            next();
        };
    }
}

// Create singleton instance
const cacheBusting = new CacheBusting();

module.exports = {
    CacheBusting,
    cacheBusting
};
