jest.mock("@/lib/db");
jest.mock("@/lib/queue/job-queue", () => ({
  enqueueJob: jest.fn().mockResolvedValue("job-1"),
}));
jest.mock("@/lib/services/quotation-notifier.service", () => ({
  notifyQuotationCustomer: jest
    .fn()
    .mockResolvedValue({ emailQueued: true, whatsappSent: true }),
}));

import { prisma } from "@/lib/db";
import { enqueueJob } from "@/lib/queue/job-queue";
import { notifyQuotationCustomer } from "@/lib/services/quotation-notifier.service";
import {
  processDueReminders,
  sendQuotationReminder,
} from "@/lib/services/quotation-reminders.service";

const today = new Date();
today.setHours(0, 0, 0, 0);

describe("processDueReminders", () => {
  beforeEach(() => {
    jest.clearAllMocks();
    (prisma.quotation_reminders.findUnique as jest.Mock).mockResolvedValue(
      null,
    );
  });

  it("enqueues reminder jobs for quotations due today", async () => {
    (prisma.quotations.findMany as jest.Mock).mockResolvedValue([
      { id: "q1", userId: "u1", dueDate: today },
    ]);
    const result = await processDueReminders();
    expect(enqueueJob).toHaveBeenCalledWith(
      "quotation_reminder",
      expect.objectContaining({
        quotationId: "q1",
        trigger: "on_due",
        dayOffset: 0,
      }),
      expect.anything(),
    );
    expect(result.sent).toBe(1);
  });

  it("enqueues reminder for 2 days after due", async () => {
    const twoDaysAgo = new Date(today);
    twoDaysAgo.setDate(twoDaysAgo.getDate() - 2);
    (prisma.quotations.findMany as jest.Mock).mockResolvedValue([
      { id: "q1", userId: "u1", dueDate: twoDaysAgo },
    ]);
    const result = await processDueReminders();
    expect(enqueueJob).toHaveBeenCalledWith(
      "quotation_reminder",
      expect.objectContaining({ trigger: "after_due", dayOffset: 2 }),
      expect.anything(),
    );
    expect(result.sent).toBe(1);
  });

  it("enqueues overdue notice for 5 days after due", async () => {
    const fiveDaysAgo = new Date(today);
    fiveDaysAgo.setDate(fiveDaysAgo.getDate() - 5);
    (prisma.quotations.findMany as jest.Mock).mockResolvedValue([
      { id: "q1", userId: "u1", dueDate: fiveDaysAgo },
    ]);
    const result = await processDueReminders();
    expect(enqueueJob).toHaveBeenCalledWith(
      "quotation_reminder",
      expect.objectContaining({ trigger: "after_due", dayOffset: 5 }),
      expect.anything(),
    );
  });

  it("skips already-sent reminders (dedup)", async () => {
    (prisma.quotations.findMany as jest.Mock).mockResolvedValue([
      { id: "q1", userId: "u1", dueDate: today },
    ]);
    (prisma.quotation_reminders.findUnique as jest.Mock).mockResolvedValue({
      id: "existing",
    });
    const result = await processDueReminders();
    expect(enqueueJob).not.toHaveBeenCalled();
    expect(result.skipped).toBe(1);
  });
});

describe("sendQuotationReminder", () => {
  beforeEach(() => jest.clearAllMocks());

  it("calls notifyQuotationCustomer with reminder type for dayOffset 0", async () => {
    (prisma.quotations.findUnique as jest.Mock).mockResolvedValue({
      id: "q1",
      userId: "u1",
      quotationNumber: "Q-001",
      title: "Test",
      total: 500,
      amountPaid: 0,
      currency: "QAR",
      dueDate: today,
      portalToken: "tok",
      createdAt: new Date(),
      penaltyAmount: 0,
      customer: { name: "Ahmed", email: "a@b.com", phone: "+974123" },
      items: [],
    });
    await sendQuotationReminder("q1", "on_due", 0, "email");
    expect(notifyQuotationCustomer).toHaveBeenCalledWith(
      expect.objectContaining({ id: "q1" }),
      "reminder",
    );
  });

  it("calls notifyQuotationCustomer with overdue type for dayOffset 5", async () => {
    (prisma.quotations.findUnique as jest.Mock).mockResolvedValue({
      id: "q1",
      userId: "u1",
      quotationNumber: "Q-001",
      title: "Test",
      total: 500,
      amountPaid: 0,
      currency: "QAR",
      dueDate: today,
      portalToken: "tok",
      createdAt: new Date(),
      penaltyAmount: 0,
      customer: { name: "Ahmed", email: "a@b.com", phone: "+974123" },
      items: [],
    });
    await sendQuotationReminder("q1", "after_due", 5, "both");
    expect(notifyQuotationCustomer).toHaveBeenCalledWith(
      expect.objectContaining({ id: "q1" }),
      "overdue",
    );
  });

  it("does not write dedup record when notifier throws", async () => {
    (prisma.quotations.findUnique as jest.Mock).mockResolvedValue({
      id: "q1",
      userId: "u1",
      quotationNumber: "Q-001",
      title: "Test",
      total: 500,
      amountPaid: 0,
      currency: "QAR",
      dueDate: today,
      portalToken: "tok",
      createdAt: new Date(),
      penaltyAmount: 0,
      customer: { name: "Ahmed", email: "a@b.com", phone: "+974123" },
      items: [],
    });
    (notifyQuotationCustomer as jest.Mock).mockRejectedValue(
      new Error("network error"),
    );
    await expect(
      sendQuotationReminder("q1", "on_due", 0, "email"),
    ).resolves.toBeUndefined();
    expect(prisma.quotation_reminders.create).not.toHaveBeenCalled();
  });
});
