import fs from "fs";
import path from "path";
import { storage } from "./storage";
import { waManager, randomBetween, WhatsAppManager } from "./whatsapp";
import { buildReminderMessage } from "./message-utils";

type APISender = (to: string, message: string) => Promise<{ ok: boolean; error?: string }>;

function getGreenAPISender(): APISender | null {
  return (global as any).__sendViaGreenAPI || null;
}

export function setupCleanupJobs(uploadDir: string) {
  setInterval(async () => {
    try {
      await cleanupExpiredAccounts(uploadDir);
    } catch (err) {
      console.error("Account cleanup failed:", err);
    }
  }, 12 * 60 * 60 * 1000);

  setInterval(async () => {
    try {
      await cleanupExpiredMedia();
    } catch (err) {
      console.error("Upload cleanup failed:", err);
    }
  }, 24 * 60 * 60 * 1000);

  setTimeout(async () => {
    try {
      await sendScheduledReminders();
    } catch (err) {
      console.error("Reminder job (initial) failed:", err);
    }
  }, 3000);

  setInterval(async () => {
    try {
      await sendScheduledReminders();
    } catch (err) {
      console.error("Reminder job failed:", err);
    }
  }, 5 * 60 * 1000);

  waManager.onSystemConnected = () => {
    flushPendingWelcomes().catch(err => console.error("flushPendingWelcomes failed:", err));
  };

  // Subscription reminder job – runs every hour
  setInterval(async () => {
    try {
      await subscriptionReminderJob();
    } catch (err) {
      console.error("Subscription reminder job failed:", err);
    }
  }, 60 * 60 * 1000);

  // MyFatoorah recurring billing job – runs every hour
  setInterval(async () => {
    try {
      await myfatoorahBillingJob();
    } catch (err) {
      console.error("MyFatoorah billing job failed:", err);
    }
  }, 60 * 60 * 1000);

  // Monthly balance reset job – runs every hour, resets halls every 30 days
  setInterval(async () => {
    try {
      await monthlyBalanceResetJob();
    } catch (err) {
      console.error("Monthly balance reset job failed:", err);
    }
  }, 60 * 60 * 1000);

  // Warmup auto-resume: no-op since DB-driven queue handles warmup automatically
  setInterval(async () => {
    try {
      const warmupResumeFn = (global as any).__triggerWarmupAutoResume;
      if (warmupResumeFn) await warmupResumeFn();
    } catch (err) {
      console.error("Warmup auto-resume job failed:", err);
    }
  }, 60 * 60 * 1000);

  // Send queue scheduler: process one due entry per user every 15 seconds
  setTimeout(async () => {
    try {
      const processSendQueue = (global as any).__processSendQueue;
      if (processSendQueue) await processSendQueue();
    } catch (err) {
      console.error("[Queue] Scheduler initial run failed:", err);
    }
  }, 5000);
  setInterval(async () => {
    try {
      const processSendQueue = (global as any).__processSendQueue;
      if (processSendQueue) await processSendQueue();
    } catch (err) {
      console.error("[Queue] Scheduler tick failed:", err);
    }
  }, 15000);

  // Run both once shortly after startup
  setTimeout(async () => {
    try {
      await subscriptionReminderJob();
    } catch (err) {
      console.error("Subscription reminder job (initial) failed:", err);
    }
    try {
      await myfatoorahBillingJob();
    } catch (err) {
      console.error("MyFatoorah billing job (initial) failed:", err);
    }
  }, 10000);

  console.log("Cleanup jobs scheduled (account expiry every 12h, uploads daily, reminders every 5min, billing hourly)");
}

function parseEventDate(dateStr: string): Date | null {
  const direct = new Date(dateStr);
  if (!isNaN(direct.getTime())) return direct;
  
  const match = dateStr.match(/(\d{4})[\/\-](\d{1,2})[\/\-](\d{1,2})/);
  if (match) return new Date(parseInt(match[1]), parseInt(match[2]) - 1, parseInt(match[3]));
  
  const match2 = dateStr.match(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{4})/);
  if (match2) return new Date(parseInt(match2[3]), parseInt(match2[2]) - 1, parseInt(match2[1]));
  
  return null;
}

function getSaudiDateParts(): { date: string; hour: number; min: number } {
  const now = new Date();
  const formatter = new Intl.DateTimeFormat('en-CA', { timeZone: 'Asia/Riyadh', year: 'numeric', month: '2-digit', day: '2-digit' });
  const dateStr = formatter.format(now);
  const timeParts = new Intl.DateTimeFormat('en-GB', { timeZone: 'Asia/Riyadh', hour: '2-digit', minute: '2-digit', hour12: false }).format(now).split(':');
  return { date: dateStr, hour: parseInt(timeParts[0]), min: parseInt(timeParts[1]) };
}

const reminderRunning = new Set<number>();

