// src/services/otpService.js
const bcrypt = require('bcrypt');
const db = require('../db');
const { markActiveOtp } = require('../middleware/rateLimitMiddleware');

// === ADDED FOR 2-MIN OTP VALIDITY & NO-RESEND ===
// OTP validity changed from 10 minutes to 2 minutes (120 seconds)
const OTP_TTL_SECONDS = parseInt(process.env.OTP_TTL_SECONDS || '120', 10); // 2 minutes
const OTP_EXPIRY_MS = OTP_TTL_SECONDS * 1000;
const MAX_OTP_ATTEMPTS = Number(process.env.OTP_VERIFY_MAX_ATTEMPTS || process.env.OTP_MAX_ATTEMPTS || 5);

let otpTableReadyPromise;

function ensureOtpCodesTable() {
  if (!otpTableReadyPromise) {
    otpTableReadyPromise = db.query(`
      CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
      CREATE TABLE IF NOT EXISTS otp_codes (
        id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
        phone_number VARCHAR(20) NOT NULL,
        otp_hash VARCHAR(255) NOT NULL,
        expires_at TIMESTAMPTZ NOT NULL,
        attempt_count INT NOT NULL DEFAULT 0,
        created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
      );
      CREATE INDEX IF NOT EXISTS idx_otp_codes_phone ON otp_codes (phone_number);
      CREATE INDEX IF NOT EXISTS idx_otp_codes_expires ON otp_codes (expires_at);
      DO $$
      BEGIN
        IF EXISTS (
          SELECT 1 FROM information_schema.columns
          WHERE table_name = 'otp_codes' AND column_name = 'code'
        ) THEN
          ALTER TABLE otp_codes RENAME COLUMN code TO otp_hash;
        END IF;
        IF EXISTS (
          SELECT 1 FROM information_schema.columns
          WHERE table_name = 'otp_codes' AND column_name = 'verified_at'
        ) THEN
          ALTER TABLE otp_codes DROP COLUMN verified_at;
        END IF;
        IF NOT EXISTS (
          SELECT 1 FROM information_schema.columns
          WHERE table_name = 'otp_codes' AND column_name = 'attempt_count'
        ) THEN
          ALTER TABLE otp_codes ADD COLUMN attempt_count INT NOT NULL DEFAULT 0;
        END IF;
      END $$;
    `);
  }
  return otpTableReadyPromise;
}

/**
 * Generate a random 6-digit OTP code
 */
function generateOtpCode() {
  return Math.floor(100000 + Math.random() * 900000).toString();
}

/**
 * Create and store an OTP for a phone number
 * @param {string} phoneNumber - The phone number to send OTP to
 * @returns {Promise<{code: string}>} - The generated OTP code
 */
async function createOtp(phoneNumber) {
  await ensureOtpCodesTable();
  const code = generateOtpCode();
  const expiresAt = new Date(Date.now() + OTP_EXPIRY_MS);
  const otpHash = await bcrypt.hash(code, 10);

  // === ADDED FOR 2-MIN OTP VALIDITY & NO-RESEND ===
  // Delete any existing OTPs for this phone number
  await db.query(
    'DELETE FROM otp_codes WHERE phone_number = $1',
    [phoneNumber]
  );

  // Insert new OTP
  await db.query(
    `INSERT INTO otp_codes (phone_number, otp_hash, expires_at, attempt_count)
     VALUES ($1, $2, $3, 0)`,
    [phoneNumber, otpHash, expiresAt]
  );

  // === ADDED FOR 2-MIN OTP VALIDITY & NO-RESEND ===
  // Mark OTP as active in Redis/memory store to prevent resend within 2 minutes
  try {
    await markActiveOtp(phoneNumber, OTP_TTL_SECONDS);
  } catch (err) {
    console.error('Failed to mark active OTP in rate limiter:', err);
    // Don't fail OTP creation if rate limiter fails
  }

  return { code };
}

/**
 * Verify an OTP code for a phone number
 * @param {string} phoneNumber - The phone number
 * @param {string} code - The OTP code to verify
 * @returns {Promise<{ok: boolean}>} - Whether the OTP is valid
 */
async function verifyOtp(phoneNumber, code) {
  await ensureOtpCodesTable();
  const result = await db.query(
    `SELECT id, otp_hash, expires_at, attempt_count
     FROM otp_codes
     WHERE phone_number = $1
     ORDER BY created_at DESC
     LIMIT 1`,
    [phoneNumber]
  );

  // === ADDED FOR OTP ATTEMPT LIMIT ===
  // Generic error message to avoid leaking information
  if (result.rows.length === 0) {
    return { ok: false, reason: 'not_found' };
  }

  const otpRecord = result.rows[0];

  // Check if expired
  if (new Date() > new Date(otpRecord.expires_at)) {
    await db.query('DELETE FROM otp_codes WHERE id = $1', [otpRecord.id]);
    return { ok: false, reason: 'expired' };
  }

  // === ADDED FOR OTP ATTEMPT LIMIT ===
  // Check if max attempts exceeded
  if (otpRecord.attempt_count >= MAX_OTP_ATTEMPTS) {
    await db.query('DELETE FROM otp_codes WHERE id = $1', [otpRecord.id]);
    return { ok: false, reason: 'max_attempts' };
  }

  const matches = await bcrypt.compare(code, otpRecord.otp_hash);

  // Check if code matches
  if (!matches) {
    // === ADDED FOR OTP ATTEMPT LIMIT ===
    // Increment attempt count
    await db.query(
      'UPDATE otp_codes SET attempt_count = attempt_count + 1 WHERE id = $1',
      [otpRecord.id]
    );
    return { ok: false, reason: 'invalid' };
  }

  // === ADDED FOR 2-MIN OTP VALIDITY & NO-RESEND ===
  // Delete OTP once verified to prevent reuse
  // Also clears the active OTP marker (via deletion, Redis TTL will handle cleanup)
  await db.query('DELETE FROM otp_codes WHERE id = $1', [otpRecord.id]);

  return { ok: true };
}

/**
 * Clean up expired OTPs (can be called periodically)
 */
async function cleanupExpiredOtps() {
  await ensureOtpCodesTable();
  await db.query(
    'DELETE FROM otp_codes WHERE expires_at < NOW()'
  );
}

module.exports = {
  createOtp,
  verifyOtp,
  cleanupExpiredOtps,
};
