// src/routes/authRoutes.js
const express = require('express');
const crypto = require('crypto');
const db = require('../db');
const { sendOtpSms } = require('../services/smsService');
const { createOtp, verifyOtp } = require('../services/otpService');
const {
  signAccessToken,
  issueRefreshToken,
  verifyRefreshToken,
  rotateRefreshToken,
  revokeRefreshToken,
} = require('../services/tokenService');
// === ADDED FOR RATE LIMITING ===
const {
  checkActiveOtpForPhone,
  rateLimitRequestOtpByPhone,
  rateLimitRequestOtpByIp,
  rateLimitVerifyOtpByPhone,
  incrementFailedVerify,
} = require('../middleware/rateLimitMiddleware');
// === SECURITY HARDENING: INPUT VALIDATION ===
const {
  validateRequestOtpBody,
  validateVerifyOtpBody,
  validateRefreshTokenBody,
  validateLogoutBody,
} = require('../middleware/validation');
// === SECURITY HARDENING: IP/DEVICE RISK ===
const {
  isIpBlocked,
  calculateRiskScore,
  getPreviousAuthInfo,
  REQUIRE_OTP_ON_SUSPICIOUS_REFRESH,
} = require('../services/riskScoring');
// === SECURITY HARDENING: AUDIT LOGS & ANOMALY FLAGS ===
const {
  logAuthEvent,
  logSuspiciousOtpAttempt,
  logBlockedIpLogin,
  logSuspiciousRefresh,
  checkAnomalies,
  RISK_LEVELS,
} = require('../services/auditLogger');

const router = express.Router();

// helper: normalize phone
function normalizePhone(phone) {
  const p = phone.trim().replace(/\s+/g, ''); // Remove spaces
  if (p.startsWith('+')) return p;
  // if user enters 10-digit, prepend +91
  if (p.length === 10) return '+91' + p;
  return p; // fallback
}

// helper: validate phone number format
function isValidPhoneNumber(phone) {
  // E.164 format: + followed by 1-15 digits
  // Reject short codes (5-6 digits)
  const e164Pattern = /^\+[1-9]\d{1,14}$/;
  
  if (!e164Pattern.test(phone)) {
    return false;
  }
  
  // Reject short codes (typically 5-6 digits total)
  const digitsOnly = phone.replace(/\D/g, '');
  if (digitsOnly.length <= 6) {
    return false; // Likely a short code
  }
  
  return true;
}


function sanitizeDeviceId(deviceId) {
  if (!deviceId || typeof deviceId !== 'string') {
    return 'unknown-device';
  }
  const trimmed = deviceId.trim();
  if (/^[A-Za-z0-9:_-]{4,128}$/.test(trimmed)) {
    return trimmed;
  }
  const hash = crypto
    .createHash('sha256')
    .update(trimmed)
    .digest('hex')
    .slice(0, 32);
  return `anon-${hash}`;
}

// POST /auth/request-otp
// === SECURITY HARDENING: INPUT VALIDATION ===
// === ADDED FOR RATE LIMITING ===
// Middleware order matters:
// 1. Input validation
// 2. Check for active OTP first (2-minute no-resend rule)
// 3. Rate limit by phone number
// 4. Rate limit by IP address
// 5. IP blocking check
router.post(
  '/request-otp',
  validateRequestOtpBody,
  checkActiveOtpForPhone,
  rateLimitRequestOtpByPhone,
  rateLimitRequestOtpByIp,
  async (req, res) => {
    // === SECURITY HARDENING: IP/DEVICE RISK ===
    // Check if IP is blocked
    const clientIp = req.ip || req.connection.remoteAddress || 'unknown';
    if (isIpBlocked(clientIp)) {
      await logBlockedIpLogin(clientIp, req.headers['user-agent']);
      return res.status(403).json({
        success: false,
        message: 'Access denied from this location.',
      });
    }
    try {
      const { phone_number } = req.body;
      if (!phone_number) {
        return res.status(400).json({ error: 'phone_number is required' });
      }

      const normalizedPhone = normalizePhone(phone_number);

      // Validate phone number format
      if (!isValidPhoneNumber(normalizedPhone)) {
        return res.status(400).json({ 
          error: 'Invalid phone number format. Please use E.164 format (e.g., +919876543210)' 
        });
      }

      const { code } = await createOtp(normalizedPhone);

      // Attempt to send SMS (will fallback to safe logging if Twilio fails)
      const smsResult = await sendOtpSms(normalizedPhone, code);
      
      // === SECURITY HARDENING: AUDIT LOGS & ANOMALY FLAGS ===
      // Log OTP request event
      await logAuthEvent({
        action: 'otp_request',
        status: smsResult?.success ? 'success' : 'failed',
        riskLevel: RISK_LEVELS.INFO,
        ipAddress: clientIp,
        userAgent: req.headers['user-agent'],
        meta: {
          phone: normalizedPhone.replace(/\d(?=\d{4})/g, '*'), // Mask phone
        },
      });
      
      // Even if SMS fails, we still return success because OTP is generated
      // In production, you may want to return an error if SMS fails
      if (!smsResult || !smsResult.success) {
        console.warn('⚠️ SMS sending failed, but OTP was generated');
        // Option 1: Still return success (current behavior - allows testing)
        // Option 2: Return error (uncomment below for production)
        // return res.status(500).json({ error: 'Failed to send OTP via SMS' });
      }

      return res.json({ ok: true });

    } catch (err) {
      console.error('request-otp error', err);
      return res.status(500).json({ error: 'Internal server error' });
    }
  }
);