export async function sendScheduledReminders() {
  const allUsers = await storage.getAllUsers();
  const saudi = getSaudiDateParts();

  for (const user of allUsers) {
    if (user.isAdmin) continue;
    if (user.role === "scanner") continue;

    // Acquire lock immediately — no await between has() and add() so this is atomic
    // in Node.js's single-threaded event loop. Any concurrent invocation will see
    // the lock already held once the first iteration yields at the next await.
    if (reminderRunning.has(user.id)) continue;
    reminderRunning.add(user.id);

    try {
      const enabled = await storage.getSetting(user.id, "auto_reminder_enabled");
      if (enabled === "false") { reminderRunning.delete(user.id); continue; }

      const alreadySent = await storage.getSetting(user.id, "reminder_sent");
      if (alreadySent === "true") { reminderRunning.delete(user.id); continue; }

      const reminderDate = await storage.getSetting(user.id, "reminder_date");
      if (!reminderDate || saudi.date !== reminderDate) { reminderRunning.delete(user.id); continue; }

      const reminderTime = await storage.getSetting(user.id, "reminder_time") || "09:00";
      const [rHour, rMin] = reminderTime.split(":").map(Number);

      // Alert user if reminder is within 3 hours and personal WhatsApp is disconnected
      {
        const currentMinutes = saudi.hour * 60 + saudi.min;
        const reminderMinutes = rHour * 60 + rMin;
        const minutesUntil = reminderMinutes - currentMinutes;
        if (minutesUntil > 0 && minutesUntil <= 180) {
          const useSystem = await storage.getSetting(user.id, "bulk_send_use_system");
          if (useSystem === "false") {
            const connectedSession = waManager.getConnectedSessionForUser(user.id);
            if (!connectedSession) {
              const alertAlreadySent = await storage.getSetting(user.id, "reminder_disconnect_alert_sent");
              const disconnectAlertEnabled = await storage.getSystemMessageEnabled("disconnect_alert");
              if (alertAlreadySent !== "true" && user.phoneNumber && disconnectAlertEnabled) {
                const hoursLabel = minutesUntil <= 60 ? "ساعة" : `${Math.ceil(minutesUntil / 60)} ساعات`;
                const alertMsg = `⚠️ تنبيه عاجل من إنفايتنا\n\nتذكير مناسبتك مجدول خلال ${hoursLabel}!\n\nرقمك الشخصي المستخدم للإرسال غير متصل الآن.\nيرجى الدخول للتطبيق وربط رقمك فوراً لضمان إرسال التذكير لضيوفك.`;
                try {
                  const alertGreenAPI = getGreenAPISender();
                  let alerted = false;
                  if (alertGreenAPI) {
                    const r = await alertGreenAPI(user.phoneNumber, alertMsg);
                    alerted = r.ok;
                  }
                  if (!alerted) await waManager.sendViaSystem(user.phoneNumber, alertMsg);
                  await storage.setSetting(user.id, "reminder_disconnect_alert_sent", "true");
                  console.log(`Reminder disconnect alert sent to user ${user.id}`);
                } catch (err) {
                  console.error(`Reminder disconnect alert failed for user ${user.id}:`, err);
                }
              }
            }
          }
        }
      }

      if (saudi.hour < rHour || (saudi.hour === rHour && saudi.min < rMin)) { reminderRunning.delete(user.id); continue; }

      const ownGuests = await storage.getGuests(user.id);
      // Also include guests from linked secondary users (who share this event and quota)
      let allEligibleGuests = ownGuests.filter(g => g.status !== "declined");
      const linkedUsers = await storage.getLinkedUsers(user.id);
      for (const linked of linkedUsers) {
        const linkedGuests = await storage.getGuests(linked.id);
        allEligibleGuests = allEligibleGuests.concat(linkedGuests.filter(g => g.status !== "declined"));
      }
      const eligibleGuests = allEligibleGuests;
      if (eligibleGuests.length === 0) { reminderRunning.delete(user.id); continue; }

      // Lock is held; spawn async — finally is the single owner of lock release
      (async () => {
        try {
          await sendRemindersForUser(user, eligibleGuests);
        } finally {
          reminderRunning.delete(user.id);
        }
      })().catch(err => {
        console.error(`Reminder job uncaught error for user ${user.id}:`, err);
      });
    } catch (err) {
      reminderRunning.delete(user.id);
      console.error(`Reminder job error for user ${user.id}:`, err);
    }
  }
}

async function sendRemindersForUser(
  user: Awaited<ReturnType<typeof storage.getAllUsers>>[number],
  eligibleGuests: Awaited<ReturnType<typeof storage.getGuests>>
) {
  await storage.setSetting(user.id, "reminder_sent", "true");

  const templateId = await storage.getSetting(user.id, "reminder_template_id") || "formal_reminder";
  const guestCount = eligibleGuests.length;

  // Read admin-configured delay settings
  let spreadDelayMs = 60000; // fallback 1 min
  let delayLabel = "60s";
  try {
    const admin = await storage.getAdminUser();
    if (admin) {
      const useSystem = (await storage.getSetting(user.id, "bulk_send_use_system")) !== "false";
      const hasPersonalSession = !!waManager.getConnectedSessionForUser(user.id);
      const greenAPI = getGreenAPISender();
      const usePersonalAccount = !useSystem && hasPersonalSession && !greenAPI;
      if (usePersonalAccount) {
        const userReminderHours = parseInt(await storage.getSetting(admin.id, "admin_delay_user_reminder_hours") || "12");
        spreadDelayMs = guestCount > 0 ? Math.max(60000, Math.floor((userReminderHours * 60 * 60 * 1000) / guestCount)) : 60000;
        delayLabel = `توزيع ${userReminderHours}h → ${Math.round(spreadDelayMs / 1000)}s/رسالة`;
      } else {
        const systemReminderSec = parseInt(await storage.getSetting(admin.id, "admin_delay_system_reminder") || "45");
        spreadDelayMs = Math.max(5000, systemReminderSec * 1000);
        delayLabel = `${systemReminderSec}s (نظام)`;
      }
    }
  } catch (e) {
    console.error("Reminder: failed to read admin delay settings, using defaults:", e);
  }

  console.log(`Reminder job: sending ${guestCount} reminders for user ${user.id} (${user.name}), delay: ${delayLabel}`);

  let count = 0;
  let lastSentAt = 0;
  const greenAPI = getGreenAPISender();
  for (const guest of eligibleGuests) {
    try {
      // Enforce spread delay
      if (lastSentAt > 0) {
        const elapsed = Date.now() - lastSentAt;
        if (elapsed < spreadDelayMs) await new Promise(r => setTimeout(r, spreadDelayMs - elapsed));
      }
      lastSentAt = Date.now();
      const message = await buildReminderMessage(templateId, user.id, guest.name);
      let sent = false;
      if (greenAPI) {
        const result = await greenAPI(guest.phoneNumber, message);
        sent = result.ok;
        if (!sent) console.log(`Green API failed for guest ${guest.id}: ${result.error}, falling back to Baileys`);
      }
      if (!sent) {
        const userSession = waManager.getConnectedSessionForUser(user.id);
        if (userSession) {
          await userSession.sendMessage(guest.phoneNumber, message);
        } else {
          await waManager.sendViaSystem(guest.phoneNumber, message);
        }
      }
      count++;
    } catch (err) {
      console.error(`Reminder send error for guest ${guest.id}:`, err);
    }
  }

  if (count === 0) {
    await storage.setSetting(user.id, "reminder_sent", "false");
    console.log(`Reminder job: no messages sent for user ${user.id} (no WhatsApp session), will retry`);
  } else {
    await storage.setSetting(user.id, "reminder_sent_count", String(count));
    console.log(`Reminder job done: sent ${count}/${eligibleGuests.length} for user ${user.id}`);
  }
}

