const express = require('express');
const router = express.Router();
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const { body, validationResult } = require('express-validator');
const { authLimiter } = require('../middleware/rateLimiter');
const { authenticate } = require('../middleware/auth');
const { db } = require('../database/db');
const logger = require('../utils/logger');
const { validatePassword, calculatePasswordStrength } = require('../utils/passwordPolicy');
const SecurityAuditLogger = require('../utils/securityAudit');
const {
  enforceAccountLockout,
  recordFailedLogin,
  clearFailedAttempts,
  updatePasswordExpiry,
  createSession,
  checkPasswordExpiry
} = require('../middleware/securityEnhancements');

const JWT_SECRET = process.env.JWT_SECRET || 'change_this_in_production';
const JWT_EXPIRES_IN = '7d';

// Register - Controlled by DISABLE_SIGNUPS environment variable
router.post('/register',
  [
    body('username').trim().isLength({ min: 3, max: 50 }).isAlphanumeric(),
    body('email').isEmail().normalizeEmail(),
    body('password').notEmpty()
  ],
  async (req, res) => {
    const ip = req.ip || req.headers['x-forwarded-for'] || req.connection.remoteAddress;
    const userAgent = req.headers['user-agent'];

    // Check if signups are disabled (default: true)
    const disableSignups = process.env.DISABLE_SIGNUPS !== 'false';
    
    if (disableSignups) {
      await SecurityAuditLogger.logAuthEvent(null, 'registration_attempt', 'blocked', {
        ip,
        userAgent,
        reason: 'Registration disabled'
      });
      return res.status(403).json({ 
        error: 'Registration is disabled. Contact an administrator to create your account.' 
      });
    }

    // If signups are enabled, proceed with registration (fallback for future flexibility)
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(400).json({ errors: errors.array() });
    }

    const { username, email, password } = req.body;

    try {
      // Validate password against policy
      const passwordValidation = validatePassword(password, username, email);
      if (!passwordValidation.valid) {
        await SecurityAuditLogger.logAuthEvent(null, 'registration_attempt', 'failed', {
          ip,
          userAgent,
          username,
          reason: passwordValidation.errors.join(', ')
        });
        return res.status(400).json({ 
          error: 'Password does not meet requirements',
          details: passwordValidation.errors,
          strength: calculatePasswordStrength(password)
        });
      }

      const hashedPassword = await bcrypt.hash(password, 10);
      const now = new Date().toISOString();

      db.run(
        'INSERT INTO users (username, email, password, role, must_change_password, password_changed_at, password_expires_at) VALUES (?, ?, ?, ?, ?, ?, ?)',
        [username, email, hashedPassword, 'user', 0, now, null],
        async function(err) {
          if (err) {
            if (err.message.includes('UNIQUE')) {
              await SecurityAuditLogger.logAuthEvent(null, 'registration_attempt', 'failed', {
                ip,
                userAgent,
                username,
                reason: 'Duplicate username or email'
              });
              return res.status(400).json({ error: 'Username or email already exists' });
            }
            logger.error('Registration error:', err);
            return res.status(500).json({ error: 'Registration failed' });
          }

          const userId = this.lastID;

          // Update password expiry
          await updatePasswordExpiry(userId);

          // Log successful registration
          await SecurityAuditLogger.logAuthEvent(userId, 'registration', 'success', {
            ip,
            userAgent,
            username
          });

          const token = jwt.sign(
            { userId, role: 'user' },
            JWT_SECRET,
            { expiresIn: JWT_EXPIRES_IN }
          );

          // CWE-778: Log token issuance
          await SecurityAuditLogger.logTokenIssuance(userId, 'JWT', {
            ip,
            userAgent,
            expiresIn: JWT_EXPIRES_IN,
            purpose: 'registration'
          });

          // Create session
          await createSession(userId, token, req);

          // Set secure HTTP-only cookie
          res.cookie('auth_token', token, {
            httpOnly: true,
            secure: process.env.NODE_ENV === 'production',
            sameSite: 'strict',
            maxAge: 7 * 24 * 60 * 60 * 1000 // 7 days
          });

          res.status(201).json({
            message: 'Registration successful',
            token,
            user: {
              id: userId,
              username,
              email,
              role: 'user'
            }
          });
        }
      );
    } catch (error) {
      logger.error('Registration error:', error);
      await SecurityAuditLogger.logAuthEvent(null, 'registration_attempt', 'error', {
        ip,
        userAgent,
        error: error.message
      });
      res.status(500).json({ error: 'Registration failed' });
    }
  }
);

