/**
 * Enhanced Security Middleware
 * Implements account lockout, password expiry, and session management
 */

const { db } = require('../database/db');
const { ACCOUNT_LOCKOUT, PASSWORD_EXPIRY, SESSION_POLICY } = require('../utils/passwordPolicy');
const SecurityAuditLogger = require('../utils/securityAudit');
const logger = require('../utils/logger');

/**
 * Check if account is locked
 */
async function checkAccountLockout(userId) {
  return new Promise((resolve, reject) => {
    db.get(
      'SELECT locked_until FROM users WHERE id = ?',
      [userId],
      (err, user) => {
        if (err) return reject(err);
        if (!user) return resolve({ locked: false });

        if (user.locked_until) {
          const lockoutEnd = new Date(user.locked_until);
          const now = new Date();

          if (now < lockoutEnd) {
            const remainingMinutes = Math.ceil((lockoutEnd - now) / 60000);
            return resolve({
              locked: true,
              remainingMinutes,
              message: `Account locked. Try again in ${remainingMinutes} minutes.`
            });
          } else {
            // Lockout expired, clear it
            db.run('UPDATE users SET locked_until = NULL, failed_login_attempts = 0 WHERE id = ?', [userId]);
            return resolve({ locked: false });
          }
        }

        resolve({ locked: false });
      }
    );
  });
}

/**
 * Record failed login attempt
 */
async function recordFailedLogin(username, ip, userAgent) {
  try {
    // Log to audit
    await SecurityAuditLogger.logLoginFailure(username, 'Invalid credentials', { ip, userAgent });

    // Get user ID
    const user = await new Promise((resolve, reject) => {
      db.get('SELECT id, failed_login_attempts FROM users WHERE username = ? OR email = ?', [username, username], (err, row) => {
        if (err) reject(err);
        else resolve(row);
      });
    });

    if (!user) return;

    const failedAttempts = (user.failed_login_attempts || 0) + 1;

    // Update failed attempts
    await new Promise((resolve, reject) => {
      db.run(
        'UPDATE users SET failed_login_attempts = ?, last_failed_login = ? WHERE id = ?',
        [failedAttempts, new Date().toISOString(), user.id],
        (err) => err ? reject(err) : resolve()
      );
    });

    // Check if lockout threshold reached
    if (failedAttempts >= ACCOUNT_LOCKOUT.maxFailedAttempts) {
      const lockUntil = new Date(Date.now() + ACCOUNT_LOCKOUT.lockoutDuration).toISOString();
      
      await new Promise((resolve, reject) => {
        db.run(
          'UPDATE users SET locked_until = ? WHERE id = ?',
          [lockUntil, user.id],
          (err) => err ? reject(err) : resolve()
        );
      });

      await SecurityAuditLogger.logAccountLockout(user.id, { 
        ip, 
        userAgent, 
        failedAttempts 
      });

      logger.warn(`Account locked for user ${username} after ${failedAttempts} failed attempts`);
    }
  } catch (error) {
    logger.error('Error recording failed login:', error);
  }
}

/**
 * Clear failed login attempts on successful login
 */
async function clearFailedAttempts(userId) {
  return new Promise((resolve, reject) => {
    db.run(
      'UPDATE users SET failed_login_attempts = 0, last_failed_login = NULL WHERE id = ?',
      [userId],
      (err) => err ? reject(err) : resolve()
    );
  });
}

/**
 * Check if password has expired
 */
async function checkPasswordExpiry(userId) {
  if (!PASSWORD_EXPIRY.enabled) {
    return { expired: false, warning: false };
  }

  return new Promise((resolve, reject) => {
    db.get(
      'SELECT password_changed_at, password_expires_at FROM users WHERE id = ?',
      [userId],
      (err, user) => {
        if (err) return reject(err);
        if (!user) return resolve({ expired: false, warning: false });

        const now = new Date();
        let expiryDate;

        if (user.password_expires_at) {
          expiryDate = new Date(user.password_expires_at);
        } else if (user.password_changed_at) {
          expiryDate = new Date(user.password_changed_at);
          expiryDate.setDate(expiryDate.getDate() + PASSWORD_EXPIRY.expiryDays);
        } else {
          // No password change date, set expiry from now
          expiryDate = new Date();
          expiryDate.setDate(expiryDate.getDate() + PASSWORD_EXPIRY.expiryDays);
        }

        const daysUntilExpiry = Math.ceil((expiryDate - now) / (24 * 60 * 60 * 1000));

        if (daysUntilExpiry <= 0) {
          return resolve({
            expired: true,
            warning: false,
            message: 'Your password has expired. Please change it to continue.',
            gracePeriodDays: PASSWORD_EXPIRY.gracePeriodDays
          });
        }

        if (daysUntilExpiry <= PASSWORD_EXPIRY.warningDays) {
          return resolve({
            expired: false,
            warning: true,
            daysRemaining: daysUntilExpiry,
            message: `Your password will expire in ${daysUntilExpiry} days. Please change it soon.`
          });
        }

        resolve({ expired: false, warning: false });
      }
    );
  });
}