export async function flushPendingWelcomes() {
  const systemSession = waManager.getSystemSession();
  const greenAPI = getGreenAPISender();
  if (!systemSession && !greenAPI) return;

  const allUsers = await storage.getAllUsers();
  const pendingUsers: typeof allUsers = [];

  for (const user of allUsers) {
    if (user.isAdmin || user.role === 'scanner') continue;
    const pending = await storage.getSetting(user.id, 'welcome_pending');
    if (pending === 'true') pendingUsers.push(user);
  }

  if (pendingUsers.length === 0) return;

  console.log(`flushPendingWelcomes: sending ${pendingUsers.length} queued welcome messages`);
  let count = 0;

  for (const user of pendingUsers) {
    const msg = await storage.getSetting(user.id, 'welcome_pending_msg');
    if (!msg) {
      await storage.setSetting(user.id, 'welcome_pending', 'false');
      continue;
    }
    try {
      let sent = false;
      if (greenAPI) {
        const result = await greenAPI(user.phoneNumber, msg);
        sent = result.ok;
      }
      if (!sent && systemSession) {
        await systemSession.sendMessage(user.phoneNumber, msg);
        sent = true;
      }
      if (sent) {
        await storage.setSetting(user.id, 'welcome_pending', 'false');
        await storage.deleteSetting(user.id, 'welcome_pending_msg');
        count++;
      }
    } catch (err) {
      console.error(`flushPendingWelcomes: failed to send to user ${user.id}:`, err);
    }
    await new Promise(r => setTimeout(r, 5000));
  }

  if (count > 0) {
    const adminUser = allUsers.find(u => u.isAdmin);
    if (adminUser?.phoneNumber) {
      const pendingFlushEnabled = await storage.getSystemMessageEnabled("pending_welcomes_flushed");
      if (pendingFlushEnabled) {
        const notifyMsg = `✅ رجع اتصال رقم المشروع.\n\nتم إرسال ${count} رسالة ترحيب كانت معلّقة.`;
        try {
          let notified = false;
          if (greenAPI) {
            const r = await greenAPI(adminUser.phoneNumber, notifyMsg);
            notified = r.ok;
          }
          if (!notified && systemSession) {
            await systemSession.sendMessage(adminUser.phoneNumber, notifyMsg);
          }
        } catch {}
      }
    }
    console.log(`flushPendingWelcomes: sent ${count} messages and notified admin`);
  }
}

async function cleanupExpiredAccounts(uploadDir: string) {
  const allUsers = await storage.getAllUsers();
  const now = new Date();
  
  for (const user of allUsers) {
    if (user.isAdmin) continue;
    if (user.role === 'scanner') continue;
    
    const eventDetails = await storage.getEventDetails(user.id);
    const eventDateStr = eventDetails?.event_date;
    if (!eventDateStr) continue;
    
    const eventDate = parseEventDate(eventDateStr);
    if (!eventDate) continue;
    
    const expiryDate = new Date(eventDate);
    const isHallClient = !!user.parentHallId;
    expiryDate.setDate(expiryDate.getDate() + (isHallClient ? 30 : 2));
    
    if (now < expiryDate) continue;
    
    const scanners = await storage.getAllUsers();
    for (const scanner of scanners) {
      if (scanner.role !== 'scanner') continue;
      const linked = await storage.getSetting(scanner.id, "linked_user_id");
      if (linked === String(user.id)) {
        await storage.deleteAllUserData(scanner.id);
        console.log(`Cleanup: deleted scanner account ${scanner.id} linked to user ${user.id}`);
      }
    }
    
    const authDir = path.join("auth_info_baileys");
    if (fs.existsSync(authDir)) {
      const accounts = await storage.getWhatsappAccounts(user.id);
      for (const acc of accounts) {
        const accDir = path.join(authDir, `account_${acc.id}`);
        if (fs.existsSync(accDir)) {
          fs.rmSync(accDir, { recursive: true, force: true });
        }
      }
    }
    
    await storage.deleteAllUserData(user.id);
    console.log(`Cleanup: deleted expired user account ${user.id} (${user.name}) - event date was ${eventDateStr}`);
  }
}

async function cleanupExpiredMedia() {
  const now = new Date();
  try {
    const allUsers = await storage.getAllUsers();
    let cleaned = 0;
    for (const user of allUsers) {
      if (user.isAdmin) continue;
      const mediaUrl = await storage.getSetting(user.id, "global_media_url");
      if (!mediaUrl) continue;
      // Enforce deletion strictly 2 days after event_date
      const eventDateStr = await storage.getSetting(user.id, "event_date");
      if (!eventDateStr) continue;
      const eventDate = parseEventDate(eventDateStr);
      if (!eventDate) continue;
      const expiryDate = new Date(eventDate);
      expiryDate.setDate(expiryDate.getDate() + 2);
      if (now < expiryDate) continue;
      await storage.deleteMediaFile(user.id);
      await storage.cleanupOldMedia(user.id);
      await storage.deleteSetting(user.id, "global_media_uploaded_at");
      cleaned++;
    }
    if (cleaned > 0) console.log(`Cleanup: removed media for ${cleaned} user(s) (event + 2d expired)`);
  } catch (e) {
    console.error("cleanupExpiredMedia error:", e);
  }
}

function cleanupOldUploads(uploadDir: string, maxAgeDays: number) {
  if (!fs.existsSync(uploadDir)) return;
  const now = Date.now();
  const maxAge = maxAgeDays * 24 * 60 * 60 * 1000;
  const files = fs.readdirSync(uploadDir);
  let deleted = 0;
  for (const file of files) {
    const filePath = path.join(uploadDir, file);
    try {
      const stat = fs.statSync(filePath);
      if (now - stat.mtimeMs > maxAge) {
        fs.unlinkSync(filePath);
        deleted++;
      }
    } catch (e) {}
  }
  if (deleted > 0) console.log(`Cleanup: removed ${deleted} old upload file(s)`);
}