// Login with strict rate limiting and account lockout
router.post('/login', authLimiter, enforceAccountLockout,
  [
    body('username').trim().notEmpty(),
    body('password').notEmpty()
  ],
  async (req, res) => {
    const ip = req.ip || req.headers['x-forwarded-for'] || req.connection.remoteAddress;
    const userAgent = req.headers['user-agent'];

    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(400).json({ errors: errors.array() });
    }

    const { username, password } = req.body;

    try {
      db.get(
        'SELECT * FROM users WHERE username = ? OR email = ?',
        [username, username],
        async (err, user) => {
          if (err) {
            logger.error('Login error:', err);
            return res.status(500).json({ error: 'Login failed' });
          }

          if (!user) {
            await recordFailedLogin(username, ip, userAgent);
            return res.status(401).json({ error: 'Invalid credentials' });
          }

          // Check if user is active
          if (!user.is_active) {
            await SecurityAuditLogger.logLoginFailure(username, 'Account disabled', { ip, userAgent });
            return res.status(403).json({ error: 'Account is disabled. Contact an administrator.' });
          }

          const isValidPassword = await bcrypt.compare(password, user.password);
          if (!isValidPassword) {
            await recordFailedLogin(username, ip, userAgent);
            return res.status(401).json({ error: 'Invalid credentials' });
          }

          // Clear failed attempts on successful password check
          await clearFailedAttempts(user.id);

          // Check password expiry
          const expiryStatus = await checkPasswordExpiry(user.id);
          if (expiryStatus.expired) {
            await SecurityAuditLogger.logLoginFailure(username, 'Password expired', { ip, userAgent });
            return res.status(403).json({
              error: expiryStatus.message,
              passwordExpired: true,
              requirePasswordChange: true
            });
          }

          // Check if 2FA is enabled
          if (user.two_factor_enabled) {
            // Create temporary token for 2FA verification
            const tempToken = jwt.sign(
              { userId: user.id, temp: true, purpose: '2fa' },
              JWT_SECRET,
              { expiresIn: '10m' }
            );

            // CWE-778: Log temp token issuance for 2FA
            await SecurityAuditLogger.logTokenIssuance(user.id, 'TEMP_2FA', {
              ip,
              userAgent,
              expiresIn: '10m',
              purpose: '2fa'
            });

            await SecurityAuditLogger.logAuthEvent(user.id, '2fa_required', 'pending', { ip, userAgent });

            return res.json({
              require2FA: true,
              tempToken,
              userId: user.id,
              passwordWarning: expiryStatus.warning ? expiryStatus.message : null
            });
          }

          const token = jwt.sign(
            { userId: user.id, role: user.role }, 
            JWT_SECRET, 
            { expiresIn: JWT_EXPIRES_IN }
          );

          // CWE-778: Log token issuance
          await SecurityAuditLogger.logTokenIssuance(user.id, 'JWT', {
            ip,
            userAgent,
            expiresIn: JWT_EXPIRES_IN,
            purpose: 'login'
          });

          // Update last login
          db.run(
            'UPDATE users SET last_login_at = ?, last_login_ip = ? WHERE id = ?',
            [new Date().toISOString(), ip, user.id]
          );

          // Create session
          await createSession(user.id, token, req);

          // Log successful login
          await SecurityAuditLogger.logLoginSuccess(user.id, { ip, userAgent });

          // Set secure HTTP-only cookie
          res.cookie('auth_token', token, {
            httpOnly: true,
            secure: process.env.NODE_ENV === 'production',
            sameSite: 'strict',
            maxAge: 7 * 24 * 60 * 60 * 1000 // 7 days
          });

          res.json({
            message: 'Login successful',
            token,
            user: {
              id: user.id,
              username: user.username,
              email: user.email,
              role: user.role,
              must_change_password: user.must_change_password === 1
            },
            passwordWarning: expiryStatus.warning ? expiryStatus.message : null
          });
        }
      );
    } catch (error) {
      logger.error('Login error:', error);
      await SecurityAuditLogger.logAuthEvent(null, 'login_attempt', 'error', {
        ip,
        userAgent,
        error: error.message
      });
      res.status(500).json({ error: 'Login failed' });
    }
  }
);