// POST /auth/verify-otp
// === SECURITY HARDENING: INPUT VALIDATION ===
// === ADDED FOR OTP ATTEMPT LIMIT ===
// Rate limit failed verification attempts per phone
router.post(
  '/verify-otp',
  validateVerifyOtpBody,
  rateLimitVerifyOtpByPhone,
  async (req, res) => {
    // === SECURITY HARDENING: IP/DEVICE RISK ===
    // Check if IP is blocked
    const clientIp = req.ip || req.connection.remoteAddress || 'unknown';
    if (isIpBlocked(clientIp)) {
      await logBlockedIpLogin(clientIp, req.headers['user-agent']);
      return res.status(403).json({
        success: false,
        message: 'Access denied from this location.',
      });
    }
    try {
      const { phone_number, code, device_id, device_info } = req.body;
      if (!phone_number || !code) {
        return res.status(400).json({ error: 'phone_number and code are required' });
      }

      const normalizedPhone = normalizePhone(phone_number);

      const result = await verifyOtp(normalizedPhone, code);
      
      // === ADDED FOR OTP ATTEMPT LIMIT ===
      // === SECURITY HARDENING: AUDIT LOGS & ANOMALY FLAGS ===
      // Use generic error message to avoid leaking information
      if (!result.ok) {
        // Increment failed verification counter
        try {
          await incrementFailedVerify(normalizedPhone);
        } catch (err) {
          console.error('Failed to increment failed verify counter:', err);
          // Don't fail the request if counter increment fails
        }
        
        // Log suspicious OTP attempt
        await logSuspiciousOtpAttempt(
          normalizedPhone,
          clientIp,
          req.headers['user-agent'],
          result.reason || 'invalid'
        );
        
        return res.status(400).json({ 
          success: false,
          message: 'OTP invalid or expired. Please request a new one.' 
        });
      }

    // find or create user
    let user;
    const found = await db.query(
      `SELECT id, phone_number, name, role, NULL::user_type_enum as user_type
       FROM users
       WHERE phone_number = $1`,
      [normalizedPhone]
    );

    if (found.rows.length === 0) {
      const inserted = await db.query(
        `INSERT INTO users (phone_number)
         VALUES ($1)
         RETURNING id, phone_number, name, role, NULL::user_type_enum as user_type`,
        [normalizedPhone]
      );
      user = inserted.rows[0];
    } else {
      user = found.rows[0];
    }

    // update last_login_at
    await db.query(
      `UPDATE users SET last_login_at = NOW() WHERE id = $1`,
      [user.id]
    );

    const devId = sanitizeDeviceId(device_id);

    // Check if this is a new device for existing account
    const existingDevice = await db.query(
      `SELECT id FROM user_devices WHERE user_id = $1 AND device_identifier = $2`,
      [user.id, devId]
    );
    const isNewDevice = existingDevice.rows.length === 0;
    const isExistingAccount = found.rows.length > 0;

    // upsert user_devices row
    await db.query(
      `
      INSERT INTO user_devices (
        user_id, device_identifier, device_platform, device_model,
        os_version, app_version, language_code, timezone, last_seen_at
      )
      VALUES ($1, $2, $3, $4, $5, $6, $7, $8, NOW())
      ON CONFLICT (user_id, device_identifier)
      DO UPDATE SET last_seen_at = EXCLUDED.last_seen_at,
                    device_platform = EXCLUDED.device_platform,
                    device_model = EXCLUDED.device_model,
                    os_version = EXCLUDED.os_version,
                    app_version = EXCLUDED.app_version,
                    is_active = true
      `,
      [
        user.id,
        devId,
        device_info?.platform || 'unknown',
        device_info?.model || null,
        device_info?.os_version || null,
        device_info?.app_version || null,
        device_info?.language_code || null,
        device_info?.timezone || null,
      ]
    );

    // === SECURITY HARDENING: IP/DEVICE RISK ===
    // Calculate risk score for this login
    const previousAuth = await getPreviousAuthInfo(user.id, devId);
    const riskScore = await calculateRiskScore({
      userId: user.id,
      currentIp: clientIp,
      currentUserAgent: req.headers['user-agent'],
      currentDeviceId: devId,
      currentDeviceInfo: device_info,
      previousIp: previousAuth.previousIp,
      previousUserAgent: previousAuth.previousUserAgent,
      previousDeviceId: previousAuth.previousDeviceId,
    });

    // === SECURITY HARDENING: AUDIT LOGS & ANOMALY FLAGS ===
    // Log authentication event with risk level
    await logAuthEvent({
      userId: user.id,
      action: 'login',
      status: 'success',
      riskLevel: riskScore.isSuspicious 
        ? (riskScore.score >= 50 ? RISK_LEVELS.HIGH_RISK : RISK_LEVELS.SUSPICIOUS)
        : RISK_LEVELS.INFO,
      deviceId: devId,
      ipAddress: clientIp,
      userAgent: req.headers['user-agent'],
      meta: {
        is_new_device: isNewDevice,
        is_new_account: !isExistingAccount,
        platform: device_info?.platform || 'unknown',
        risk_score: riskScore.score,
        risk_reasons: riskScore.reasons,
      },
    });

    // Check for anomalies
    await checkAnomalies(user.id, 'otp_verify', clientIp);

    // === SECURITY HARDENING: ACCESS TOKEN REPLAY MITIGATION ===
    // Issue access token with high_assurance flag if this is a fresh OTP verification
    // This allows step-up auth for sensitive actions
    const accessToken = signAccessToken(user, { highAssurance: true });
    const refreshToken = await issueRefreshToken({
      userId: user.id,
      deviceId: devId,
      userAgent: req.headers['user-agent'],
      ip: clientIp,
    });

    const needsProfile = !user.name || !user.user_type;

    // Get count of active devices
    const deviceCountResult = await db.query(
      `SELECT COUNT(*) as count FROM user_devices WHERE user_id = $1 AND is_active = true`,
      [user.id]
    );
    const activeDevicesCount = parseInt(deviceCountResult.rows[0].count, 10);

    return res.json({
      user,
      access_token: accessToken,
      refresh_token: refreshToken,
      needs_profile: needsProfile,
      is_new_device: isNewDevice,
      is_new_account: !isExistingAccount,
      active_devices_count: activeDevicesCount,
    });
  } catch (err) {
    console.error('verify-otp error', err);
    return res.status(500).json({ error: 'Internal server error' });
  }
});

