// src/routes/authRoutes.js
const express = require('express');
const crypto = require('crypto');
const db = require('../db');
const config = require('../config');
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,
  checkEnumerationRateLimit,
  blockIpForEnumeration,
  isIpBlockedForEnumeration,
} = require('../middleware/rateLimitMiddleware');
// === SECURITY HARDENING: INPUT VALIDATION ===
const {
  validateRequestOtpBody,
  validateVerifyOtpBody,
  validateRefreshTokenBody,
  validateLogoutBody,
  validateSignupBody,
} = 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');
// === SECURITY HARDENING: FIELD-LEVEL ENCRYPTION ===
const { encryptPhoneNumber, decryptPhoneNumber } = require('../utils/fieldEncryption');
const { preparePhoneSearchParams } = require('../utils/encryptedPhoneSearch');
// === SECURITY HARDENING: TIMING ATTACK PROTECTION ===
const { executeOtpRequestWithTiming, otpRequestDelay, executeOtpVerifyWithTiming, otpVerifyDelay } = require('../utils/timingProtection');
// === SECURITY HARDENING: ENUMERATION DETECTION ===
const { detectAndLogEnumeration } = require('../utils/enumerationDetection');

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.',
      });
    }
    
    // === SECURITY HARDENING: ENUMERATION PROTECTION ===
    // Check if IP is blocked for enumeration attempts
    if (await isIpBlockedForEnumeration(clientIp)) {
      return res.status(429).json({
        success: false,
        message: 'Too many requests. Please try again later.',
      });
    }
    
    try {
      // === SECURITY HARDENING: TIMING ATTACK PROTECTION ===
      // Execute all work with timing protection to prevent enumeration
      // This ensures all requests take similar time regardless of phone existence
      const result = await executeOtpRequestWithTiming(async () => {
        const { phone_number } = req.body;
        if (!phone_number) {
          return { error: 'phone_number is required', status: 400 };
        }

        const normalizedPhone = normalizePhone(phone_number);

        // === SECURITY HARDENING: ENUMERATION DETECTION ===
        // Check for enumeration attempts before processing
        const enumerationCheck = await detectAndLogEnumeration(
          clientIp,
          normalizedPhone,
          req.headers['user-agent']
        );

        // Apply stricter rate limiting if enumeration detected
        if (enumerationCheck.isEnumeration) {
          const enumRateLimit = await checkEnumerationRateLimit(clientIp);
          if (!enumRateLimit.allowed) {
            // Block IP if enumeration rate limit exceeded
            await blockIpForEnumeration(clientIp);
            return {
              error: enumRateLimit.reason || 'Too many requests. Please try again later.',
              status: 429,
            };
          }
        }

        // Block high-risk enumeration attempts immediately
        if (enumerationCheck.shouldBlock) {
          await blockIpForEnumeration(clientIp);
          return {
            error: 'Too many requests. IP blocked due to enumeration attempts.',
            status: 429,
          };
        }

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

        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 { error: 'Failed to send OTP via SMS', status: 500 };
        }

        return { ok: true, status: 200 };
      });

      // Handle errors or return success
      if (result.error) {
        return res.status(result.status || 500).json({ error: result.error });
      }

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

    } catch (err) {
      console.error('request-otp error', err);
      // === SECURITY HARDENING: TIMING ATTACK PROTECTION ===
      // Even on error, ensure minimum delay to prevent timing leaks
      await otpRequestDelay();
      return res.status(500).json({ error: 'Internal server error' });
    }
  }
);

