/**
 * Notification Emails Service Tests
 *
 * Tests for email notification service functionality
 *
 * Test Coverage:
 * - getUserPreferences returns correct data or defaults
 * - isNotificationEnabled returns correct boolean based on preferences
 * - sendPreferencesConfirmationEmail sends email
 * - Notification preference checking before sending
 * - Security alerts sent even when user not found (fail-safe)
 */

import { notificationEmailsService } from "@/lib/services/notification-emails.service";
import { prisma } from "@/lib/db";
import { sendEmail } from "@/lib/email";
import type { EmailNotificationPreferences } from "@/lib/types/notification.types";

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

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

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

jest.mock("@/lib/services/notification-templates.service", () => ({
  notificationTemplatesService: {
    createPreferencesConfirmationEmail: jest.fn(() => ({
      subject: "Preferences Updated",
      html: "<p>Your preferences have been updated</p>",
      text: "Your preferences have been updated",
    })),
    createBookingNotificationEmail: jest.fn(() => ({
      subject: "New Booking",
      html: "<p>New booking received</p>",
      text: "New booking received",
    })),
    createReminderEmail: jest.fn(() => ({
      subject: "Appointment Reminder",
      html: "<p>Reminder</p>",
      text: "Reminder",
    })),
    createPaymentReceiptEmail: jest.fn(() => ({
      subject: "Payment Receipt",
      html: "<p>Payment receipt</p>",
      text: "Payment receipt",
    })),
    createSecurityAlertEmail: jest.fn(() => ({
      subject: "Security Alert",
      html: "<p>Security alert</p>",
      text: "Security alert",
    })),
    createWeeklyReportEmail: jest.fn(() => ({
      subject: "Weekly Report",
      html: "<p>Weekly report</p>",
      text: "Weekly report",
    })),
  },
}));