// Verify 2FA and complete login
router.post('/verify-2fa', authLimiter,
  [
    body('tempToken').notEmpty(),
    body('code').notEmpty()
  ],
  async (req, res) => {
    const ip = req.ip || req.headers['x-forwarded-for'] || req.connection.remoteAddress;
    const userAgent = req.headers['user-agent'];

    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(400).json({ errors: errors.array() });
    }

    const { tempToken, code } = req.body;

    try {
      // Verify temp token
      const decoded = jwt.verify(tempToken, JWT_SECRET);
      
      if (!decoded.temp || decoded.purpose !== '2fa') {
        return res.status(401).json({ error: 'Invalid token' });
      }

      const speakeasy = require('speakeasy');
      
      db.get(
        'SELECT * FROM users WHERE id = ?',
        [decoded.userId],
        async (err, user) => {
          if (err || !user) {
            logger.error('2FA verify - user not found:', err);
            return res.status(401).json({ error: 'Invalid token' });
          }

          if (!user.two_factor_enabled) {
            return res.status(400).json({ error: '2FA not enabled for this user' });
          }

          // Check if it's a backup code
          db.get(
            'SELECT id FROM two_factor_backup_codes WHERE user_id = ? AND code = ? AND used = 0',
            [user.id, code.toUpperCase()],
            async (err, backupCode) => {
              if (backupCode) {
                // Mark backup code as used
                db.run(
                  'UPDATE two_factor_backup_codes SET used = 1, used_at = CURRENT_TIMESTAMP WHERE id = ?',
                  [backupCode.id]
                );

                logger.info(`Backup code used for user ${user.id}`);
                
                // Log 2FA success with backup code
                await SecurityAuditLogger.log2FAEvent(user.id, 'backup_code_used', 'success', { ip, userAgent });
                
                // Generate full token
                const token = jwt.sign(
                  { userId: user.id, role: user.role },
                  JWT_SECRET,
                  { expiresIn: JWT_EXPIRES_IN }
                );

                // CWE-778: Log token issuance after 2FA backup code
                await SecurityAuditLogger.logTokenIssuance(user.id, 'JWT', {
                  ip,
                  userAgent,
                  expiresIn: JWT_EXPIRES_IN,
                  purpose: '2fa_backup_verification'
                });

                // Update last login
                db.run(
                  'UPDATE users SET last_login_at = ?, last_login_ip = ? WHERE id = ?',
                  [new Date().toISOString(), ip, user.id]
                );

                // Create session
                await createSession(user.id, token, req);

                // Log successful login
                await SecurityAuditLogger.logLoginSuccess(user.id, { ip, userAgent, method: '2fa_backup' });

                // Set secure HTTP-only cookie
                res.cookie('auth_token', token, {
                  httpOnly: true,
                  secure: process.env.NODE_ENV === 'production',
                  sameSite: 'strict',
                  maxAge: 7 * 24 * 60 * 60 * 1000 // 7 days
                });

                return res.json({
                  message: 'Login successful with backup code',
                  token,
                  user: {
                    id: user.id,
                    username: user.username,
                    email: user.email,
                    role: user.role,
                    must_change_password: user.must_change_password === 1
                  }
                });
              }

              // Verify TOTP code
              const verified = speakeasy.totp.verify({
                secret: user.two_factor_secret,
                encoding: 'base32',
                token: code,
                window: 2
              });

              if (!verified) {
                await SecurityAuditLogger.log2FAEvent(user.id, 'totp_verification', 'failed', { 
                  ip, 
                  userAgent,
                  reason: 'Invalid code'
                });
                return res.status(400).json({ error: 'Invalid 2FA code' });
              }

              // Log 2FA success
              await SecurityAuditLogger.log2FAEvent(user.id, 'totp_verification', 'success', { ip, userAgent });

              // Generate full token
              const token = jwt.sign(
                { userId: user.id, role: user.role },
                JWT_SECRET,
                { expiresIn: JWT_EXPIRES_IN }
              );

              // CWE-778: Log token issuance after TOTP 2FA
              await SecurityAuditLogger.logTokenIssuance(user.id, 'JWT', {
                ip,
                userAgent,
                expiresIn: JWT_EXPIRES_IN,
                purpose: '2fa_totp_verification'
              });

              // Update last login
              db.run(
                'UPDATE users SET last_login_at = ?, last_login_ip = ? WHERE id = ?',
                [new Date().toISOString(), ip, user.id]
              );

              // Create session
              await createSession(user.id, token, req);

              // Log successful login
              await SecurityAuditLogger.logLoginSuccess(user.id, { ip, userAgent, method: '2fa_totp' });

              // Set secure HTTP-only cookie
              res.cookie('auth_token', token, {
                httpOnly: true,
                secure: process.env.NODE_ENV === 'production',
                sameSite: 'strict',
                maxAge: 7 * 24 * 60 * 60 * 1000 // 7 days
              });

              res.json({
                message: 'Login successful',
                token,
                user: {
                  id: user.id,
                  username: user.username,
                  email: user.email,
                  role: user.role,
                  must_change_password: user.must_change_password === 1
                }
              });
            }
          );
        }
      );
    } catch (error) {
      logger.error('2FA verify error:', error);
      if (error.name === 'TokenExpiredError') {
        return res.status(401).json({ error: '2FA session expired. Please login again' });
      }
      res.status(500).json({ error: 'Failed to verify 2FA' });
    }
  }
);

