/**
 * Reminder runner — picks due reminders, sends emails, advances notified state.
 *
 * Lifecycle:
 *   - Cron hits POST /api/cron/reminders once per hour.
 *   - We fetch all reminders where notified=false AND
 *       computed-fire-date <= now-end-of-day.
 *   - For lunar reminders, "this year's fire date" is the lunar (month, day)
 *     of the original reminderDate, projected onto the current solar year.
 *   - For solar reminders without recurrence, fire date = reminderDate.
 *   - For yearly recurrence, fire date = (month, day) of reminderDate this year.
 *   - For monthly recurrence, fire date = day of reminderDate this month.
 *
 * Idempotency: notified is flipped true after a successful send. Yearly/monthly
 * reminders need notified reset by the cron at end-of-day so next year fires.
 */
import { prisma } from "@/lib/db";
import { sendEmail } from "@/lib/email/provider";
import { reminderEmail } from "@/lib/email/templates";
import { solarToLunar, lunarToSolar } from "@/lib/calendar/engine";

type ReminderRow = {
  id: string;
  userId: string;
  title: string;
  reminderDate: Date;
  isLunar: boolean;
  recurrence: string | null;
  notified: boolean;
};

type RunResult = {
  scanned: number;
  fired: number;
  skipped: number;
  errors: { id: string; error: string }[];
  rolled: number;
};

function ymdLocal(d: Date): { y: number; m: number; day: number } {
  return { y: d.getFullYear(), m: d.getMonth() + 1, day: d.getDate() };
}

/** Compute the next "fire" Date for a reminder relative to `now` (start-of-day VN). */
function nextFireDate(r: ReminderRow, now: Date): Date {
  const orig = r.reminderDate;
  if (!r.recurrence && !r.isLunar) {
    // One-shot solar reminder — fires on its original date.
    return new Date(orig.getFullYear(), orig.getMonth(), orig.getDate());
  }
  if (r.isLunar) {
    // Project lunar (month, day) of the original date into now.year.
    const lunar = solarToLunar(orig);
    // For one-shot lunar, fire when this year's solar projection occurs.
    // If projection has already passed in current year, jump to next year.
    const targetYear = now.getFullYear();
    let solar = lunarToSolar(targetYear, lunar.month, lunar.day);
    let solarDate = new Date(solar.year, solar.month - 1, solar.day);
    if (solarDate < startOfDay(now) && (r.recurrence === "yearly" || !r.recurrence)) {
      // For one-shot lunar in past, return original projection (it already fired or is overdue).
      // For yearly, roll forward.
      if (r.recurrence === "yearly") {
        solar = lunarToSolar(targetYear + 1, lunar.month, lunar.day);
        solarDate = new Date(solar.year, solar.month - 1, solar.day);
      }
    }
    return solarDate;
  }
  // Solar with recurrence
  if (r.recurrence === "yearly") {
    const candidate = new Date(now.getFullYear(), orig.getMonth(), orig.getDate());
    return candidate < startOfDay(now)
      ? new Date(now.getFullYear() + 1, orig.getMonth(), orig.getDate())
      : candidate;
  }
  if (r.recurrence === "monthly") {
    const candidate = new Date(now.getFullYear(), now.getMonth(), orig.getDate());
    return candidate < startOfDay(now)
      ? new Date(now.getFullYear(), now.getMonth() + 1, orig.getDate())
      : candidate;
  }
  return new Date(orig.getFullYear(), orig.getMonth(), orig.getDate());
}

function startOfDay(d: Date): Date {
  return new Date(d.getFullYear(), d.getMonth(), d.getDate());
}
function endOfDay(d: Date): Date {
  return new Date(d.getFullYear(), d.getMonth(), d.getDate(), 23, 59, 59, 999);
}

/** Should this reminder fire today? */
function shouldFire(r: ReminderRow, now: Date): boolean {
  const fire = nextFireDate(r, now);
  const t = ymdLocal(now);
  const f = ymdLocal(fire);
  return t.y === f.y && t.m === f.m && t.day === f.day;
}

/** Reset `notified` flag for recurring reminders the day after they fired,
 *  so next cycle fires again. Single-shot reminders stay notified=true. */
async function rolloverRecurring(now: Date): Promise<number> {
  const today = startOfDay(now);
  // Recurring reminders whose computed fire date is in the past → reset.
  const candidates = await prisma.reminder.findMany({
    where: { notified: true, recurrence: { not: null } },
  });
  let rolled = 0;
  for (const r of candidates) {
    const fire = nextFireDate(r as ReminderRow, now);
    if (fire >= today) {
      // Next fire is today or future — reset so it can fire.
      await prisma.reminder.update({
        where: { id: r.id },
        data: { notified: false },
      });
      rolled += 1;
    }
  }
  return rolled;
}

export async function runReminders(now: Date = new Date()): Promise<RunResult> {
  const result: RunResult = { scanned: 0, fired: 0, skipped: 0, errors: [], rolled: 0 };

  // Step 1: roll over recurring reminders that already fired in past cycle
  result.rolled = await rolloverRecurring(now);

  // Step 2: find candidates
  const today = endOfDay(now);
  const candidates = await prisma.reminder.findMany({
    where: { notified: false },
    include: { user: { select: { email: true, name: true } } },
  });
  result.scanned = candidates.length;

  for (const r of candidates) {
    const row: ReminderRow = {
      id: r.id,
      userId: r.userId,
      title: r.title,
      reminderDate: r.reminderDate,
      isLunar: r.isLunar,
      recurrence: r.recurrence,
      notified: r.notified,
    };

    if (!shouldFire(row, now)) {
      // Check if past-due (one-shot solar overdue) — still send, then mark.
      const fire = nextFireDate(row, now);
      if (fire > today) {
        result.skipped += 1;
        continue;
      }
      // Past-due: fire it now to avoid silent miss.
    }

    if (!r.user.email) {
      result.skipped += 1;
      continue;
    }

    const tpl = reminderEmail({
      userName: r.user.name ?? "bạn",
      reminderTitle: r.title,
      reminderDate: nextFireDate(row, now),
      isLunar: r.isLunar,
    });
    const send = await sendEmail({
      to: r.user.email,
      subject: tpl.subject,
      html: tpl.html,
      text: tpl.text,
      tag: "reminder",
    });

    if (send.ok) {
      await prisma.reminder.update({
        where: { id: r.id },
        data: { notified: true },
      });
      result.fired += 1;
    } else {
      result.errors.push({ id: r.id, error: send.error });
    }
  }

  return result;
}
