/**
 * Tests for Review Reminder Service
 */

// Mock dependencies before imports
jest.mock("@/lib/db");

jest.mock("@/lib/queue/email-queue", () => ({
  queueEmail: jest.fn(),
}));

jest.mock("@/lib/logger", () => ({
  logger: {
    info: jest.fn(),
    warn: jest.fn(),
    error: jest.fn(),
    debug: jest.fn(),
    scope: jest.fn().mockReturnValue({
      info: jest.fn(),
      warn: jest.fn(),
      error: jest.fn(),
      debug: jest.fn(),
    }),
  },
}));

jest.mock("@/lib/services/twilio-whatsapp.service", () => ({
  twilioWhatsAppService: {
    getCredentialsForOrg: jest.fn(),
    sendTextMessage: jest.fn(),
  },
}));

import { prisma } from "@/lib/db";
import { queueEmail } from "@/lib/queue/email-queue";
import {
  processReviewReminders,
  expireStaleRequests,
} from "@/lib/services/review-reminder.service";

const mockPrisma = prisma as jest.Mocked<typeof prisma>;
const mockQueueEmail = queueEmail as jest.MockedFunction<typeof queueEmail>;

function makeRequest(overrides: Record<string, unknown> = {}) {
  return {
    id: "req-1",
    userId: "user-1",
    customerName: "Test Customer",
    customerEmail: "test@example.com",
    customerPhone: null,
    token: "token-abc",
    language: "en",
    reminderCount: 0,
    ...overrides,
  };
}

describe("processReviewReminders", () => {
  beforeEach(() => {
    jest.clearAllMocks();
    process.env.NEXT_PUBLIC_APP_URL = "https://test.mawidi.com";
    // Default: expire returns 0, no due requests
    (mockPrisma.review_requests.updateMany as jest.Mock).mockResolvedValue({
      count: 0,
    });
    (mockPrisma.review_requests.findMany as jest.Mock).mockResolvedValue([]);
    (mockPrisma.review_requests.update as jest.Mock).mockResolvedValue({});
    mockQueueEmail.mockResolvedValue(undefined);
  });

  it("sends reminder for due requests", async () => {
    (mockPrisma.review_requests.findMany as jest.Mock).mockResolvedValueOnce([
      makeRequest(),
    ]);

    const result = await processReviewReminders();

    expect(result.reminded).toBe(1);
    expect(mockQueueEmail).toHaveBeenCalledWith(
      expect.objectContaining({
        to: "test@example.com",
        category: "review_reminder",
      }),
    );
  });

  it("increments reminderCount from 0 to 1", async () => {
    (mockPrisma.review_requests.findMany as jest.Mock).mockResolvedValueOnce([
      makeRequest({ reminderCount: 0 }),
    ]);

    await processReviewReminders();

    expect(mockPrisma.review_requests.update).toHaveBeenCalledWith(
      expect.objectContaining({
        where: { id: "req-1" },
        data: expect.objectContaining({
          reminderCount: 1,
        }),
      }),
    );
  });

  it("sets nextReminderAt to +7 days after first reminder (count 0→1)", async () => {
    (mockPrisma.review_requests.findMany as jest.Mock).mockResolvedValueOnce([
      makeRequest({ reminderCount: 0 }),
    ]);

    await processReviewReminders();

    const updateCall = (mockPrisma.review_requests.update as jest.Mock).mock
      .calls[0][0];
    const nextReminder = updateCall.data.nextReminderAt as Date;
    expect(nextReminder).toBeInstanceOf(Date);
    // Should be approximately 7 days from now
    const sevenDaysMs = 7 * 24 * 60 * 60 * 1000;
    const diff = nextReminder.getTime() - Date.now();
    expect(diff).toBeGreaterThan(sevenDaysMs - 60000); // within 1 minute tolerance
    expect(diff).toBeLessThan(sevenDaysMs + 60000);
  });

  it("sets nextReminderAt to null after second reminder (count 1→2)", async () => {
    (mockPrisma.review_requests.findMany as jest.Mock).mockResolvedValueOnce([
      makeRequest({ reminderCount: 1 }),
    ]);

    await processReviewReminders();

    expect(mockPrisma.review_requests.update).toHaveBeenCalledWith(
      expect.objectContaining({
        data: expect.objectContaining({
          reminderCount: 2,
          nextReminderAt: null,
        }),
      }),
    );
  });

  it("handles errors per-request without failing batch", async () => {
    (mockPrisma.review_requests.findMany as jest.Mock).mockResolvedValueOnce([
      makeRequest({ id: "req-ok" }),
      makeRequest({ id: "req-fail" }),
    ]);

    mockQueueEmail
      .mockResolvedValueOnce(undefined)
      .mockResolvedValueOnce(undefined);

    (mockPrisma.review_requests.update as jest.Mock)
      .mockResolvedValueOnce({})
      .mockRejectedValueOnce(new Error("DB error"));

    const result = await processReviewReminders();
    expect(result.reminded).toBe(1);
    expect(result.errors).toBe(1);
  });

  it("calls expireStaleRequests before processing reminders", async () => {
    (mockPrisma.review_requests.updateMany as jest.Mock).mockResolvedValueOnce({
      count: 3,
    });

    const result = await processReviewReminders();
    expect(result.expired).toBe(3);
    expect(mockPrisma.review_requests.updateMany).toHaveBeenCalled();
  });
});

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

  it("marks expired PENDING/SENT requests as EXPIRED", async () => {
    (mockPrisma.review_requests.updateMany as jest.Mock).mockResolvedValueOnce({
      count: 5,
    });

    const count = await expireStaleRequests();
    expect(count).toBe(5);
    expect(mockPrisma.review_requests.updateMany).toHaveBeenCalledWith({
      where: {
        status: { in: ["PENDING", "SENT"] },
        expiresAt: { lte: expect.any(Date) },
      },
      data: {
        status: "EXPIRED",
        nextReminderAt: null,
      },
    });
  });

  it("returns 0 when no requests to expire", async () => {
    (mockPrisma.review_requests.updateMany as jest.Mock).mockResolvedValueOnce({
      count: 0,
    });

    const count = await expireStaleRequests();
    expect(count).toBe(0);
  });

  it("returns 0 on database error", async () => {
    (mockPrisma.review_requests.updateMany as jest.Mock).mockRejectedValueOnce(
      new Error("DB error"),
    );

    const count = await expireStaleRequests();
    expect(count).toBe(0);
  });
});