// Change password with enhanced security
router.post('/change-password',
  [
    body('currentPassword').notEmpty(),
    body('newPassword').notEmpty()
  ],
  async (req, res) => {
    const token = req.headers.authorization?.split(' ')[1];
    const ip = req.ip || req.headers['x-forwarded-for'] || req.connection.remoteAddress;
    const userAgent = req.headers['user-agent'];

    if (!token) {
      return res.status(401).json({ error: 'No token provided' });
    }

    try {
      const decoded = jwt.verify(token, JWT_SECRET);
      const { currentPassword, newPassword } = req.body;

      db.get(
        'SELECT * FROM users WHERE id = ?',
        [decoded.userId],
        async (err, user) => {
          if (err || !user) {
            return res.status(404).json({ error: 'User not found' });
          }

          const isValidPassword = await bcrypt.compare(currentPassword, user.password);
          if (!isValidPassword) {
            await SecurityAuditLogger.logPasswordChange(user.id, 'failed', { 
              ip, 
              userAgent, 
              reason: 'Incorrect current password' 
            });
            return res.status(401).json({ error: 'Current password is incorrect' });
          }

          // Validate new password
          const passwordValidation = validatePassword(newPassword, user.username, user.email);
          if (!passwordValidation.valid) {
            await SecurityAuditLogger.logPasswordChange(user.id, 'failed', {
              ip,
              userAgent,
              reason: passwordValidation.errors.join(', ')
            });
            return res.status(400).json({
              error: 'New password does not meet requirements',
              details: passwordValidation.errors,
              strength: calculatePasswordStrength(newPassword)
            });
          }

          // Check password history (prevent reuse of last 5 passwords)
          db.all(
            'SELECT password_hash FROM password_history WHERE user_id = ? ORDER BY changed_at DESC LIMIT 5',
            [user.id],
            async (err, history) => {
              if (err) {
                logger.error('Password history check error:', err);
                return res.status(500).json({ error: 'Failed to change password' });
              }

              // Check if new password matches any recent password
              for (const record of history || []) {
                const matches = await bcrypt.compare(newPassword, record.password_hash);
                if (matches) {
                  await SecurityAuditLogger.logPasswordChange(user.id, 'failed', {
                    ip,
                    userAgent,
                    reason: 'Password reused from history'
                  });
                  return res.status(400).json({
                    error: 'Cannot reuse any of your last 5 passwords'
                  });
                }
              }

              const hashedPassword = await bcrypt.hash(newPassword, 10);

              // Save old password to history
              db.run(
                'INSERT INTO password_history (user_id, password_hash) VALUES (?, ?)',
                [user.id, user.password]
              );

              // Update password
              db.run(
                'UPDATE users SET password = ?, must_change_password = 0, updated_at = CURRENT_TIMESTAMP WHERE id = ?',
                [hashedPassword, user.id],
                async (err) => {
                  if (err) {
                    logger.error('Password change error:', err);
                    return res.status(500).json({ error: 'Failed to change password' });
                  }

                  // Update password expiry
                  await updatePasswordExpiry(user.id);

                  // CWE-778: Revoke all tokens on password change (security best practice)
                  await SecurityAuditLogger.logTokenRevocation(user.id, 'password_change', {
                    ip,
                    userAgent
                  });

                  // Log successful password change
                  await SecurityAuditLogger.logPasswordChange(user.id, 'success', { ip, userAgent });

                  res.json({ message: 'Password changed successfully' });
                }
              );
            }
          );
        }
      );
    } catch (error) {
      logger.error('Password change error:', error);
      res.status(401).json({ error: 'Invalid token' });
    }
  }
);

