const { expressjwt: expressJwt } = require('express-jwt');
const dbSAPPS = require('../apps/sapps/helpers/db');
const redisClient = require('../redisClient');
const config = require('../config.json');
require('dotenv').config();

module.exports = authorize;

// ROBUST: Custom token extractor to handle token from URL parameter or Authorization header
function tokenExtractor(req) {
    // First, try to get token from Authorization header (standard way)
    if (req.headers.authorization && req.headers.authorization.split(' ')[0] === 'Bearer') {
        return req.headers.authorization.split(' ')[1];
    }
    
    // ROBUST: Fallback to URL parameter (for backward compatibility with /sapps/users/:servicetoken)
    // Check if servicetoken is in URL params (common pattern: /sapps/users/:servicetoken)
    if (req.params && req.params.servicetoken) {
        return req.params.servicetoken;
    }
    
    // ROBUST: Also check query parameter as fallback
    if (req.query && req.query.token) {
        return req.query.token;
    }
    
    return null;
}

function authorize() {
    const { secret } = config.databaseConfigSAPPS;

    return [
        // ROBUST: Use custom token extractor to handle token from multiple sources
        // Wrap expressJwt in error handler to catch token errors gracefully
        (req, res, next) => {
            expressJwt({ 
                secret, 
                algorithms: ['HS256'],
                getToken: tokenExtractor,
                // ROBUST: Don't throw error immediately, let us handle it gracefully
                credentialsRequired: false,
            })(req, res, (err) => {
                // ROBUST: Handle expressJwt errors gracefully
                if (err) {
                    // ROBUST: If expressJwt throws error, handle it in our middleware
                    // Don't call next(err) - let our middleware handle it
                    req.jwtError = err;
                }
                // ROBUST: Always continue to next middleware, even if there's an error
                // We'll handle the error in the next middleware
                next();
            });
        },

        async (req, res, next) => {
            // ROBUST: Handle expressJwt errors first
            if (req.jwtError) {
                const jwtError = req.jwtError;
                
                // ROBUST: Handle token expiration
                if (jwtError.name === 'UnauthorizedError' && jwtError.message?.includes('expired')) {
                    return res.status(401).json({ 
                        success: false,
                        message: 'Token expired. Please login again.',
                        code: 'TOKEN_EXPIRED'
                    });
                }
                
                // ROBUST: Handle invalid token
                if (jwtError.name === 'UnauthorizedError' || jwtError.name === 'JsonWebTokenError') {
                    // ROBUST: Try to get token from URL parameter as fallback
                    const tokenFromUrl = req.params?.servicetoken || req.query?.token;
                    
                    if (tokenFromUrl) {
                        // ROBUST: Try manual verification as fallback
                        try {
                            const jwt = require('jsonwebtoken');
                            const decoded = jwt.verify(tokenFromUrl, secret);
                            
                            // ROBUST: Get user and attach to request
                            const user = await dbSAPPS.User.findByPk(decoded.sub, {
                                benchmark: false,
                                logging: false,
                            });
                            
                            if (!user) {
                                return res.status(401).json({ 
                                    success: false,
                                    message: 'Sorry! you are unauthorized!',
                                    code: 'UNAUTHORIZED'
                                });
                            }
                            
                            req.user = user.get();
                            req.auth = { sub: decoded.sub };
                            return next();
                        } catch (tokenError) {
                            // ROBUST: Manual verification also failed
                            if (tokenError.name === 'TokenExpiredError') {
                                return res.status(401).json({ 
                                    success: false,
                                    message: 'Token expired. Please login again.',
                                    code: 'TOKEN_EXPIRED'
                                });
                            }
                            
                            return res.status(401).json({ 
                                success: false,
                                message: 'Invalid token. Please login again.',
                                code: 'INVALID_TOKEN'
                            });
                        }
                    }
                    
                    // ROBUST: No valid token found
                    return res.status(401).json({ 
                        success: false,
                        message: 'Invalid token. Please login again.',
                        code: 'INVALID_TOKEN'
                    });
                }
            }
            
            // ROBUST: Handle case where token is missing or invalid
            if (!req.auth || !req.auth.sub) {
                // ROBUST: Try to get token from URL parameter as fallback
                const tokenFromUrl = req.params?.servicetoken || req.query?.token;
                
                if (tokenFromUrl) {
                    try {
                        // ROBUST: Manually verify token from URL parameter
                        const jwt = require('jsonwebtoken');
                        const decoded = jwt.verify(tokenFromUrl, secret);
                        
                        // ROBUST: Get user and attach to request
                        const user = await dbSAPPS.User.findByPk(decoded.sub, {
                            benchmark: false, // ROBUST: Disable caching
                            logging: false,
                        });
                        
                        if (!user) {
                            return res.status(401).json({ 
                                success: false,
                                message: 'Sorry! you are unauthorized!' 
                            });
                        }
                        
                        req.user = user.get();
                        req.auth = { sub: decoded.sub }; // Set auth for consistency
                        return next();
                    } catch (tokenError) {
                        // ROBUST: Handle token expiration or invalid token gracefully
                        if (tokenError.name === 'TokenExpiredError') {
                            return res.status(401).json({ 
                                success: false,
                                message: 'Token expired. Please login again.',
                                code: 'TOKEN_EXPIRED'
                            });
                        }
                        
                        if (tokenError.name === 'JsonWebTokenError') {
                            return res.status(401).json({ 
                                success: false,
                                message: 'Invalid token. Please login again.',
                                code: 'INVALID_TOKEN'
                            });
                        }
                        
                        return res.status(401).json({ 
                            success: false,
                            message: 'Sorry! you are unauthorized!',
                            code: 'UNAUTHORIZED'
                        });
                    }
                }
                
                // ROBUST: No token found anywhere
                return res.status(401).json({ 
                    success: false,
                    message: 'Authentication required. Please login again.',
                    code: 'NO_TOKEN'
                });
            }
            
			// ROBUST: Token is valid, get user with caching to prevent DB exhaustion
			try {
				const userId = req.auth.sub;
				const cacheKey = `auth:user:${userId}`;

				// 1. Try to get from Cache
				try {
					const cachedUser = await redisClient.get(cacheKey);
					if (cachedUser) {
						req.user = cachedUser; // redisClient.get returns parsed JSON if using custom wrapper, or string.
						// NOTE: explicit parsing if the redisClient returns string.
						// Assuming standard redisClient usage from this project which usually handles JSON or string.
						// Based on callPlan.service.js usage: `const cached = await redisClient.get(cacheKey); if (cached) return cached;`
						// It seems redisClient.get might return the object if it was stored as such (or the wrapper handles it).
						// But to be safe and robust, we check type.
						if (typeof cachedUser === 'string') {
							try {
								req.user = JSON.parse(cachedUser);
							} catch (e) {
								req.user = cachedUser;
							}
						}
						
						return next();
					}
				} catch (cacheError) {
					// Ignore cache errors and fall back to DB
					console.error('[Authorize Middleware] Cache error:', cacheError);
				}

				// 2. Fetch from DB if not in cache
				const user = await dbSAPPS.User.findByPk(userId, {
					benchmark: false,
					logging: false,
				});
				
				if (!user) {
					return res.status(401).json({ 
						success: false,
						message: 'Sorry! you are unauthorized!' 
					});
				}
				
				const userData = user.get();
				req.user = userData;

				// 3. Store in Cache (TTL 5 minutes)
				try {
					// Use setEx to store with expiration
					await redisClient.setEx(cacheKey, 300, userData);
				} catch (cacheSetError) {
					console.error('[Authorize Middleware] Cache set error:', cacheSetError);
				}

				next();
			} catch (dbError) {
				// ROBUST: Handle database errors gracefully
				console.error('[Authorize Middleware] Database error:', dbError);
				return res.status(500).json({ 
					success: false,
					message: 'Database error. Please try again.',
					code: 'DATABASE_ERROR'
				});
			}
		}
    ];
}
