const express = require("express");
const { User } = require("../models");
const TwoFactorService = require("../services/TwoFactorService");
const emailServices = require("../services/email");
const logger = require("../utils/logger");
const { authenticateToken } = require("../middleware/auth");
const { requireStepUpAuth } = require("../middleware/stepUpAuth");
const { csrfProtection } = require("../middleware/csrf");
const {
  sanitizeInput,
  validateTotpCode,
  validateEmailOtp,
  validateRecoveryCode,
} = require("../middleware/validation");
const {
  twoFactorVerificationLimiter,
  twoFactorSetupLimiter,
  recoveryCodeLimiter,
  emailOtpSendLimiter,
} = require("../middleware/rateLimiter");

const router = express.Router();

// Helper for structured security audit logging
const auditLog = (req, action, userId, details = {}) => {
  logger.info({
    type: 'security_audit',
    action,
    userId,
    ip: req.ip,
    userAgent: req.get('User-Agent'),
    ...details,
  });
};

// All routes require authentication
router.use(authenticateToken);

// ============================================
// SETUP ENDPOINTS
// ============================================

/**
 * POST /api/2fa/setup/totp/init
 * Initialize TOTP setup - generate secret and QR code
 */
router.post(
  "/setup/totp/init",
  twoFactorSetupLimiter,
  csrfProtection,
  async (req, res) => {
    try {
      const user = await User.findByPk(req.user.id);

      if (!user) {
        return res.status(404).json({ error: "User not found" });
      }

      if (user.twoFactorEnabled) {
        return res.status(400).json({
          error: "Multi-factor authentication is already enabled",
        });
      }

      // Generate TOTP secret and QR code
      const { qrCodeDataUrl, encryptedSecret, encryptedSecretIv } =
        await TwoFactorService.generateTotpSecret(user.email);

      // Store pending secret for verification
      await user.storePendingTotpSecret(encryptedSecret, encryptedSecretIv);

      auditLog(req, '2fa.setup.initiated', user.id, { method: 'totp' });

      res.json({
        qrCodeDataUrl,
        message: "Scan the QR code with your authenticator app",
      });
    } catch (error) {
      logger.error("TOTP setup init error:", error);
      res.status(500).json({ error: "Failed to initialize TOTP setup" });
    }
  }
);

/**
 * POST /api/2fa/setup/totp/verify
 * Verify TOTP code and enable 2FA
 */
router.post(
  "/setup/totp/verify",
  twoFactorSetupLimiter,
  csrfProtection,
  sanitizeInput,
  validateTotpCode,
  async (req, res) => {
    try {
      const { code } = req.body;
      const user = await User.findByPk(req.user.id);

      if (!user) {
        return res.status(404).json({ error: "User not found" });
      }

      if (user.twoFactorEnabled) {
        return res.status(400).json({
          error: "Multi-factor authentication is already enabled",
        });
      }

      if (!user.twoFactorSetupPendingSecret) {
        return res.status(400).json({
          error: "No pending TOTP setup. Please start the setup process again.",
        });
      }

      // Verify the code against the pending secret
      const isValid = user.verifyPendingTotpCode(code);

      if (!isValid) {
        return res.status(400).json({
          error: "Invalid verification code. Please try again.",
        });
      }

      // Generate recovery codes
      const { codes: recoveryCodes } =
        await TwoFactorService.generateRecoveryCodes();

      // Enable TOTP
      await user.enableTotp(recoveryCodes);

      // Send confirmation email
      try {
        await emailServices.auth.sendTwoFactorEnabledEmail(user);
      } catch (emailError) {
        logger.error("Failed to send 2FA enabled email:", emailError);
        // Don't fail the request if email fails
      }

      auditLog(req, '2fa.setup.completed', user.id, { method: 'totp' });

      res.json({
        message: "Multi-factor authentication enabled successfully",
        recoveryCodes,
        warning:
          "Save these recovery codes in a secure location. You will not be able to see them again.",
      });
    } catch (error) {
      logger.error("TOTP setup verify error:", error);
      res.status(500).json({ error: "Failed to enable multi-factor authentication" });
    }
  }
);