function deleteUploadFile(fileUrl: string, uploadDir: string) {
  if (fileUrl?.startsWith('/uploads/')) {
    const filePath = path.join(uploadDir, fileUrl.replace('/uploads/', ''));
    try {
      if (fs.existsSync(filePath)) fs.unlinkSync(filePath);
    } catch (e) {}
  }
}

// ==============================
// SUBSCRIPTION REMINDER JOB
// ==============================
async function subscriptionReminderJob() {
  const halls = await storage.getAllHalls();
  const now = new Date();
  const DAY = 24 * 60 * 60 * 1000;
  const THROTTLE_MS = 25 * 60 * 60 * 1000; // 25 hours between sends per stage

  // Helper: send a WhatsApp message via Green API then system session fallback
  async function sendWAMsg(phone: string, msg: string): Promise<void> {
    const greenAPI = getGreenAPISender();
    let sent = false;
    if (greenAPI) {
      const r = await greenAPI(phone, msg);
      sent = r.ok;
    }
    if (!sent) {
      const sysSession = waManager.getSystemSession();
      if (sysSession) await sysSession.sendMessage(phone, msg);
    }
  }

  // Fetch admin phone once for all halls
  let adminPhone: string | undefined;
  try {
    const allUsers = await storage.getAllUsers();
    adminPhone = allUsers.find(u => u.isAdmin)?.phoneNumber;
    if (!adminPhone) console.warn("Subscription reminders: no admin user with a phone number found — admin notifications will be skipped");
  } catch (err) {
    console.error("Subscription reminders: failed to fetch admin user:", err);
  }

  for (const hall of halls) {
    if (!hall.subscriptionExpiresAt) continue;
    // Skip halls that are actively auto-renewing via MyFatoorah — the billing job handles them
    if (hall.billingStatus === "active") continue;

    const expiry = new Date(hall.subscriptionExpiresAt);
    const msToExpiry = expiry.getTime() - now.getTime();
    const daysToExpiry = msToExpiry / DAY;
    const daysOverdue = -daysToExpiry; // positive if past expiry

    // "paid" halls: skip reminders until the new subscription period approaches expiry.
    // When ≤7 days remain, the hall needs to pay again — reset status so reminders fire normally.
    if (hall.billingStatus === "paid") {
      if (daysToExpiry > 7) continue; // still plenty of time — no reminder needed
      // Approaching expiry of the newly-extended period: clear "paid" flag so reminders work
      await storage.updateUser(hall.id, { billingStatus: null } as any);
      hall.billingStatus = null;
    }

    // Helper: attempt admin send and update throttle timestamp only when send was attempted
    async function tryAdminStage(
      msgKey: string,
      windowCondition: boolean,
      alreadyThrottled: boolean,
      throttleField: Parameters<typeof storage.updateUser>[1],
      buildMsg: () => string,
      logLabel: string,
    ): Promise<void> {
      if (!windowCondition || alreadyThrottled) return;
      const enabled = await storage.getSystemMessageEnabled(msgKey);
      if (!enabled || !adminPhone) return; // skip if disabled or no admin phone — will retry
      const msg = buildMsg();
      try {
        await sendWAMsg(adminPhone!, msg);
      } catch (e) {
        console.error(`Subscription: failed to send ${logLabel} for hall ${hall.id}:`, e);
      }
      // Mark throttle timestamp only after a send was attempted (success or soft-fail)
      await storage.updateUser(hall.id, throttleField);
      console.log(`Subscription: ${logLabel} admin warning for hall ${hall.id} (${hall.name})`);
    }

    // Helper: attempt hall send and throttle via settings key
    async function tryHallStage(
      settingKey: string,
      windowCondition: boolean,
      buildMsg: () => string,
      logLabel: string,
    ): Promise<void> {
      if (!windowCondition) return;
      const lastStr = await storage.getSetting(hall.id, settingKey);
      const last = lastStr ? new Date(lastStr) : null;
      if (last && (now.getTime() - last.getTime()) < THROTTLE_MS) return;
      const enabled = await storage.getSystemMessageEnabled("subscription_reminder_hall");
      if (!enabled) return;
      const msg = buildMsg();
      try {
        await sendWAMsg(hall.phoneNumber, msg);
      } catch (e) {
        console.error(`Subscription: failed to send hall ${logLabel} for hall ${hall.id}:`, e);
      }
      await storage.setSetting(hall.id, settingKey, now.toISOString());
      console.log(`Subscription: sent hall ${logLabel} reminder for hall ${hall.id} (${hall.name})`);
    }

    // ── Admin reminders ───────────────────────────────────────────────────────

    // 1. Admin 3 days before expiry
    const REMIND_BEFORE_DAYS = 3;
    const alreadyWarnedAdmin = hall.paymentWarningAt &&
      (now.getTime() - new Date(hall.paymentWarningAt).getTime()) < THROTTLE_MS;
    await tryAdminStage(
      "subscription_reminder_admin",
      daysToExpiry <= REMIND_BEFORE_DAYS && daysToExpiry >= -1,
      !!alreadyWarnedAdmin,
      { paymentWarningAt: now },
      () => {
        const daysStr = daysToExpiry < 0 ? "انتهى اليوم" : daysToExpiry < 1 ? "ينتهي اليوم" : `ينتهي خلال ${Math.ceil(daysToExpiry)} يوم`;
        return `⚠️ *تذكير تجديد اشتراك قاعة*\n\nالقاعة: *${hall.name}*\nالاشتراك ${daysStr}\n\nيرجى تأكيد الدفع من لوحة الأدمن.`;
      },
      "3-day",
    );

    // 2. Admin 7 days before expiry
    const alreadyWarned7Days = hall.adminWarning7DaysAt &&
      (now.getTime() - new Date(hall.adminWarning7DaysAt).getTime()) < THROTTLE_MS;
    await tryAdminStage(
      "subscription_reminder_admin_7days",
      daysToExpiry <= 7 && daysToExpiry > 3,
      !!alreadyWarned7Days,
      { adminWarning7DaysAt: now },
      () => `🔔 *تذكير مبكر — اشتراك قاعة*\n\nالقاعة: *${hall.name}*\nالاشتراك ينتهي خلال ${Math.ceil(daysToExpiry)} يوم\n\nيرجى التحضير لتجديد الاشتراك.`,
      "7-day",
    );

    // 3. Admin on expiry day (day 0)
    const alreadyWarnedDay0 = hall.adminWarningDayZeroAt &&
      (now.getTime() - new Date(hall.adminWarningDayZeroAt).getTime()) < THROTTLE_MS;
    await tryAdminStage(
      "subscription_reminder_admin_day0",
      daysToExpiry <= 0 && daysOverdue < 1,
      !!alreadyWarnedDay0,
      { adminWarningDayZeroAt: now },
      () => `⚠️ *اشتراك قاعة ينتهي اليوم*\n\nالقاعة: *${hall.name}*\nاشتراكها ينتهي اليوم!\n\nيرجى تأكيد الدفع من لوحة الأدمن.`,
      "day-0",
    );

    // 4. Admin 1 day after expiry
    const alreadyWarned1Day = hall.adminWarning1DayAt &&
      (now.getTime() - new Date(hall.adminWarning1DayAt).getTime()) < THROTTLE_MS;
    await tryAdminStage(
      "subscription_reminder_admin_1day_after",
      daysOverdue >= 1 && daysOverdue < 2,
      !!alreadyWarned1Day,
      { adminWarning1DayAt: now },
      () => `🚨 *قاعة لم تجدد اشتراكها*\n\nالقاعة: *${hall.name}*\nمضى يوم على انتهاء اشتراكها ولم تجدد.\n\nيرجى المتابعة.`,
      "1-day-after",
    );

    // 5. Admin 3 days after expiry
    const alreadyWarned3DaysAfter = hall.adminWarning3DaysAfterAt &&
      (now.getTime() - new Date(hall.adminWarning3DaysAfterAt).getTime()) < THROTTLE_MS;
    await tryAdminStage(
      "subscription_reminder_admin_3days_after",
      daysOverdue >= 3 && daysOverdue < 4,
      !!alreadyWarned3DaysAfter,
      { adminWarning3DaysAfterAt: now },
      () => `🚨 *قاعة لا تزال بدون تجديد*\n\nالقاعة: *${hall.name}*\nمضت 3 أيام على انتهاء اشتراكها ولم تجدد.\n\nيرجى اتخاذ إجراء.`,
      "3-days-after",
    );

    // ── Hall reminders ────────────────────────────────────────────────────────

    // Hall -7 days
    await tryHallStage(
      "sub_hall_reminder_7d",
      daysToExpiry <= 7 && daysToExpiry > 3,
      () => `🔔 *تذكير — اشتراكك ينتهي قريباً*\n\nنظام إنفايتنا الخاص بك سينتهي خلال ${Math.ceil(daysToExpiry)} يوم.\n\nيرجى التواصل مع المشرف لتجديد الاشتراك في الوقت المناسب.`,
      "hall-7d",
    );

    // Hall -3 days (covers 3 days and 2 days before — window: >1 day remaining)
    await tryHallStage(
      "sub_hall_reminder_3d",
      daysToExpiry <= 3 && daysToExpiry > 1,
      () => `⚠️ *تذكير عاجل — اشتراكك ينتهي قريباً*\n\nنظام إنفايتنا الخاص بك سينتهي خلال ${Math.ceil(daysToExpiry)} أيام.\n\nيرجى التواصل مع المشرف في أقرب وقت لضمان استمرار الخدمة.`,
      "hall-3d",
    );

    // Hall -1 day (day before expiry — window: 0 to 1 day remaining)
    await tryHallStage(
      "sub_hall_reminder_1d",
      daysToExpiry <= 1 && daysToExpiry > 0,
      () => `🔴 *تنبيه — اشتراكك ينتهي غداً*\n\nنظام إنفايتنا الخاص بك سينتهي اشتراكه *غداً*!\n\nتواصل مع المشرف فوراً قبل انقطاع الخدمة.`,
      "hall-1d",
    );

    // Hall day 0 (expiry day)
    await tryHallStage(
      "sub_hall_reminder_0d",
      daysToExpiry <= 0 && daysOverdue < 1,
      () => `🚨 *اشتراكك ينتهي اليوم*\n\nنظام إنفايتنا الخاص بك ينتهي اشتراكه *اليوم*!\n\nتواصل مع المشرف فوراً لتجنب انقطاع الخدمة.`,
      "hall-0d",
    );

    // Hall +1 day after expiry
    const suspendDeadline1 = new Date(expiry.getTime() + 15 * DAY).toLocaleDateString("ar-SA-u-ca-gregory");
    await tryHallStage(
      "sub_hall_reminder_1da",
      daysOverdue >= 1 && daysOverdue < 2,
      () => `⛔ *انتهى اشتراكك*\n\nانتهى اشتراكك في إنفايتنا. لديك مهلة حتى ${suspendDeadline1} قبل تجميد الحساب تلقائياً.\n\nيرجى التواصل مع المشرف لتجديد الاشتراك.`,
      "hall-1da",
    );

    // Hall +3 days after expiry
    await tryHallStage(
      "sub_hall_reminder_3da",
      daysOverdue >= 3 && daysOverdue < 4,
      () => `⛔ *تذكير — اشتراك منتهٍ*\n\nمضت 3 أيام على انتهاء اشتراكك في إنفايتنا. تبقت ${Math.ceil(15 - daysOverdue)} يوماً قبل تجميد الحساب تلقائياً.\n\nتواصل مع المشرف للتجديد.`,
      "hall-3da",
    );

    // ── Auto-suspend at day +15 ───────────────────────────────────────────────
    if (daysOverdue >= 15 && !hall.isSuspended) {
      await storage.suspendUser(hall.id, true);
      await storage.cascadeSuspendHallClients(hall.id, true);
      console.log(`Subscription: auto-suspended hall ${hall.id} (${hall.name}) after 15 days overdue`);
      const suspendMsg = `⛔ *تجميد الحساب*\n\nتم تجميد حسابك في إنفايتنا بسبب عدم تجديد الاشتراك خلال 15 يوماً من تاريخ الانتهاء.\n\nللاستئناف تواصل مع المشرف.`;
      try { await sendWAMsg(hall.phoneNumber, suspendMsg); } catch (_) { /* best-effort */ }
      if (adminPhone) {
        const adminSuspendMsg = `⛔ *تجميد قاعة تلقائي*\n\nتم تجميد قاعة *${hall.name}* تلقائياً بعد مرور 15 يوماً على انتهاء اشتراكها دون تجديد.`;
        try { await sendWAMsg(adminPhone, adminSuspendMsg); } catch (_) { /* best-effort */ }
      }
    }
  }
}