const mockSendEmail = sendEmail as jest.MockedFunction<typeof sendEmail>;

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

  const mockUser = {
    id: "user-123",
    email: "test@example.com",
    name: "Test User",
    role: "CUSTOMER",
    isActive: true,
    isSuspended: false,
  };

  const mockPreferences: EmailNotificationPreferences = {
    bookings: true,
    reminders: true,
    payments: true,
    marketing: false,
    security: true,
    weeklyReport: false,
  };

  const mockUserPreferences = {
    id: "pref-123",
    userId: mockUser.id,
    language: "en" as const,
    timezone: "Asia/Qatar",
    dateFormat: "DD/MM/YYYY",
    emailNotifications: mockPreferences,
    emailFrequency: "instant" as const,
    dashboardPreferences: {
      defaultTab: "overview",
      refreshInterval: 60,
      viewDensity: "comfortable" as const,
    },
    createdAt: new Date(),
    updatedAt: new Date(),
  };

  describe("getUserPreferences", () => {
    it("should return user preferences when user exists", async () => {
      (prisma.users.findUnique as jest.Mock).mockResolvedValue({
        ...mockUser,
        user_preferences: mockUserPreferences,
      });

      const result =
        await notificationEmailsService.getUserPreferences("user-123");

      expect(result).toEqual({
        preferences: mockPreferences,
        language: "en",
        timezone: "Asia/Qatar",
        email: "test@example.com",
        name: "Test User",
      });
    });

    it("should return default preferences when user has no preferences set", async () => {
      (prisma.users.findUnique as jest.Mock).mockResolvedValue({
        ...mockUser,
        user_preferences: null,
      });

      const result =
        await notificationEmailsService.getUserPreferences("user-123");

      expect(result).toEqual({
        preferences: {
          bookings: true,
          reminders: true,
          payments: true,
          marketing: false,
          security: true,
          weeklyReport: false,
        },
        language: "ar", // Default language
        timezone: "Asia/Qatar",
        email: "test@example.com",
        name: "Test User",
      });
    });

    it("should return null when user not found", async () => {
      (prisma.users.findUnique as jest.Mock).mockResolvedValue(null);

      const result =
        await notificationEmailsService.getUserPreferences("nonexistent");

      expect(result).toBeNull();
    });

    it("should handle database errors gracefully", async () => {
      (prisma.users.findUnique as jest.Mock).mockRejectedValue(
        new Error("Database error"),
      );

      const result =
        await notificationEmailsService.getUserPreferences("user-123");

      expect(result).toBeNull();
    });
  });

  describe("isNotificationEnabled", () => {
    it("should return true when notification is enabled", async () => {
      (prisma.users.findUnique as jest.Mock).mockResolvedValue({
        ...mockUser,
        user_preferences: mockUserPreferences,
      });

      const isEnabled = await notificationEmailsService.isNotificationEnabled(
        "user-123",
        "bookings",
      );

      expect(isEnabled).toBe(true);
    });

    it("should return false when notification is disabled", async () => {
      (prisma.users.findUnique as jest.Mock).mockResolvedValue({
        ...mockUser,
        user_preferences: mockUserPreferences,
      });

      const isEnabled = await notificationEmailsService.isNotificationEnabled(
        "user-123",
        "marketing",
      );

      expect(isEnabled).toBe(false);
    });

    it("should return true for security alerts when user not found (fail-safe)", async () => {
      (prisma.users.findUnique as jest.Mock).mockResolvedValue(null);

      const isEnabled = await notificationEmailsService.isNotificationEnabled(
        "user-123",
        "security",
      );

      expect(isEnabled).toBe(true);
    });

    it("should return false for non-security notifications when user not found", async () => {
      (prisma.users.findUnique as jest.Mock).mockResolvedValue(null);

      const isEnabled = await notificationEmailsService.isNotificationEnabled(
        "user-123",
        "bookings",
      );

      expect(isEnabled).toBe(false);
    });

    it("should use default preferences when user has no preferences", async () => {
      (prisma.users.findUnique as jest.Mock).mockResolvedValue({
        ...mockUser,
        user_preferences: null,
      });

      const isEnabledBookings =
        await notificationEmailsService.isNotificationEnabled(
          "user-123",
          "bookings",
        );
      const isEnabledMarketing =
        await notificationEmailsService.isNotificationEnabled(
          "user-123",
          "marketing",
        );

      expect(isEnabledBookings).toBe(true); // Default is true
      expect(isEnabledMarketing).toBe(false); // Default is false
    });
  });

  describe("sendPreferencesConfirmationEmail", () => {
    beforeEach(() => {
      (prisma.users.findUnique as jest.Mock).mockResolvedValue({
        ...mockUser,
        user_preferences: mockUserPreferences,
      });
    });

    it("should send confirmation email successfully", async () => {
      mockSendEmail.mockResolvedValue(true);

      const result =
        await notificationEmailsService.sendPreferencesConfirmationEmail({
          userId: "user-123",
          previousPreferences: mockPreferences,
          newPreferences: { ...mockPreferences, marketing: true },
          changes: [
            {
              setting: "marketing",
              previousValue: false,
              newValue: true,
            },
          ],
        });

      expect(result.success).toBe(true);
      expect(mockSendEmail).toHaveBeenCalledWith(
        "test@example.com",
        expect.objectContaining({
          subject: expect.any(String),
          html: expect.any(String),
          text: expect.any(String),
        }),
      );
    });

    it("should return error when user not found", async () => {
      (prisma.users.findUnique as jest.Mock).mockResolvedValue(null);

      const result =
        await notificationEmailsService.sendPreferencesConfirmationEmail({
          userId: "nonexistent",
          previousPreferences: mockPreferences,
          newPreferences: mockPreferences,
          changes: [],
        });

      expect(result.success).toBe(false);
      expect(result.error).toBe("User not found");
    });

    it("should return error when email sending fails", async () => {
      mockSendEmail.mockResolvedValue(false);

      const result =
        await notificationEmailsService.sendPreferencesConfirmationEmail({
          userId: "user-123",
          previousPreferences: mockPreferences,
          newPreferences: mockPreferences,
          changes: [],
        });

      expect(result.success).toBe(false);
      expect(result.error).toBe("Failed to send email");
    });

    it("should always send preferences confirmation (not subject to preferences)", async () => {
      // Even if all notifications are disabled, confirmation should still be sent
      const allDisabledPrefs = {
        ...mockUserPreferences,
        emailNotifications: {
          bookings: false,
          reminders: false,
          payments: false,
          marketing: false,
          security: false,
          weeklyReport: false,
        },
      };

      (prisma.users.findUnique as jest.Mock).mockResolvedValue({
        ...mockUser,
        user_preferences: allDisabledPrefs,
      });

      mockSendEmail.mockResolvedValue(true);

      const result =
        await notificationEmailsService.sendPreferencesConfirmationEmail({
          userId: "user-123",
          previousPreferences: mockPreferences,
          newPreferences:
            allDisabledPrefs.emailNotifications as EmailNotificationPreferences,
          changes: [
            {
              setting: "bookings",
              previousValue: true,
              newValue: false,
            },
          ],
        });

      expect(result.success).toBe(true);
      expect(mockSendEmail).toHaveBeenCalled();
    });
  });

  describe("sendBookingNotificationEmail", () => {
    beforeEach(() => {
      (prisma.users.findUnique as jest.Mock).mockResolvedValue({
        ...mockUser,
        user_preferences: mockUserPreferences,
      });
    });

    it("should send booking notification when enabled", async () => {
      mockSendEmail.mockResolvedValue(true);

      const result =
        await notificationEmailsService.sendBookingNotificationEmail({
          userId: "user-123",
          bookingId: "booking-123",
          customerName: "John Doe",
          customerPhone: "+97412345678",
          service: "Consultation",
          appointmentDate: new Date("2025-12-15"),
          appointmentTime: "10:00",
          duration: 30,
          totalAmount: 500,
          source: "whatsapp",
        });

      expect(result.success).toBe(true);
      expect(mockSendEmail).toHaveBeenCalled();
    });

    it("should skip booking notification when disabled", async () => {
      const disabledBookingsPrefs = {
        ...mockUserPreferences,
        emailNotifications: {
          ...mockPreferences,
          bookings: false,
        },
      };

      (prisma.users.findUnique as jest.Mock).mockResolvedValue({
        ...mockUser,
        user_preferences: disabledBookingsPrefs,
      });

      const result =
        await notificationEmailsService.sendBookingNotificationEmail({
          userId: "user-123",
          bookingId: "booking-123",
          customerName: "John Doe",
          customerPhone: "+97412345678",
          service: "Consultation",
          appointmentDate: new Date("2025-12-15"),
          appointmentTime: "10:00",
          duration: 30,
          totalAmount: 500,
          source: "whatsapp",
        });

      expect(result.success).toBe(true);
      expect(result.skipped).toBe(true);
      expect(result.skipReason).toBe("Notification disabled by user");
      expect(mockSendEmail).not.toHaveBeenCalled();
    });
  });

  describe("sendReminderEmail", () => {
    beforeEach(() => {
      (prisma.users.findUnique as jest.Mock).mockResolvedValue({
        ...mockUser,
        user_preferences: mockUserPreferences,
      });
    });

    it("should send reminder when enabled", async () => {
      mockSendEmail.mockResolvedValue(true);

      const result = await notificationEmailsService.sendReminderEmail({
        userId: "user-123",
        bookingId: "booking-123",
        customerName: "John Doe",
        service: "Consultation",
        appointmentDate: new Date("2025-12-15T10:00:00"),
        appointmentTime: "10:00",
        duration: 30,
        hoursUntil: 24,
      });

      expect(result.success).toBe(true);
      expect(mockSendEmail).toHaveBeenCalled();
    });

    it("should skip reminder when disabled", async () => {
      const disabledRemindersPrefs = {
        ...mockUserPreferences,
        emailNotifications: {
          ...mockPreferences,
          reminders: false,
        },
      };

      (prisma.users.findUnique as jest.Mock).mockResolvedValue({
        ...mockUser,
        user_preferences: disabledRemindersPrefs,
      });

      const result = await notificationEmailsService.sendReminderEmail({
        userId: "user-123",
        bookingId: "booking-123",
        customerName: "John Doe",
        service: "Consultation",
        appointmentDate: new Date("2025-12-15T10:00:00"),
        appointmentTime: "10:00",
        duration: 30,
        hoursUntil: 24,
      });

      expect(result.skipped).toBe(true);
      expect(mockSendEmail).not.toHaveBeenCalled();
    });
  });

  describe("sendSecurityAlertEmail", () => {
    beforeEach(() => {
      (prisma.users.findUnique as jest.Mock).mockResolvedValue({
        ...mockUser,
        user_preferences: mockUserPreferences,
      });
    });

    it("should send security alert when enabled", async () => {
      mockSendEmail.mockResolvedValue(true);

      const result = await notificationEmailsService.sendSecurityAlertEmail({
        userId: "user-123",
        alertType: "new_login",
        ipAddress: "1.2.3.4",
        eventTime: new Date(),
      });

      expect(result.success).toBe(true);
      expect(mockSendEmail).toHaveBeenCalled();
    });

    it("should skip security alert when explicitly disabled", async () => {
      const disabledSecurityPrefs = {
        ...mockUserPreferences,
        emailNotifications: {
          ...mockPreferences,
          security: false,
        },
      };

      (prisma.users.findUnique as jest.Mock).mockResolvedValue({
        ...mockUser,
        user_preferences: disabledSecurityPrefs,
      });

      const result = await notificationEmailsService.sendSecurityAlertEmail({
        userId: "user-123",
        alertType: "new_login",
        ipAddress: "1.2.3.4",
        eventTime: new Date(),
      });

      expect(result.skipped).toBe(true);
      expect(mockSendEmail).not.toHaveBeenCalled();
    });

    it("should handle user not found gracefully", async () => {
      (prisma.users.findUnique as jest.Mock).mockResolvedValue(null);

      const result = await notificationEmailsService.sendSecurityAlertEmail({
        userId: "nonexistent",
        alertType: "new_login",
        ipAddress: "1.2.3.4",
        eventTime: new Date(),
      });

      expect(result.success).toBe(false);
      expect(result.error).toBe("User not found");
    });
  });

  describe("getUsersWithNotificationEnabled", () => {
    it("should return users with notification enabled", async () => {
      const users = [
        {
          id: "user-1",
          user_preferences: {
            emailNotifications: {
              bookings: true,
              reminders: true,
              payments: true,
              marketing: false,
              security: true,
              weeklyReport: true,
            },
          },
        },
        {
          id: "user-2",
          user_preferences: {
            emailNotifications: {
              bookings: true,
              reminders: true,
              payments: true,
              marketing: false,
              security: true,
              weeklyReport: false,
            },
          },
        },
        {
          id: "user-3",
          user_preferences: null, // Should use defaults
        },
      ];

      (prisma.users.findMany as jest.Mock).mockResolvedValue(users);

      const result =
        await notificationEmailsService.getUsersWithNotificationEnabled(
          "weeklyReport",
        );

      expect(result).toEqual(["user-1"]); // Only user-1 has weeklyReport enabled
    });

    it("should use default preferences for users without preferences", async () => {
      const users = [
        {
          id: "user-1",
          user_preferences: null,
        },
        {
          id: "user-2",
          user_preferences: null,
        },
      ];

      (prisma.users.findMany as jest.Mock).mockResolvedValue(users);

      const result =
        await notificationEmailsService.getUsersWithNotificationEnabled(
          "bookings",
        );

      // Bookings is enabled by default
      expect(result).toEqual(["user-1", "user-2"]);
    });

    it("should handle database errors gracefully", async () => {
      (prisma.users.findMany as jest.Mock).mockRejectedValue(
        new Error("Database error"),
      );

      const result =
        await notificationEmailsService.getUsersWithNotificationEnabled(
          "bookings",
        );

      expect(result).toEqual([]);
    });
  });
});