/**
 * POST /api/2fa/setup/email/init
 * Initialize email 2FA setup - send verification code
 */
router.post(
  "/setup/email/init",
  twoFactorSetupLimiter,
  emailOtpSendLimiter,
  csrfProtection,
  async (req, res) => {
    try {
      const user = await User.findByPk(req.user.id);

      if (!user) {
        return res.status(404).json({ error: "User not found" });
      }

      if (user.twoFactorEnabled) {
        return res.status(400).json({
          error: "Multi-factor authentication is already enabled",
        });
      }

      // Generate and send email OTP
      const otpCode = await user.generateEmailOtp();

      try {
        await emailServices.auth.sendTwoFactorOtpEmail(user, otpCode);
      } catch (emailError) {
        logger.error("Failed to send 2FA setup OTP email:", emailError);
        return res.status(500).json({ error: "Failed to send verification email" });
      }

      auditLog(req, '2fa.setup.initiated', user.id, { method: 'email' });

      res.json({
        message: "Verification code sent to your email",
      });
    } catch (error) {
      logger.error("Email 2FA setup init error:", error);
      res.status(500).json({ error: "Failed to initialize email 2FA setup" });
    }
  }
);

/**
 * POST /api/2fa/setup/email/verify
 * Verify email OTP and enable email 2FA
 */
router.post(
  "/setup/email/verify",
  twoFactorSetupLimiter,
  csrfProtection,
  sanitizeInput,
  validateEmailOtp,
  async (req, res) => {
    try {
      const { code } = req.body;
      const user = await User.findByPk(req.user.id);

      if (!user) {
        return res.status(404).json({ error: "User not found" });
      }

      if (user.twoFactorEnabled) {
        return res.status(400).json({
          error: "Multi-factor authentication is already enabled",
        });
      }

      if (user.isEmailOtpLocked()) {
        return res.status(429).json({
          error: "Too many failed attempts. Please request a new code.",
        });
      }

      // Verify the OTP
      const isValid = user.verifyEmailOtp(code);

      if (!isValid) {
        await user.incrementEmailOtpAttempts();
        return res.status(400).json({
          error: "Invalid or expired verification code",
        });
      }

      // Generate recovery codes
      const { codes: recoveryCodes } =
        await TwoFactorService.generateRecoveryCodes();

      // Enable email 2FA
      await user.enableEmailTwoFactor(recoveryCodes);
      await user.clearEmailOtp();

      // Send confirmation email
      try {
        await emailServices.auth.sendTwoFactorEnabledEmail(user);
      } catch (emailError) {
        logger.error("Failed to send 2FA enabled email:", emailError);
      }

      auditLog(req, '2fa.setup.completed', user.id, { method: 'email' });

      res.json({
        message: "Multi-factor authentication enabled successfully",
        recoveryCodes,
        warning:
          "Save these recovery codes in a secure location. You will not be able to see them again.",
      });
    } catch (error) {
      logger.error("Email 2FA setup verify error:", error);
      res.status(500).json({ error: "Failed to enable multi-factor authentication" });
    }
  }
);

// ============================================
// VERIFICATION ENDPOINTS (Step-up auth)
// ============================================

/**
 * POST /api/2fa/verify/totp
 * Verify TOTP code for step-up authentication
 */
router.post(
  "/verify/totp",
  twoFactorVerificationLimiter,
  csrfProtection,
  sanitizeInput,
  validateTotpCode,
  async (req, res) => {
    try {
      const { code } = req.body;
      const user = await User.findByPk(req.user.id);

      if (!user) {
        return res.status(404).json({ error: "User not found" });
      }

      if (!user.twoFactorEnabled || user.twoFactorMethod !== "totp") {
        logger.warn(`2FA verify failed for user ${user.id}: TOTP not enabled or wrong method`);
        return res.status(400).json({
          error: "Verification failed",
        });
      }

      const isValid = user.verifyTotpCode(code);

      if (!isValid) {
        auditLog(req, '2fa.verify.failed', user.id, { method: 'totp' });
        return res.status(400).json({
          error: "Invalid verification code",
        });
      }

      // Mark code as used for replay protection
      await user.markTotpCodeUsed(code);

      // Update step-up session
      await user.updateStepUpSession();

      auditLog(req, '2fa.verify.success', user.id, { method: 'totp' });

      res.json({
        message: "Verification successful",
        verified: true,
      });
    } catch (error) {
      logger.error("TOTP verification error:", error);
      res.status(500).json({ error: "Verification failed" });
    }
  }
);