// Verify token
router.get('/verify', async (req, res) => {
  const token = req.headers.authorization?.split(' ')[1];

  if (!token) {
    return res.status(401).json({ error: 'No token provided' });
  }

  try {
    const decoded = jwt.verify(token, JWT_SECRET);
    db.get(
      'SELECT id, username, email, role, must_change_password, is_active FROM users WHERE id = ?',
      [decoded.userId],
      async (err, user) => {
        if (err || !user) {
          return res.status(401).json({ error: 'Invalid token' });
        }

        if (!user.is_active) {
          return res.status(403).json({ error: 'Account is disabled' });
        }

        // Check password expiry for warning
        const expiryStatus = await checkPasswordExpiry(user.id);

        res.json({ 
          valid: true, 
          user: {
            ...user,
            must_change_password: user.must_change_password === 1
          },
          passwordWarning: expiryStatus.warning ? expiryStatus.message : null,
          daysUntilExpiry: expiryStatus.daysRemaining || null
        });
      }
    );
  } catch (error) {
    res.status(401).json({ error: 'Invalid token' });
  }
});

// Check password strength
router.post('/check-password-strength',
  [body('password').notEmpty()],
  (req, res) => {
    const { password, username, email } = req.body;
    
    const validation = validatePassword(password, username, email);
    const strength = calculatePasswordStrength(password);

    res.json({
      valid: validation.valid,
      errors: validation.errors,
      strength: {
        score: strength.score,
        level: strength.level,
        feedback: strength.feedback
      }
    });
  }
);

// Get security status for current user
router.get('/security-status', async (req, res) => {
  const token = req.headers.authorization?.split(' ')[1];

  if (!token) {
    return res.status(401).json({ error: 'No token provided' });
  }

  try {
    const decoded = jwt.verify(token, JWT_SECRET);

    db.get(
      'SELECT id, username, two_factor_enabled, password_changed_at, password_expires_at, last_login_at, last_login_ip, failed_login_attempts FROM users WHERE id = ?',
      [decoded.userId],
      async (err, user) => {
        if (err || !user) {
          return res.status(401).json({ error: 'Invalid token' });
        }

        // Get active sessions count
        const sessions = await new Promise((resolve, reject) => {
          db.all(
            'SELECT COUNT(*) as count, MAX(last_activity) as last_activity FROM active_sessions WHERE user_id = ? AND expires_at > ?',
            [user.id, new Date().toISOString()],
            (err, rows) => err ? reject(err) : resolve(rows[0])
          );
        });

        // Get recent security events
        let recentEvents = [];
        try {
          recentEvents = await SecurityAuditLogger.getUserSecurityEvents(user.id, 10);
        } catch (eventErr) {
          logger.error('Error fetching security events:', eventErr);
        }

        // Check password expiry
        const expiryStatus = await checkPasswordExpiry(user.id);

        res.json({
          twoFactorEnabled: user.two_factor_enabled === 1,
          passwordAge: user.password_changed_at ? 
            Math.floor((Date.now() - new Date(user.password_changed_at)) / (24 * 60 * 60 * 1000)) : null,
          passwordExpiry: expiryStatus,
          lastLogin: {
            timestamp: user.last_login_at,
            ip: user.last_login_ip
          },
          activeSessions: sessions.count || 0,
          lastActivity: sessions.last_activity,
          failedLoginAttempts: user.failed_login_attempts || 0,
          recentEvents: Array.isArray(recentEvents) ? recentEvents.map(e => ({
            type: e.event_type,
            status: e.status,
            timestamp: e.created_at
          })) : []
        });
      }
    );
  } catch (error) {
    logger.error('Security status error:', error);
    res.status(401).json({ error: 'Invalid token' });
  }
});

// Logout endpoint - invalidate session and clear cookie
router.post('/logout', authenticate, async (req, res) => {
  const token = req.headers.authorization?.replace('Bearer ', '');
  const ip = req.ip || req.headers['x-forwarded-for'] || req.connection.remoteAddress;
  const userAgent = req.headers['user-agent'];

  try {
    // Delete the session from database
    await new Promise((resolve, reject) => {
      db.run(
        'DELETE FROM active_sessions WHERE session_token = ?',
        [token],
        (err) => err ? reject(err) : resolve()
      );
    });

    // CWE-778: Log token revocation on logout
    await SecurityAuditLogger.logTokenRevocation(req.user.userId, 'user_logout', {
      ip,
      userAgent
    });

    // Log logout event
    await SecurityAuditLogger.logAuthEvent(req.user.userId, 'logout', 'success', {
      ip,
      userAgent
    });

    // Clear the HTTP-only cookie
    res.clearCookie('auth_token', {
      httpOnly: true,
      secure: process.env.NODE_ENV === 'production',
      sameSite: 'strict'
    });

    res.json({ message: 'Logout successful' });
  } catch (error) {
    logger.error('Logout error:', error);
    res.status(500).json({ error: 'Logout failed' });
  }
});

module.exports = router;