// POST /auth/refresh
// === SECURITY HARDENING: INPUT VALIDATION ===
// === SECURITY HARDENING: REFRESH TOKEN THEFT MITIGATION ===
// === SECURITY HARDENING: CSRF (FUTURE-PROOFING) ===
// NOTE: If tokens are ever moved to HTTP-only cookies, CSRF protection becomes mandatory.
// Consider implementing: SameSite cookie attribute + CSRF token validation
router.post(
  '/refresh',
  validateRefreshTokenBody,
  async (req, res) => {
    try {
      const { refresh_token } = req.body;
      const clientIp = req.ip || req.connection.remoteAddress || 'unknown';

      // === SECURITY HARDENING: IP/DEVICE RISK ===
      // Check if IP is blocked
      if (isIpBlocked(clientIp)) {
        await logBlockedIpLogin(clientIp, req.headers['user-agent']);
        return res.status(403).json({ error: 'Access denied from this location.' });
      }

      const verification = await verifyRefreshToken(refresh_token);
      if (!verification || verification.reuseDetected) {
        return res.status(401).json({ error: 'Invalid refresh token' });
      }

      const { userId, deviceId, row: tokenRow } = verification;

      const { rows } = await db.query(
        `SELECT id, phone_number, name, role, user_type FROM users WHERE id = $1`,
        [userId]
      );
      if (rows.length === 0) {
        return res.status(401).json({ error: 'User not found' });
      }

      const user = rows[0];

      // === SECURITY HARDENING: REFRESH TOKEN THEFT MITIGATION ===
      // Calculate risk score for refresh from different environment
      const previousAuth = await getPreviousAuthInfo(userId, deviceId);
      const riskScore = await calculateRiskScore({
        userId,
        currentIp: clientIp,
        currentUserAgent: req.headers['user-agent'],
        currentDeviceId: deviceId,
        currentDeviceInfo: null,
        previousIp: tokenRow.ip_address || previousAuth.previousIp,
        previousUserAgent: tokenRow.user_agent || previousAuth.previousUserAgent,
        previousDeviceId: deviceId,
      });

      // === SECURITY HARDENING: REFRESH TOKEN THEFT MITIGATION ===
      // If refresh is from suspicious environment, log it
      if (riskScore.isSuspicious) {
        await logSuspiciousRefresh(
          userId,
          deviceId,
          clientIp,
          req.headers['user-agent'],
          riskScore.score,
          riskScore.reasons
        );

        // Optionally require OTP re-verification for suspicious refreshes
        if (REQUIRE_OTP_ON_SUSPICIOUS_REFRESH) {
          return res.status(403).json({
            error: 'step_up_required',
            message: 'Additional verification required. Please verify your OTP.',
            requires_otp: true,
          });
        }
      }

      const newAccess = signAccessToken(user);
      const newRefresh = await rotateRefreshToken({
        tokenRow,
        userAgent: req.headers['user-agent'],
        ip: clientIp,
      });

      // === SECURITY HARDENING: AUDIT LOGS & ANOMALY FLAGS ===
      // Log refresh event
      await logAuthEvent({
        userId,
        action: 'token_refresh',
        status: 'success',
        riskLevel: riskScore.isSuspicious 
          ? (riskScore.score >= 50 ? RISK_LEVELS.HIGH_RISK : RISK_LEVELS.SUSPICIOUS)
          : RISK_LEVELS.INFO,
        deviceId,
        ipAddress: clientIp,
        userAgent: req.headers['user-agent'],
        meta: {
          risk_score: riskScore.score,
          risk_reasons: riskScore.reasons,
        },
      });

    // Update device last_seen_at when tokens are refreshed
    try {
      await db.query(
        `UPDATE user_devices SET last_seen_at = NOW() WHERE user_id = $1 AND device_identifier = $2`,
        [userId, deviceId]
      );
    } catch (deviceErr) {
      console.error('Failed to update device last_seen_at', deviceErr);
      // Don't fail the refresh if device update fails
    }

    return res.json({ access_token: newAccess, refresh_token: newRefresh });
  } catch (err) {
    console.error('refresh error', err);
    return res.status(500).json({ error: 'Internal server error' });
  }
});

// POST /auth/logout
// === SECURITY HARDENING: INPUT VALIDATION ===
router.post(
  '/logout',
  validateLogoutBody,
  async (req, res) => {
    try {
      const { refresh_token } = req.body;
      const clientIp = req.ip || req.connection.remoteAddress || 'unknown';

      const info = await verifyRefreshToken(refresh_token);
      if (!info || info.reuseDetected) {
        return res.status(200).json({ ok: true }); // already invalid -> treat as logged out
      }

      await revokeRefreshToken(info.userId, info.deviceId);

      // === SECURITY HARDENING: AUDIT LOGS & ANOMALY FLAGS ===
      await logAuthEvent({
        userId: info.userId,
        action: 'logout',
        status: 'success',
        riskLevel: RISK_LEVELS.INFO,
        deviceId: info.deviceId,
        ipAddress: clientIp,
        userAgent: req.headers['user-agent'],
      });

      return res.json({ ok: true });
    } catch (err) {
      console.error('logout error', err);
      return res.status(500).json({ error: 'Internal server error' });
    }
  }
);

module.exports = router;