/**
 * POST /api/2fa/verify/email/send
 * Send email OTP for step-up authentication
 */
router.post(
  "/verify/email/send",
  emailOtpSendLimiter,
  csrfProtection,
  async (req, res) => {
    try {
      const user = await User.findByPk(req.user.id);

      if (!user) {
        return res.status(404).json({ error: "User not found" });
      }

      if (!user.twoFactorEnabled) {
        logger.warn(`2FA verify failed for user ${user.id}: 2FA not enabled`);
        return res.status(400).json({
          error: "Verification failed",
        });
      }

      // Generate and send email OTP
      const otpCode = await user.generateEmailOtp();

      try {
        await emailServices.auth.sendTwoFactorOtpEmail(user, otpCode);
      } catch (emailError) {
        logger.error("Failed to send 2FA OTP email:", emailError);
        return res.status(500).json({ error: "Failed to send verification email" });
      }

      auditLog(req, '2fa.otp.sent', user.id, { method: 'email' });

      res.json({
        message: "Verification code sent to your email",
      });
    } catch (error) {
      logger.error("Email OTP send error:", error);
      res.status(500).json({ error: "Failed to send verification code" });
    }
  }
);

/**
 * POST /api/2fa/verify/email
 * Verify email OTP for step-up authentication
 */
router.post(
  "/verify/email",
  twoFactorVerificationLimiter,
  csrfProtection,
  sanitizeInput,
  validateEmailOtp,
  async (req, res) => {
    try {
      const { code } = req.body;
      const user = await User.findByPk(req.user.id);

      if (!user) {
        return res.status(404).json({ error: "User not found" });
      }

      if (!user.twoFactorEnabled) {
        logger.warn(`2FA verify failed for user ${user.id}: 2FA not enabled`);
        return res.status(400).json({
          error: "Verification failed",
        });
      }

      if (user.isEmailOtpLocked()) {
        return res.status(429).json({
          error: "Too many failed attempts. Please request a new code.",
        });
      }

      const isValid = user.verifyEmailOtp(code);

      if (!isValid) {
        await user.incrementEmailOtpAttempts();
        auditLog(req, '2fa.verify.failed', user.id, { method: 'email' });
        return res.status(400).json({
          error: "Invalid or expired verification code",
        });
      }

      // Update step-up session and clear OTP
      await user.updateStepUpSession();
      await user.clearEmailOtp();

      auditLog(req, '2fa.verify.success', user.id, { method: 'email' });

      res.json({
        message: "Verification successful",
        verified: true,
      });
    } catch (error) {
      logger.error("Email OTP verification error:", error);
      res.status(500).json({ error: "Verification failed" });
    }
  }
);

/**
 * POST /api/2fa/verify/recovery
 * Use recovery code for step-up authentication
 */
router.post(
  "/verify/recovery",
  recoveryCodeLimiter,
  csrfProtection,
  sanitizeInput,
  validateRecoveryCode,
  async (req, res) => {
    try {
      const { code } = req.body;
      const user = await User.findByPk(req.user.id);

      if (!user) {
        return res.status(404).json({ error: "User not found" });
      }

      if (!user.twoFactorEnabled) {
        logger.warn(`2FA verify failed for user ${user.id}: 2FA not enabled`);
        return res.status(400).json({
          error: "Verification failed",
        });
      }

      const { valid, remainingCodes } = await user.useRecoveryCode(code);

      if (!valid) {
        auditLog(req, '2fa.verify.failed', user.id, { method: 'recovery' });
        return res.status(400).json({
          error: "Invalid recovery code",
        });
      }

      // Send alert email about recovery code usage
      try {
        await emailServices.auth.sendRecoveryCodeUsedEmail(user, remainingCodes);
      } catch (emailError) {
        logger.error("Failed to send recovery code used email:", emailError);
      }

      auditLog(req, '2fa.recovery.used', user.id, { lowCodes: remainingCodes <= 2 });

      res.json({
        message: "Verification successful",
        verified: true,
        remainingCodes,
        warning:
          remainingCodes <= 2
            ? "You are running low on recovery codes. Please generate new ones."
            : null,
      });
    } catch (error) {
      logger.error("Recovery code verification error:", error);
      res.status(500).json({ error: "Verification failed" });
    }
  }
);

