const crypto = require("crypto");
const { sendMail, getConfig } = require("../services/email.service");
const { sendSms } = require("../services/sms.service");
const emailTemplateRenderer = require("../services/emailTemplateRenderer.service");
const OtpVerification = require("../models/otpVerification.model");
const ErrorHandler = require("./ErrorHandler");
const { getEnv } = require("./envLoader");
const webhookService = require("../services/webhook.service");

const OTP_EXPIRY_MS = 10 * 60 * 1000; // 5 minutes
const OTP_COOLDOWN_MS = 60 * 1000; // 60 seconds
const OTP_WINDOW_MS = 10 * 60 * 1000; // 10 minutes
const MAX_SENDS_PER_WINDOW = 3;
const MAX_ATTEMPTS = 5;
const MAX_EMAIL_RETRIES = 3;

const hashOtp = (otp) => crypto.createHash("sha256").update(otp).digest("hex");

const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

async function sendEmailWithRetry(mailOptions) {
  let attempt = 0;
  let lastError;
  while (attempt < MAX_EMAIL_RETRIES) {
    attempt += 1;
    try {
      const info = await sendMail(mailOptions);
      return info;
    } catch (error) {
      lastError = error;
      if (attempt < MAX_EMAIL_RETRIES) {
        await sleep(500 * attempt);
      }
    }
  }
  throw lastError;
}

const generateOtp = (length = 6) => {
  const digits = '0123456789';
  let otp = '';
  for (let i = 0; i < length; i++) {
    otp += digits[Math.floor(Math.random() * 10)];
  }
  return otp;
};

/**
 * Send OTP to Email or Phone
 * @param {Object} options
 * @param {string} options.identifier - Email or Phone Number
 * @param {string} options.type - 'email' or 'phone'
 * @param {string} [options.userId] - Optional User ID
 * @param {number} [options.length] - Optional OTP length (default 6)
 */
async function sendOtp({ identifier, type = 'email', userId, length = 6 }) {
  if (!identifier) {
    throw new ErrorHandler("Identifier (Email/Phone) is required to send OTP", 400);
  }

  const now = new Date();
  let record = await OtpVerification.findOne({ identifier, type });

  if (record?.cooldownUntil && now < record.cooldownUntil) {
    const seconds =
      Math.ceil((record.cooldownUntil.getTime() - now.getTime()) / 1000) || 1;
    throw new ErrorHandler(
      `OTP already sent. Try again in ${seconds}s.`,
      429
    );
  }

  let windowStart = record?.windowStart || now;
  let sendCount = record?.sendCount || 0;

  if (now - windowStart > OTP_WINDOW_MS) {
    windowStart = now;
    sendCount = 0;
  }

  if (sendCount >= MAX_SENDS_PER_WINDOW) {
    throw new ErrorHandler(
      "Maximum OTP requests reached. Please try again after 10 minutes.",
      429
    );
  }

  const otp = generateOtp(length);
  const otpHash = hashOtp(otp);
  const expiresAt = new Date(now.getTime() + OTP_EXPIRY_MS);
  const cooldownUntil = new Date(now.getTime() + OTP_COOLDOWN_MS);

  // console.log('📤 [sendOtp] Generated OTP:', {
  //   identifier,
  //   type,
  //   otp, // This will show the actual OTP for debugging
  //   otpHash,
  //   length: otp.length,
  //   expiresAt
  // });

  const payload = {
    identifier,
    type,
    user: userId || record?.user,
    otpHash,
    expiresAt,
    attempts: 0,
    verified: false,
    lastSentAt: now,
    cooldownUntil,
    windowStart,
    sendCount: sendCount + 1,
  };

  let savedRecord;

  try {
    savedRecord = await OtpVerification.findOneAndUpdate(
      { identifier, type },
      payload,
      {
        upsert: true,
        new: true,
        setDefaultsOnInsert: true,
      }
    );

    if (type === 'email') {
      try {
        await emailTemplateRenderer.sendTemplateEmail(
          'otp_verification',
          identifier,
          {
            otp: otp,
            expiryMinutes: Math.ceil(OTP_EXPIRY_MS / (60 * 1000)),
            appName: 'GoCabs',
            recipientName: 'User'
          }
        );
      } catch (templateError) {
        console.warn('⚠️ Template email failed, using fallback:', templateError.message);
        // Fallback to direct email sending
        const emailConfig = await getConfig();
        await sendEmailWithRetry({
          from: emailConfig.user,
          to: identifier,
          subject: "Your Cab Booking verification OTP",
          text: `Your OTP is ${otp}. It expires in ${Math.ceil(OTP_EXPIRY_MS / (60 * 1000))} minutes.`,
        });
      }
    } else if (type === 'phone') {
      // Use SMS template renderer service
      const smsTemplateRenderer = require('../services/smsTemplateRenderer.service');

      try {
        await smsTemplateRenderer.sendTemplateSms(
          'otp_verification',
          identifier,
          {
            otp: otp,
            expiryMinutes: Math.ceil(OTP_EXPIRY_MS / (60 * 1000)),
            appName: 'GoCabs'
          }
        );
      } catch (templateError) {
        console.warn('⚠️ SMS template failed, using fallback:', templateError.message);
        // Fallback to hardcoded message
        const fallbackMessage = `Your Cab Booking OTP is ${otp}. It expires in ${Math.ceil(OTP_EXPIRY_MS / (60 * 1000))} minutes.`;
        await sendSms(identifier, fallbackMessage);
      }
    }

  } catch (error) {
    console.log(error)
    if (savedRecord?._id) {
      await OtpVerification.deleteOne({ _id: savedRecord._id });
    }
    throw new ErrorHandler(
      `Unable to send OTP to ${type}. Please try again later.`,
      502,
    );
  }

  // Trigger Webhook for OTP Verification
  webhookService.triggerWebhook('otp_verification', { identifier, type });

  const remainingSends = Math.max(
    0,
    MAX_SENDS_PER_WINDOW - (payload.sendCount || 0)
  );

  return {
    success: true,
    message: `OTP sent successfully to ${identifier}`,
    meta: {
      expiresAt,
      cooldownUntil,
      resendAvailableInSeconds: Math.ceil(OTP_COOLDOWN_MS / 1000),
      remainingSends,
    },
  };
}