// POST /auth/check-user
// Check if user exists by phone number (for sign-in flow)
// This prevents sending OTP to non-existent users
router.post(
  '/check-user',
  validateRequestOtpBody, // Reuse phone number validation
  async (req, res) => {
    const clientIp = req.ip || req.connection.remoteAddress || 'unknown';
    
    // Check if IP is blocked
    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({
          success: false,
          message: 'phone_number is required',
        });
      }

      // Normalize phone number
      const normalizedPhone = normalizePhone(phone_number);
      
      // Validate phone number format
      if (!isValidPhoneNumber(normalizedPhone)) {
        return res.status(400).json({
          success: false,
          message: 'Invalid phone number format. Please use E.164 format (e.g., +919876543210)',
        });
      }

      // === SECURITY HARDENING: FIELD-LEVEL ENCRYPTION ===
      // Encrypt phone number before searching
      const phoneSearchParams = preparePhoneSearchParams(normalizedPhone);

      // Check if user exists and has a name (fully registered)
      const found = await db.query(
        `SELECT id, name FROM users 
         WHERE (phone_number = $1 OR phone_number = $2)
           AND deleted = FALSE`,
        phoneSearchParams
      );

      if (found.rows.length === 0) {
        // User not found
        return res.status(404).json({
          success: false,
          error: 'USER_NOT_FOUND',
          message: 'User is not registered. Please sign up to create a new account.',
          user_exists: false
        });
      }

      const user = found.rows[0];
      // Check if user has a name (fully registered)
      const isFullyRegistered = user.name && user.name.trim() !== '';

      if (isFullyRegistered) {
        // User exists and is fully registered - should sign in
        return res.json({
          success: true,
          message: 'User is already registered. Please sign in instead.',
          user_exists: true
        });
      } else {
        // User exists but doesn't have a name (incomplete signup) - allow signup to continue
        return res.status(404).json({
          success: false,
          error: 'USER_NOT_FOUND',
          message: 'User is not registered. Please sign up to create a new account.',
          user_exists: false
        });
      }

    } catch (err) {
      console.error('check-user error', err);
      return res.status(500).json({
        success: false,
        message: '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 {
      // === SECURITY HARDENING: TIMING ATTACK PROTECTION ===
      // Execute all verification work with timing protection
      // This ensures all verification attempts take similar time regardless of outcome
      const verificationResult = await executeOtpVerifyWithTiming(async () => {
        const { phone_number, code, device_id, device_info } = req.body;
        if (!phone_number || !code) {
          return { error: 'phone_number and code are required', status: 400 };
        }

        const normalizedPhone = normalizePhone(phone_number);
        
        // Ensure code is a string (handle case where it might come as number from JSON)
        // bcrypt.compare requires string, and validation expects string
        // Note: If code comes as number, leading zeros may be lost - client should send as string
        const codeString = String(code);
        
        // Debug logging (remove in production or use proper logger)
        console.log('[OTP Verify] Request body:', JSON.stringify({
          phone_number: normalizedPhone.replace(/\d(?=\d{4})/g, '*'),
          code: codeString,
          code_type: typeof code,
          code_length: codeString.length,
          device_id: device_id?.substring(0, 20) + '...'
        }));

        const result = await verifyOtp(normalizedPhone, codeString);
        
        // === 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 { 
            success: false,
            message: 'OTP invalid or expired. Please request a new one.',
            status: 400
          };
        }

        return { ok: true, normalizedPhone, device_id, device_info };
      });

      // Handle verification failure
      if (verificationResult.error || !verificationResult.ok) {
        return res.status(verificationResult.status || 400).json({ 
          success: false,
          message: verificationResult.message || 'OTP invalid or expired. Please request a new one.' 
        });
      }

      // Continue with successful verification
      const { normalizedPhone, device_id, device_info } = verificationResult;

    // find or create user
    // === SECURITY HARDENING: FIELD-LEVEL ENCRYPTION ===
    // Encrypt phone number before storing/searching
    const encryptedPhone = encryptPhoneNumber(normalizedPhone);
    const phoneSearchParams = preparePhoneSearchParams(normalizedPhone);
    
    let user;
    // Check if token_version column exists, use it if available, otherwise default to 1
    const found = await db.query(
      `SELECT id, phone_number, name, role, NULL::user_type_enum as user_type, 
              COALESCE(token_version, 1) as token_version
       FROM users
       WHERE phone_number = $1 OR phone_number = $2`,
      phoneSearchParams
    ).catch(async (err) => {
      // If token_version column doesn't exist, try without it
      if (err.code === '42703' && err.message.includes('token_version')) {
        console.warn('token_version column not found, using default value of 1');
        return await db.query(
          `SELECT id, phone_number, name, role, NULL::user_type_enum as user_type, 1 as token_version
           FROM users
           WHERE phone_number = $1 OR phone_number = $2`,
          phoneSearchParams
        );
      }
      throw err;
    });

    if (found.rows.length === 0) {
      // User not found - create a minimal user for signup flow
      // Extract country code from phone number
      const countryCode = normalizedPhone.startsWith('+') 
        ? normalizedPhone.match(/^\+(\d{1,3})/)?.[0] || '+91'
        : '+91';
      
      // Create new user with minimal data (name will be added via signup endpoint)
      const newUserResult = await db.query(
        `INSERT INTO users (phone_number, country_code, language, timezone)
         VALUES ($1, $2, $3, $4)
         RETURNING id, phone_number, name, role, NULL::user_type_enum as user_type, 
                   COALESCE(token_version, 1) as token_version`,
        [
          encryptedPhone,
          countryCode,
          device_info?.language_code || null,
          device_info?.timezone || null
        ]
      ).catch(async (err) => {
        if (err.code === '42703' && err.message.includes('token_version')) {
          const result = await db.query(
            `INSERT INTO users (phone_number, country_code, language, timezone)
             VALUES ($1, $2, $3, $4)
             RETURNING id, phone_number, name, role, NULL::user_type_enum as user_type, 1 as token_version`,
            [
              encryptedPhone,
              countryCode,
              device_info?.language_code || null,
              device_info?.timezone || null
            ]
          );
          result.rows[0].token_version = 1;
          return result;
        }
        throw err;
      });
      
      user = newUserResult.rows[0];
    } else {
      user = found.rows[0];
    }
    
    // === SECURITY HARDENING: FIELD-LEVEL ENCRYPTION ===
    // Decrypt phone number before returning to client
    if (user.phone_number) {
      user.phone_number = decryptPhoneNumber(user.phone_number);
    }

    // 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;
    // Check if this is a new account (user was just created or has no name)
    const isExistingAccount = user.name && user.name.trim() !== '';

    // 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,
                    language_code = EXCLUDED.language_code,
                    timezone = EXCLUDED.timezone,
                    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 OTP verification event with risk level (needed for step-up authentication)
    await logAuthEvent({
      userId: user.id,
      action: 'otp_verify',
      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);
    // === SECURITY HARDENING: TIMING ATTACK PROTECTION ===
    // Even on error, ensure minimum delay to prevent timing leaks
    await otpVerifyDelay();
    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.' });
      }

      console.log(`[AUTH] Refresh token request received: ip=${clientIp}, userAgent=${req.headers['user-agent']}`);
      const verification = await verifyRefreshToken(refresh_token);
      if (!verification || verification.reuseDetected) {
        console.log(`[AUTH] Refresh token verification failed: reuseDetected=${verification?.reuseDetected || false}`);
        return res.status(401).json({ error: 'Invalid refresh token' });
      }
      console.log(`[AUTH] Refresh token verified: userId=${verification.userId}, deviceId=${verification.deviceId}`);

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

      const { rows } = await db.query(
        `SELECT id, phone_number, name, role, user_type, COALESCE(token_version, 1) as token_version 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,
          });
        }
      }

      // === SECURITY HARDENING: ACCESS TOKEN REPLAY MITIGATION ===
      // Preserve high_assurance status if there's been a recent OTP verification
      // This ensures step-up authentication remains valid after token refresh
      const RECENT_OTP_WINDOW_MINUTES = parseInt(process.env.STEP_UP_OTP_WINDOW_MINUTES || '5', 10);
      const recentOtpCheck = await db.query(
        `SELECT created_at
         FROM auth_audit
         WHERE user_id = $1
           AND action = 'otp_verify'
           AND status = 'success'
           AND created_at > NOW() - INTERVAL '${RECENT_OTP_WINDOW_MINUTES} minutes'
         ORDER BY created_at DESC
         LIMIT 1`,
        [userId]
      );
      const hasRecentOtp = recentOtpCheck.rows.length > 0;

      console.log(`[AUTH] Token refresh: userId=${userId}, deviceId=${deviceId}, ip=${clientIp}`);
      
      const newAccess = signAccessToken(user, { highAssurance: hasRecentOtp });
      console.log(`[AUTH] New access token generated: userId=${userId}, expiresIn=${config.jwtAccessTtl}`);
      
      const newRefresh = await rotateRefreshToken({
        tokenRow,
        userAgent: req.headers['user-agent'],
        ip: clientIp,
      });
      console.log(`[AUTH] New refresh token generated and rotated: userId=${userId}, deviceId=${deviceId}`);

      // === 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
    }

    console.log(`[AUTH] Token refresh successful: userId=${userId}, deviceId=${deviceId}`);
    return res.json({ access_token: newAccess, refresh_token: newRefresh });
  } catch (err) {
    console.error('[AUTH] 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' });
    }
  }
);

// POST /auth/signup
// Signup endpoint: Create new user with name, phone, and location
router.post(
  '/signup',
  validateSignupBody,
  async (req, res) => {
    const clientIp = req.ip || req.connection.remoteAddress || 'unknown';
    
    // Check if IP is blocked
    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 { name, phone_number, state, district, city_village, device_id, device_info } = req.body;

      // Normalize phone number
      const normalizedPhone = normalizePhone(phone_number);
      
      // Validate phone number format
      if (!isValidPhoneNumber(normalizedPhone)) {
        return res.status(400).json({
          success: false,
          message: 'Invalid phone number format. Please use E.164 format (e.g., +919876543210)',
        });
      }

      // === SECURITY HARDENING: FIELD-LEVEL ENCRYPTION ===
      // Encrypt phone number before storing/searching
      const encryptedPhone = encryptPhoneNumber(normalizedPhone);
      const phoneSearchParams = preparePhoneSearchParams(normalizedPhone);

      // Check if user already exists
      const existingUser = await db.query(
        `SELECT id, phone_number, name, role, NULL::user_type_enum as user_type, 
                COALESCE(token_version, 1) as token_version
         FROM users
         WHERE (phone_number = $1 OR phone_number = $2)
           AND deleted = FALSE`,
        phoneSearchParams
      ).catch(async (err) => {
        if (err.code === '42703' && err.message.includes('token_version')) {
          return await db.query(
            `SELECT id, phone_number, name, role, NULL::user_type_enum as user_type, 1 as token_version
             FROM users
             WHERE (phone_number = $1 OR phone_number = $2)
               AND deleted = FALSE`,
            phoneSearchParams
          );
        }
        throw err;
      });

      if (existingUser.rows.length > 0) {
        const existing = existingUser.rows[0];
        // Check if user was just created by verify-otp (has no name) - allow update
        if (!existing.name || existing.name.trim() === '') {
          // User exists but has no name (was just created by verify-otp), update it with signup data
          const countryCode = normalizedPhone.startsWith('+') 
            ? normalizedPhone.match(/^\+(\d{1,3})/)?.[0] || '+91'
            : '+91';
          
          // Update user with name, country code, language, and timezone
          const updatedUser = await db.query(
            `UPDATE users 
             SET name = $1, country_code = $2, language = $4, timezone = $5
             WHERE id = $3
             RETURNING id, phone_number, name, role, NULL::user_type_enum as user_type, 
                       COALESCE(token_version, 1) as token_version, country_code, created_at`,
            [
              name,
              countryCode,
              existing.id,
              device_info?.language_code || null,
              device_info?.timezone || null
            ]
          ).catch(async (err) => {
            if (err.code === '42703' && err.message.includes('token_version')) {
              const result = await db.query(
                `UPDATE users 
                 SET name = $1, country_code = $2, language = $4, timezone = $5
                 WHERE id = $3
                 RETURNING id, phone_number, name, role, NULL::user_type_enum as user_type, 
                           1 as token_version, country_code, created_at`,
                [
                  name,
                  countryCode,
                  existing.id,
                  device_info?.language_code || null,
                  device_info?.timezone || null
                ]
              );
              result.rows[0].token_version = 1;
              return result;
            }
            throw err;
          });

          const user = updatedUser.rows[0];

          // === SECURITY HARDENING: FIELD-LEVEL ENCRYPTION ===
          // Decrypt phone number before returning to client
          if (user.phone_number) {
            user.phone_number = decryptPhoneNumber(user.phone_number);
          }

          // Create location entry if location data provided
          let locationId = null;
          if (state || district || city_village) {
            const locationResult = await db.query(
              `INSERT INTO locations (user_id, state, district, city_village, is_saved_address, location_type, source_type, source_confidence)
               VALUES ($1, $2, $3, $4, TRUE, 'home', 'manual', 'high')
               RETURNING id`,
              [user.id, state || null, district || null, city_village || null]
            );
            locationId = locationResult.rows[0]?.id;
          }

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

          const devId = sanitizeDeviceId(device_id);

          // 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,
                          language_code = EXCLUDED.language_code,
                          timezone = EXCLUDED.timezone,
                          is_active = true
            `,
            [
              user.id,
              devId,
              device_info?.platform || 'android',
              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: AUDIT LOGS & ANOMALY FLAGS ===
          await logAuthEvent({
            userId: user.id,
            action: 'signup',
            status: 'success',
            riskLevel: RISK_LEVELS.INFO,
            deviceId: devId,
            ipAddress: clientIp,
            userAgent: req.headers['user-agent'],
            meta: {
              is_new_account: false,
              is_profile_completed: true,
              platform: device_info?.platform || 'android',
            },
          });

          // Issue tokens
          const accessToken = signAccessToken(user, { highAssurance: true });
          const refreshToken = await issueRefreshToken({
            userId: user.id,
            deviceId: devId,
            userAgent: req.headers['user-agent'],
            ip: clientIp,
          });

          // Check if profile needs completion (user_type not set)
          const needsProfile = !user.user_type || user.user_type === 'user';

          // 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({
            success: true,
            user: {
              id: user.id,
              phone_number: user.phone_number,
              name: user.name,
              country_code: user.country_code,
              created_at: user.created_at,
            },
            access_token: accessToken,
            refresh_token: refreshToken,
            needs_profile: needsProfile,
            is_new_account: false,
            is_new_device: true,
            active_devices_count: activeDevicesCount,
            location_id: locationId,
          });
        } else {
          // User already exists with a name - they should sign in instead
          return res.status(409).json({
            success: false,
            message: 'User with this phone number already exists. Please sign in instead.',
            user_exists: true,
          });
        }
      }

      // Extract country code from phone number
      const countryCode = normalizedPhone.startsWith('+') 
        ? normalizedPhone.match(/^\+(\d{1,3})/)?.[0] || '+91'
        : '+91';

      // Create new user with all available data
      const newUser = await db.query(
        `INSERT INTO users (phone_number, name, country_code, language, timezone)
         VALUES ($1, $2, $3, $4, $5)
         RETURNING id, phone_number, name, role, NULL::user_type_enum as user_type, 
                   COALESCE(token_version, 1) as token_version, country_code, created_at`,
        [
          encryptedPhone,
          name,
          countryCode,
          device_info?.language_code || null,
          device_info?.timezone || null
        ]
      ).catch(async (err) => {
        if (err.code === '42703' && err.message.includes('token_version')) {
          const result = await db.query(
            `INSERT INTO users (phone_number, name, country_code, language, timezone)
             VALUES ($1, $2, $3, $4, $5)
             RETURNING id, phone_number, name, role, NULL::user_type_enum as user_type, 
                       1 as token_version, country_code, created_at`,
            [
              encryptedPhone,
              name,
              countryCode,
              device_info?.language_code || null,
              device_info?.timezone || null
            ]
          );
          result.rows[0].token_version = 1;
          return result;
        }
        throw err;
      });

      const user = newUser.rows[0];

      // === SECURITY HARDENING: FIELD-LEVEL ENCRYPTION ===
      // Decrypt phone number before returning to client
      if (user.phone_number) {
        user.phone_number = decryptPhoneNumber(user.phone_number);
      }

      // Create location entry if location data provided
      let locationId = null;
      if (state || district || city_village) {
        const locationResult = await db.query(
          `INSERT INTO locations (user_id, state, district, city_village, is_saved_address, location_type, source_type, source_confidence)
           VALUES ($1, $2, $3, $4, TRUE, 'home', 'manual', 'high')
           RETURNING id`,
          [user.id, state || null, district || null, city_village || null]
        );
        locationId = locationResult.rows[0]?.id;
      }

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

      const devId = sanitizeDeviceId(device_id);

      // 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,
                      language_code = EXCLUDED.language_code,
                      timezone = EXCLUDED.timezone,
                      is_active = true
        `,
        [
          user.id,
          devId,
          device_info?.platform || 'android',
          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: AUDIT LOGS & ANOMALY FLAGS ===
      await logAuthEvent({
        userId: user.id,
        action: 'signup',
        status: 'success',
        riskLevel: RISK_LEVELS.INFO,
        deviceId: devId,
        ipAddress: clientIp,
        userAgent: req.headers['user-agent'],
        meta: {
          is_new_account: true,
          platform: device_info?.platform || 'android',
        },
      });

      // Issue tokens
      const accessToken = signAccessToken(user, { highAssurance: true });
      const refreshToken = await issueRefreshToken({
        userId: user.id,
        deviceId: devId,
        userAgent: req.headers['user-agent'],
        ip: clientIp,
      });

      // Check if profile needs completion (user_type not set)
      const needsProfile = !user.user_type || user.user_type === 'user';

      // 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({
        success: true,
        user: {
          id: user.id,
          phone_number: user.phone_number,
          name: user.name,
          country_code: user.country_code,
          created_at: user.created_at,
        },
        access_token: accessToken,
        refresh_token: refreshToken,
        needs_profile: needsProfile,
        is_new_account: true,
        is_new_device: true,
        active_devices_count: activeDevicesCount,
        location_id: locationId,
      });
    } catch (err) {
      console.error('signup error', err);
      
      // Handle unique constraint violation (phone number already exists)
      if (err.code === '23505' && err.constraint === 'users_phone_number_key') {
        return res.status(409).json({
          success: false,
          message: 'User with this phone number already exists. Please sign in instead.',
          user_exists: true,
        });
      }

      return res.status(500).json({
        success: false,
        message: 'Internal server error',
      });
    }
  }
);

// POST /auth/validate-token
// Endpoint for other services (like BuySellService) to validate JWT tokens
// This allows centralized token validation
router.post('/validate-token', async (req, res) => {
  const clientIp = req.ip || req.headers['x-forwarded-for']?.split(',')[0]?.trim() || req.headers['x-real-ip'] || 'unknown';
  const userAgent = req.headers['user-agent'] || 'unknown';
  
  console.log(`[Auth Service] POST /auth/validate-token - Request received`);
  console.log(`[Auth Service] Client IP: ${clientIp}`);
  console.log(`[Auth Service] User-Agent: ${userAgent.substring(0, 80)}`);
  console.log(`[Auth Service] Request body contains token: ${!!req.body?.token}`);

  try {
    const { token } = req.body;

    if (!token) {
      console.log(`[Auth Service] ❌ FAILED: Token is missing in request body`);
      return res.status(400).json({ 
        valid: false, 
        error: 'Token is required' 
      });
    }

    console.log(`[Auth Service] Token received (length: ${token.length} characters)`);

    // Use the auth middleware logic to validate token
    const jwt = require('jsonwebtoken');
    const { getKeySecret, getAllKeys, validateTokenClaims } = require('../services/jwtKeys');
    const db = require('../db');

    // Decode token to get key ID from header
    let decoded;
    try {
      decoded = jwt.decode(token, { complete: true });
      if (!decoded || !decoded.header) {
        console.log(`[Auth Service] ❌ FAILED: Invalid token format - unable to decode header`);
        return res.json({ valid: false, error: 'Invalid token format' });
      }
      console.log(`[Auth Service] Token decoded successfully`);
      console.log(`[Auth Service] Token header:`, {
        alg: decoded.header.alg,
        kid: decoded.header.kid || 'NOT SET',
        typ: decoded.header.typ,
      });
    } catch (err) {
      console.log(`[Auth Service] ❌ FAILED: Error decoding token - ${err.message}`);
      return res.json({ valid: false, error: 'Invalid token' });
    }

    // Get secret for the key ID (if present) or try all keys
    const keyId = decoded.header.kid;
    let payload = null;
    let verified = false;
    let verificationMethod = null;

    if (keyId) {
      console.log(`[Auth Service] Key ID found in token: ${keyId}`);
      const secret = getKeySecret(keyId);
      if (secret) {
        console.log(`[Auth Service] Attempting verification with key ID: ${keyId}`);
        try {
          payload = jwt.verify(token, secret);
          verified = true;
          verificationMethod = `keyId: ${keyId}`;
          console.log(`[Auth Service] ✅ Token verified successfully using key ID: ${keyId}`);
        } catch (err) {
          console.log(`[Auth Service] ❌ Verification failed with key ID ${keyId}: ${err.message}`);
          return res.json({ valid: false, error: 'Invalid or expired token' });
        }
      } else {
        console.log(`[Auth Service] ⚠️ Key ID ${keyId} not found in available keys, will try all keys`);
      }
    } else {
      console.log(`[Auth Service] ⚠️ No key ID in token header, trying all available keys`);
    }

    // If key ID not found or not specified, try all keys (for rotation support)
    if (!verified) {
      const allKeys = getAllKeys();
      console.log(`[Auth Service] Attempting verification with ${Object.keys(allKeys).length} available keys`);
      for (const [kid, keySecret] of Object.entries(allKeys)) {
        try {
          payload = jwt.verify(token, keySecret);
          verified = true;
          verificationMethod = `allKeys: ${kid}`;
          console.log(`[Auth Service] ✅ Token verified successfully using key: ${kid}`);
          break;
        } catch (err) {
          // Try next key silently
          continue;
        }
      }
      if (!verified) {
        console.log(`[Auth Service] ❌ FAILED: Token verification failed with all available keys`);
      }
    }

    if (!verified || !payload) {
      console.log(`[Auth Service] ❌ FINAL RESULT: Token is invalid or expired`);
      return res.json({ valid: false, error: 'Invalid or expired token' });
    }

    console.log(`[Auth Service] Token payload extracted:`, {
      userId: payload.sub,
      role: payload.role,
      userType: payload.user_type,
      exp: payload.exp ? new Date(payload.exp * 1000).toISOString() : 'NOT SET',
      iat: payload.iat ? new Date(payload.iat * 1000).toISOString() : 'NOT SET',
      tokenVersion: payload.token_version || 1,
    });

    // Validate JWT claims (iss, aud, exp, iat, nbf)
    console.log(`[Auth Service] Validating JWT claims...`);
    const claimsValidation = validateTokenClaims(payload);
    if (!claimsValidation.valid) {
      console.log(`[Auth Service] ❌ FAILED: Claims validation failed - ${claimsValidation.reason}`);
      return res.json({ valid: false, error: 'Invalid token claims' });
    }
    console.log(`[Auth Service] ✅ Claims validation passed`);

    // Validate token_version to ensure token hasn't been invalidated by logout-all-devices
    console.log(`[Auth Service] Checking token version in database for user: ${payload.sub}`);
    try {
      const { rows } = await db.query(
        `SELECT COALESCE(token_version, 1) as token_version FROM users WHERE id = $1`,
        [payload.sub]
      );
      
      if (rows.length === 0) {
        console.log(`[Auth Service] ❌ FAILED: User not found in database: ${payload.sub}`);
        return res.json({ valid: false, error: 'User not found' });
      }

      const userTokenVersion = rows[0].token_version;
      const tokenVersion = payload.token_version || 1;

      console.log(`[Auth Service] Token version check:`, {
        tokenVersion,
        userTokenVersion,
        match: tokenVersion === userTokenVersion,
      });

      // If token version doesn't match, token has been invalidated
      if (tokenVersion !== userTokenVersion) {
        console.log(`[Auth Service] ❌ FAILED: Token version mismatch - token has been invalidated`);
        return res.json({ valid: false, error: 'Token has been invalidated' });
      }
      console.log(`[Auth Service] ✅ Token version check passed`);
    } catch (dbErr) {
      // Handle missing token_version column gracefully
      if (dbErr.code === '42703' && dbErr.message && dbErr.message.includes('token_version')) {
        console.warn('[Auth Service] ⚠️ token_version column not found in database, skipping version check');
        // Continue without token version validation
      } else {
        console.error('[Auth Service] ❌ Error validating token version:', dbErr);
        return res.status(500).json({ valid: false, error: 'Internal server error' });
      }
    }

    // Token is valid - return user info
    console.log(`[Auth Service] ✅ FINAL RESULT: Token is VALID`);
    console.log(`[Auth Service] Returning user info:`, {
      userId: payload.sub,
      role: payload.role,
      userType: payload.user_type,
      verificationMethod,
    });

    return res.json({
      valid: true,
      payload: {
        sub: payload.sub,
        role: payload.role,
        user_type: payload.user_type,
        phone_number: payload.phone_number,
        token_version: payload.token_version || 1,
        high_assurance: payload.high_assurance || false,
      },
    });
  } catch (err) {
    console.error('[Auth Service] ❌ UNEXPECTED ERROR in validate-token:', err);
    console.error('[Auth Service] Error stack:', err.stack);
    return res.status(500).json({ valid: false, error: 'Internal server error' });
  }
});

module.exports = router;