/**
 * Update password expiry date
 */
async function updatePasswordExpiry(userId) {
  if (!PASSWORD_EXPIRY.enabled) return;

  const now = new Date();
  const expiryDate = new Date(now);
  expiryDate.setDate(expiryDate.getDate() + PASSWORD_EXPIRY.expiryDays);

  return new Promise((resolve, reject) => {
    db.run(
      'UPDATE users SET password_changed_at = ?, password_expires_at = ? WHERE id = ?',
      [now.toISOString(), expiryDate.toISOString(), userId],
      (err) => err ? reject(err) : resolve()
    );
  });
}

/**
 * Middleware: Check account lockout before authentication
 */
const enforceAccountLockout = async (req, res, next) => {
  const { username } = req.body;
  
  if (!username) {
    return next();
  }

  try {
    // Get user
    const user = await new Promise((resolve, reject) => {
      db.get('SELECT id FROM users WHERE username = ? OR email = ?', [username, username], (err, row) => {
        if (err) reject(err);
        else resolve(row);
      });
    });

    if (!user) {
      return next(); // User doesn't exist, let auth handle it
    }

    // Check lockout
    const lockoutStatus = await checkAccountLockout(user.id);
    
    if (lockoutStatus.locked) {
      return res.status(423).json({
        error: lockoutStatus.message,
        remainingMinutes: lockoutStatus.remainingMinutes,
        locked: true
      });
    }

    next();
  } catch (error) {
    logger.error('Account lockout check error:', error);
    next();
  }
};

/**
 * Middleware: Check password expiry after authentication
 */
const enforcePasswordExpiry = async (req, res, next) => {
  if (!req.user || !req.user.userId) {
    return next();
  }

  try {
    const expiryStatus = await checkPasswordExpiry(req.user.userId);

    if (expiryStatus.expired) {
      return res.status(403).json({
        error: expiryStatus.message,
        passwordExpired: true,
        gracePeriodDays: expiryStatus.gracePeriodDays,
        requirePasswordChange: true
      });
    }

    if (expiryStatus.warning) {
      // Add warning header but allow request
      res.setHeader('X-Password-Expiry-Warning', expiryStatus.message);
      res.setHeader('X-Password-Days-Remaining', expiryStatus.daysRemaining.toString());
    }

    next();
  } catch (error) {
    logger.error('Password expiry check error:', error);
    next();
  }
};

/**
 * Manage active sessions
 */
async function createSession(userId, token, req) {
  const ip = req.ip || req.headers['x-forwarded-for'] || req.connection.remoteAddress;
  const userAgent = req.headers['user-agent'];
  const expiresAt = new Date(Date.now() + SESSION_POLICY.absoluteTimeout);

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

  if (activeSessions >= SESSION_POLICY.maxConcurrentSessions) {
    // Remove oldest session
    await new Promise((resolve, reject) => {
      db.run(
        'DELETE FROM active_sessions WHERE id IN (SELECT id FROM active_sessions WHERE user_id = ? ORDER BY last_activity ASC LIMIT 1)',
        [userId],
        (err) => err ? reject(err) : resolve()
      );
    });
  }

  // Create new session
  return new Promise((resolve, reject) => {
    db.run(
      'INSERT INTO active_sessions (user_id, session_token, ip_address, user_agent, expires_at) VALUES (?, ?, ?, ?, ?)',
      [userId, token, ip, userAgent, expiresAt.toISOString()],
      (err) => err ? reject(err) : resolve()
    );
  });
}

/**
 * Update session activity
 */
async function updateSessionActivity(token) {
  return new Promise((resolve, reject) => {
    db.run(
      'UPDATE active_sessions SET last_activity = ? WHERE session_token = ?',
      [new Date().toISOString(), token],
      (err) => err ? reject(err) : resolve()
    );
  });
}

/**
 * Cleanup expired sessions
 */
async function cleanupExpiredSessions() {
  return new Promise((resolve, reject) => {
    db.run(
      'DELETE FROM active_sessions WHERE expires_at < ?',
      [new Date().toISOString()],
      function(err) {
        if (err) reject(err);
        else {
          if (this.changes > 0) {
            logger.info(`Cleaned up ${this.changes} expired sessions`);
          }
          resolve(this.changes);
        }
      }
    );
  });
}

// Run session cleanup every hour
setInterval(cleanupExpiredSessions, 60 * 60 * 1000);

module.exports = {
  checkAccountLockout,
  recordFailedLogin,
  clearFailedAttempts,
  checkPasswordExpiry,
  updatePasswordExpiry,
  enforceAccountLockout,
  enforcePasswordExpiry,
  createSession,
  updateSessionActivity,
  cleanupExpiredSessions
};