async function verifyOtp(identifier, otp, type = 'email') {
  if (!identifier || !otp) {
    throw new ErrorHandler("Identifier and OTP are required", 400);
  }

  console.log('🔐 [verifyOtp] Starting verification:', {
    identifier,
    otp,
    otpLength: otp.length,
    otpType: typeof otp,
    type
  });

  const record = await OtpVerification.findOne({ identifier, type });

  if (!record) {
    console.log('❌ [verifyOtp] No OTP record found for:', { identifier, type });
    throw new ErrorHandler("OTP not found or expired. Please resend.", 404);
  }

  const now = new Date();
  if (now > record.expiresAt) {
    console.log('❌ [verifyOtp] OTP expired:', {
      identifier,
      expiresAt: record.expiresAt,
      now
    });
    await record.deleteOne();
    throw new ErrorHandler("OTP expired. Please request a new one.", 410);
  }

  const hashed = hashOtp(otp);

  console.log('🔍 [verifyOtp] Hash comparison:', {
    enteredOtp: otp,
    enteredHash: hashed,
    storedHash: record.otpHash,
    match: hashed === record.otpHash
  });

  if (hashed !== record.otpHash) {
    record.attempts += 1;
    if (record.attempts >= MAX_ATTEMPTS) {
      console.log('❌ [verifyOtp] Max attempts exceeded for:', identifier);
      await record.deleteOne();
      throw new ErrorHandler(
        "Maximum attempts exceeded. OTP reset, please request again.",
        429
      );
    }
    await record.save();
    const attemptsLeft = MAX_ATTEMPTS - record.attempts;
    console.log('❌ [verifyOtp] Invalid OTP, attempts left:', attemptsLeft);
    throw new ErrorHandler(
      `Invalid OTP. ${attemptsLeft} attempt(s) left.`,
      400
    );
  }

  console.log('✅ [verifyOtp] OTP verified successfully for:', identifier);

  const response = {
    success: true,
    message: "OTP verified successfully",
    data: {
      identifier: record.identifier,
      userId: record.user,
    },
  };

  await record.deleteOne();

  return response;
}

async function sendEmail({ email, subject, message }) {
  if (!email || !message) {
    throw new ErrorHandler("Email and message are required", 400);
  }

  const emailConfig = await getConfig();
  const result = await sendMail({
    from: emailConfig.user,
    to: email,
    subject: subject || "Your Cab Booking verification OTP",
    text: message,
  });

  return result;
}

module.exports = {
  sendOtp,
  verifyOtp,
  sendEmail,
  OTP_LIMITS: {
    OTP_EXPIRY_MS,
    OTP_COOLDOWN_MS,
    OTP_WINDOW_MS,
    MAX_SENDS_PER_WINDOW,
    MAX_ATTEMPTS,
  },
};