// ==============================
// MONTHLY BALANCE RESET JOB
// ==============================
// Runs hourly. Resets messagesSent for halls (and their clients) if 30+ days
// have passed since their last balance reset (balanceResetAt).
export async function monthlyBalanceResetJob() {
  const halls = await storage.getAllHalls();
  const now = new Date();
  const THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1000;

  for (const hall of halls) {
    if (!hall.subscriptionExpiresAt) continue;
    const expiry = new Date(hall.subscriptionExpiresAt);
    if (expiry < now) continue; // skip expired subscriptions

    const lastReset = (hall as any).balanceResetAt ? new Date((hall as any).balanceResetAt) : null;
    if (!lastReset) continue; // skip halls with no balanceResetAt set yet

    const msSinceReset = now.getTime() - lastReset.getTime();
    if (msSinceReset < THIRTY_DAYS_MS) continue; // not 30 days yet

    try {
      await storage.resetHallBalance(hall.id);
      console.log(`Monthly balance reset: hall ${hall.id} (${hall.name}) — ${Math.floor(msSinceReset / 86400000)} days since last reset`);
    } catch (e) {
      console.error(`Monthly balance reset failed for hall ${hall.id}:`, e);
    }
  }
}

// ==============================
// MYFATOORAH RECURRING BILLING JOB
// ==============================
// Runs hourly. For halls with billingStatus='active':
//   1. If subscription is expiring (<=3 days): attempt auto-renewal via DirectPayment
//      using stored recurring token (myfatoorahToken). If no token, send reminder.
//   2. If subscription expired and auto-renewal fails: mark expired + suspend, notify admin.
interface DirectPaymentResult {
  IsSuccess: boolean;
  Message: string;
  Data: { InvoiceStatus: string; InvoiceId: number } | null;
}