// ============================================
// MANAGEMENT ENDPOINTS
// ============================================

/**
 * GET /api/2fa/status
 * Get current 2FA status for the user
 */
router.get("/status", async (req, res) => {
  try {
    const user = await User.findByPk(req.user.id);

    if (!user) {
      return res.status(404).json({ error: "User not found" });
    }

    res.json({
      enabled: user.twoFactorEnabled,
      method: user.twoFactorMethod,
      hasRecoveryCodes: user.getRemainingRecoveryCodes() > 0,
      lowRecoveryCodes: user.getRemainingRecoveryCodes() <= 2,
    });
  } catch (error) {
    logger.error("2FA status error:", error);
    res.status(500).json({ error: "Failed to get 2FA status" });
  }
});

/**
 * POST /api/2fa/disable
 * Disable 2FA (requires step-up authentication)
 */
router.post(
  "/disable",
  csrfProtection,
  requireStepUpAuth("2fa_disable"),
  async (req, res) => {
    try {
      const user = await User.findByPk(req.user.id);

      if (!user) {
        return res.status(404).json({ error: "User not found" });
      }

      if (!user.twoFactorEnabled) {
        logger.warn(`2FA disable failed for user ${user.id}: 2FA not enabled`);
        return res.status(400).json({
          error: "Operation failed",
        });
      }

      await user.disableTwoFactor();

      // Send notification email
      try {
        await emailServices.auth.sendTwoFactorDisabledEmail(user);
      } catch (emailError) {
        logger.error("Failed to send 2FA disabled email:", emailError);
      }

      auditLog(req, '2fa.disabled', user.id);

      res.json({
        message: "Multi-factor authentication has been disabled",
      });
    } catch (error) {
      logger.error("2FA disable error:", error);
      res.status(500).json({ error: "Failed to disable multi-factor authentication" });
    }
  }
);

/**
 * POST /api/2fa/recovery/regenerate
 * Generate new recovery codes (requires step-up authentication)
 */
router.post(
  "/recovery/regenerate",
  csrfProtection,
  requireStepUpAuth("recovery_regenerate"),
  async (req, res) => {
    try {
      const user = await User.findByPk(req.user.id);

      if (!user) {
        return res.status(404).json({ error: "User not found" });
      }

      if (!user.twoFactorEnabled) {
        logger.warn(`Recovery regenerate failed for user ${user.id}: 2FA not enabled`);
        return res.status(400).json({
          error: "Operation failed",
        });
      }

      const recoveryCodes = await user.regenerateRecoveryCodes();

      auditLog(req, '2fa.recovery.regenerated', user.id);

      res.json({
        recoveryCodes,
        warning:
          "Save these recovery codes in a secure location. Your previous codes are now invalid.",
      });
    } catch (error) {
      logger.error("Recovery code regeneration error:", error);
      res.status(500).json({ error: "Failed to regenerate recovery codes" });
    }
  }
);

/**
 * GET /api/2fa/recovery/remaining
 * Get recovery codes status (not exact count for security)
 */
router.get("/recovery/remaining", async (req, res) => {
  try {
    const user = await User.findByPk(req.user.id);

    if (!user) {
      return res.status(404).json({ error: "User not found" });
    }

    const remaining = user.getRemainingRecoveryCodes();
    res.json({
      hasRecoveryCodes: remaining > 0,
      lowRecoveryCodes: remaining <= 2,
    });
  } catch (error) {
    logger.error("Recovery codes remaining error:", error);
    res.status(500).json({ error: "Failed to get recovery codes status" });
  }
});

module.exports = router;