export async function myfatoorahBillingJob() {
  const halls = await storage.getAllHalls();
  const now = new Date();
  const DAY = 24 * 60 * 60 * 1000;

  // Get admin MyFatoorah config
  const allUsers = await storage.getAllUsers();
  const admin = allUsers.find(u => u.isAdmin);
  if (!admin) return;
  const mfToken = await storage.getSetting(admin.id, "myfatoorah_token");
  const mfAmount = await storage.getSetting(admin.id, "myfatoorah_amount");
  const mfTestMode = (await storage.getSetting(admin.id, "myfatoorah_test_mode")) === "true";
  const mfCountry = await storage.getSetting(admin.id, "myfatoorah_country") || "SAU";
  const mfGatewayId = parseInt(await storage.getSetting(admin.id, "myfatoorah_gateway_id") || "0");
  if (!mfToken || !mfAmount) return; // MyFatoorah not configured
  const mfActive = await storage.getSetting(admin.id, "myfatoorah_active");
  if (mfActive === "false") return; // MyFatoorah is configured but disabled by admin

  const baseUrl = mfTestMode ? "https://apitest.myfatoorah.com" : "https://api.myfatoorah.com";
  const CURRENCY_MAP: Record<string, string> = {
    SAU: "SAR", KWT: "KWD", UAE: "AED", BHR: "BHD",
    OMN: "OMR", QAT: "QAR", EGY: "EGP", JOR: "JOD",
  };
  const currency = CURRENCY_MAP[mfCountry] || "SAR";
  const amount = parseFloat(mfAmount);

  async function mfPost<T>(path: string, body: unknown): Promise<T> {
    const r = await fetch(`${baseUrl}${path}`, {
      method: "POST",
      headers: { "Content-Type": "application/json", "Authorization": `Bearer ${mfToken}` },
      body: JSON.stringify(body),
    });
    return r.json() as Promise<T>;
  }

  async function sendHallMsg(phone: string, msg: string): Promise<void> {
    const greenAPI = getGreenAPISender();
    let sent = false;
    if (greenAPI) {
      const r = await greenAPI(phone, msg);
      sent = r.ok;
    }
    if (!sent) {
      const sysSession = waManager.getSystemSession();
      if (sysSession) await sysSession.sendMessage(phone, msg);
    }
  }

  // ──────────────────────────────────────────────────────────
  // Phase 0: Expire cancelled halls whose grace period has ended
  // ──────────────────────────────────────────────────────────
  for (const hall of halls) {
    if (hall.billingStatus !== "cancelled") continue;
    // Grace expiry = max(subscriptionExpiresAt, nextBillingAt)
    const a = hall.subscriptionExpiresAt ? new Date(hall.subscriptionExpiresAt) : null;
    const b = hall.nextBillingAt ? new Date(hall.nextBillingAt) : null;
    const graceExpiry = a && b ? (a > b ? a : b) : (a || b);
    if (!graceExpiry || graceExpiry > now) continue; // still in grace period

    // Grace period over — suspend and mark expired
    await storage.updateUser(hall.id, { billingStatus: "expired", isSuspended: true });
    await storage.cascadeSuspendHallClients(hall.id, true);
    console.log(`MyFatoorah billing: grace period ended for cancelled hall ${hall.id} (${hall.name}) — suspended`);
    const msg = `⛔ *انتهت فترة السماح*\n\nانتهت فترة السماح لاشتراكك الملغى في إنفايتنا. تم إيقاف الحساب مؤقتاً. للاستئناف تواصل مع الإدارة.`;
    try { await sendHallMsg(hall.phoneNumber, msg); } catch (_) { /* best-effort */ }
    if (admin.phoneNumber) {
      const adminMsg = `⛔ *انتهاء فترة سماح قاعة*\n\nانتهت فترة سماح القاعة الملغاة *${hall.name}* وتم إيقافها.`;
      try { await sendHallMsg(admin.phoneNumber, adminMsg); } catch (_) { /* best-effort */ }
    }
  }

  for (const hall of halls) {
    // Only process halls that are actively subscribed via MyFatoorah
    if (hall.billingStatus !== "active") continue;
    if (!hall.nextBillingAt) continue;

    const billingDate = new Date(hall.nextBillingAt);
    const msUntilBilling = billingDate.getTime() - now.getTime();
    const daysUntilBilling = msUntilBilling / DAY;

    // ──────────────────────────────────────────────────────────
    // A) nextBillingAt has passed — attempt automatic renewal
    // ──────────────────────────────────────────────────────────
    if (daysUntilBilling <= 0) {
      // Grace period reminders (checked every hourly run, throttled via settings)
      if (!hall.isSuspended) {
        const paymentFailedAtStr = await storage.getSetting(hall.id, "mf_payment_failed_at");
        if (paymentFailedAtStr) {
          const daysSinceFailure = (now.getTime() - new Date(paymentFailedAtStr).getTime()) / DAY;
          const GRACE_THROTTLE = 25 * 60 * 60 * 1000;
          if (daysSinceFailure >= 1 && daysSinceFailure < 2) {
            const key = "mf_grace_reminder_1da";
            const last = await storage.getSetting(hall.id, key);
            if (!last || (now.getTime() - new Date(last).getTime()) > GRACE_THROTTLE) {
              const msg = `⚠️ *تذكير بالسداد*\n\nمضى يوم على فشل خصم اشتراكك في إنفايتنا. تبقت 14 يوماً قبل تجميد الحساب تلقائياً.\n\nيرجى تحديث بيانات الدفع أو التواصل مع الإدارة.`;
              try { await sendHallMsg(hall.phoneNumber, msg); } catch (_) { /* best-effort */ }
              await storage.setSetting(hall.id, key, now.toISOString());
            }
          }
          if (daysSinceFailure >= 3 && daysSinceFailure < 4) {
            const key = "mf_grace_reminder_3da";
            const last = await storage.getSetting(hall.id, key);
            if (!last || (now.getTime() - new Date(last).getTime()) > GRACE_THROTTLE) {
              const msg = `⚠️ *تذكير بالسداد*\n\nمضت 3 أيام على فشل خصم اشتراكك في إنفايتنا. تبقت ${Math.ceil(15 - daysSinceFailure)} يوماً قبل تجميد الحساب.\n\nيرجى التواصل مع الإدارة لتجديد اشتراكك.`;
              try { await sendHallMsg(hall.phoneNumber, msg); } catch (_) { /* best-effort */ }
              await storage.setSetting(hall.id, key, now.toISOString());
            }
          }
        }
      }

      const autoRenewKey = "myfatoorah_auto_renew_attempted_at";
      const lastAttemptStr = await storage.getSetting(hall.id, autoRenewKey);
      const lastAttempt = lastAttemptStr ? new Date(lastAttemptStr) : null;
      const alreadyAttempted = lastAttempt && (now.getTime() - lastAttempt.getTime()) < 23 * 60 * 60 * 1000;
      if (alreadyAttempted) continue;

      await storage.setSetting(hall.id, autoRenewKey, now.toISOString());

      const recurringToken = hall.myfatoorahToken;
      if (recurringToken) {
        // Attempt DirectPayment with stored recurring token
        let chargeSucceeded = false;
        try {
          const result = await mfPost<DirectPaymentResult>("/v2/DirectPayment", {
            InitiatePaymentId: recurringToken,
            InvoiceValue: amount,
            DisplayCurrencyIso: currency,
            CustomerReference: `hall:${hall.id}`,
            PaymentMethodId: mfGatewayId || 0,
          });

          if (result.IsSuccess && result.Data?.InvoiceStatus === "Paid") {
            chargeSucceeded = true;
            const newBillingAt = new Date(billingDate);
            newBillingAt.setMonth(newBillingAt.getMonth() + 1);
            await storage.updateUser(hall.id, {
              subscriptionExpiresAt: newBillingAt,
              subscriptionStartedAt: now,
              nextBillingAt: newBillingAt,
              paymentWarningAt: null,
              hallNotifiedAt: null,
            });
            // Clear any payment-failure grace tracking
            await storage.deleteSetting(hall.id, "mf_payment_failed_at");
            await storage.deleteSetting(hall.id, "mf_grace_reminder_1da");
            await storage.deleteSetting(hall.id, "mf_grace_reminder_3da");
            console.log(`MyFatoorah billing: auto-renewed hall ${hall.id} (${hall.name}) until ${newBillingAt.toLocaleDateString()}`);
            const successMsg = `✅ *تجديد تلقائي ناجح*\n\nتم تجديد اشتراكك في إنفايتنا تلقائياً حتى ${newBillingAt.toLocaleDateString("ar-SA-u-ca-gregory")}.`;
            try { await sendHallMsg(hall.phoneNumber, successMsg); } catch (_) { /* best-effort */ }
            if (admin.phoneNumber) {
              const adminMsg = `✅ *تجديد تلقائي*\n\nتم تجديد اشتراك قاعة *${hall.name}* تلقائياً حتى ${newBillingAt.toLocaleDateString("ar-SA-u-ca-gregory")}.`;
              try { await sendHallMsg(admin.phoneNumber, adminMsg); } catch (_) { /* best-effort */ }
            }
          } else {
            console.warn(`MyFatoorah billing: DirectPayment failed for hall ${hall.id}: ${result.Message}`);
          }
        } catch (err: unknown) {
          console.error(`MyFatoorah billing: auto-renew error for hall ${hall.id}:`, err instanceof Error ? err.message : err);
        }

        if (!chargeSucceeded) {
          await applyPaymentFailureGrace(hall, "charge_failed");
        }
      } else {
        // No recurring token — apply same 15-day grace as charge failure
        await applyPaymentFailureGrace(hall, "no_token");
      }
      continue;
    }

    // ──────────────────────────────────────────────────────────
    // B) Upcoming renewal — send reminders (7-day and 3-day)
    // ──────────────────────────────────────────────────────────
    if (daysUntilBilling > 0 && daysUntilBilling <= 7) {
      const REMINDER_THROTTLE = 25 * 60 * 60 * 1000;

      // 3-day reminder
      if (daysUntilBilling <= 3) {
        const reminderKey = "myfatoorah_renewal_reminder_sent_at";
        const lastReminderStr = await storage.getSetting(hall.id, reminderKey);
        const lastReminder = lastReminderStr ? new Date(lastReminderStr) : null;
        const alreadyReminded = lastReminder && (now.getTime() - lastReminder.getTime()) < REMINDER_THROTTLE;
        if (!alreadyReminded) {
          const daysStr = daysUntilBilling < 1 ? "اليوم" : `خلال ${Math.ceil(daysUntilBilling)} يوم`;
          const msg = `🔔 *تذكير تجديد الاشتراك*\n\nسيتم تجديد اشتراكك في إنفايتنا تلقائياً ${daysStr}.\n\nإذا واجهت أي مشكلة تواصل معنا مسبقاً.`;
          try {
            await sendHallMsg(hall.phoneNumber, msg);
            await storage.setSetting(hall.id, reminderKey, now.toISOString());
            console.log(`MyFatoorah billing: sent 3-day reminder to hall ${hall.id} (${hall.name})`);
          } catch (e: unknown) {
            console.error(`MyFatoorah billing: failed to remind hall ${hall.id}:`, e instanceof Error ? e.message : e);
          }
        }
      }

      // 7-day reminder
      if (daysUntilBilling > 3) {
        const reminderKey7 = "myfatoorah_renewal_reminder_7d_sent_at";
        const lastReminder7Str = await storage.getSetting(hall.id, reminderKey7);
        const lastReminder7 = lastReminder7Str ? new Date(lastReminder7Str) : null;
        const alreadyReminded7 = lastReminder7 && (now.getTime() - lastReminder7.getTime()) < REMINDER_THROTTLE;
        if (!alreadyReminded7) {
          const msg = `🔔 *تذكير مبكر — تجديد الاشتراك*\n\nسيتم تجديد اشتراكك في إنفايتنا تلقائياً خلال ${Math.ceil(daysUntilBilling)} يوم.\n\nإذا واجهت أي مشكلة تواصل معنا مسبقاً.`;
          try {
            await sendHallMsg(hall.phoneNumber, msg);
            await storage.setSetting(hall.id, reminderKey7, now.toISOString());
            console.log(`MyFatoorah billing: sent 7-day reminder to hall ${hall.id} (${hall.name})`);
          } catch (e: unknown) {
            console.error(`MyFatoorah billing: failed to remind hall ${hall.id}:`, e instanceof Error ? e.message : e);
          }
        }
      }
    }
  }

  // ──────────────────────────────────────────────────────────
  // Helper: apply 15-day payment grace period (shared logic for
  // both charge failures and no-token cases)
  // ──────────────────────────────────────────────────────────
  async function applyPaymentFailureGrace(hall: Awaited<ReturnType<typeof storage.getAllHalls>>[number], reason: string): Promise<void> {
    const now2 = new Date();
    const DAY2 = 24 * 60 * 60 * 1000;
    const failedAtStr = await storage.getSetting(hall.id, "mf_payment_failed_at");

    if (!failedAtStr) {
      // First failure — start grace period
      await storage.setSetting(hall.id, "mf_payment_failed_at", now2.toISOString());
      console.log(`MyFatoorah billing: payment failure (${reason}) for hall ${hall.id} (${hall.name}) — starting 15-day grace period`);
      const failMsg = reason === "no_token"
        ? `⚠️ *لم يتم تجديد اشتراكك*\n\nلا يوجد رمز تجديد تلقائي لاشتراكك في إنفايتنا. ستستمر خدمتك لمدة 15 يوماً. يرجى التواصل مع الإدارة لتجديد اشتراكك.`
        : `⚠️ *فشل خصم الاشتراك*\n\nفشل خصم رسوم تجديد اشتراكك في إنفايتنا. ستستمر خدمتك لمدة 15 يوماً. يرجى تحديث بيانات الدفع أو التواصل مع الإدارة.`;
      try { await sendHallMsg(hall.phoneNumber, failMsg); } catch (_) { /* best-effort */ }
      if (admin.phoneNumber) {
        const adminMsg = `⚠️ *فشل خصم اشتراك*\n\nقاعة *${hall.name}*: فشل السداد (${reason === "no_token" ? "لا يوجد رمز" : "خطأ في الخصم"}) — بدأت مهلة 15 يوماً.`;
        try { await sendHallMsg(admin.phoneNumber, adminMsg); } catch (_) { /* best-effort */ }
      }
    } else {
      const daysSince = (now2.getTime() - new Date(failedAtStr).getTime()) / DAY2;
      if (daysSince >= 15) {
        // Grace period ended — suspend now
        await storage.updateUser(hall.id, { billingStatus: "expired", isSuspended: true });
        await storage.cascadeSuspendHallClients(hall.id, true);
        console.log(`MyFatoorah billing: suspended hall ${hall.id} (${hall.name}) after 15-day grace period`);
        const suspendMsg = `⛔ *تجميد الحساب*\n\nتم تجميد حسابك في إنفايتنا بسبب عدم السداد خلال 15 يوماً. تواصل مع الإدارة لإعادة التفعيل.`;
        try { await sendHallMsg(hall.phoneNumber, suspendMsg); } catch (_) { /* best-effort */ }
        if (admin.phoneNumber) {
          const adminMsg = `⛔ *تجميد قاعة تلقائي*\n\nتم تجميد قاعة *${hall.name}* بعد مرور 15 يوماً على فشل السداد.`;
          try { await sendHallMsg(admin.phoneNumber, adminMsg); } catch (_) { /* best-effort */ }
        }
      } else {
        console.log(`MyFatoorah billing: hall ${hall.id} (${hall.name}) still in grace period — ${Math.floor(daysSince)}/${15} days`);
      }
    }
  }
}
