import { ServiceUnavailableException } from '@nestjs/common';
import * as nodemailer from 'nodemailer';
import { EmailService } from './email.service';

jest.mock('nodemailer', () => ({ createTransport: jest.fn() }));

describe('EmailService', () => {
  const setup = (overrides: Record<string, unknown> = {}) => {
    const values: Record<string, unknown> = {
      nodeEnv: 'test',
      'email.enabled': true,
      'email.fromName': 'Oudelaa',
      'email.fromEmail': 'noreply@oudelaa.test',
      'email.smtpHost': 'smtp.oudelaa.test',
      'email.smtpPort': 587,
      'email.smtpSecure': false,
      'email.smtpUser': 'smtp-user',
      'email.smtpPass': 'smtp-pass',
      ...overrides,
    };
    const config = { get: jest.fn((key: string) => values[key]) };
    const sendMail = jest.fn().mockResolvedValue({ messageId: 'message-1' });
    (nodemailer.createTransport as jest.Mock).mockReturnValue({ sendMail });
    return { service: new EmailService(config as any), config, sendMail };
  };

  beforeEach(() => jest.clearAllMocks());

  it('sends verification and reset codes and reuses the SMTP transporter', async () => {
    const { service, sendMail } = setup();

    await service.sendVerificationCode('member@example.com', '123456', 10);
    await service.sendPasswordResetCode('member@example.com', '654321', 15);

    expect(nodemailer.createTransport).toHaveBeenCalledTimes(1);
    expect(nodemailer.createTransport).toHaveBeenCalledWith({
      host: 'smtp.oudelaa.test',
      port: 587,
      secure: false,
      auth: { user: 'smtp-user', pass: 'smtp-pass' },
    });
    expect(sendMail).toHaveBeenNthCalledWith(
      1,
      expect.objectContaining({
        from: 'Oudelaa <noreply@oudelaa.test>',
        to: 'member@example.com',
        text: expect.stringContaining('123456'),
        html: expect.stringContaining('123456'),
      }),
    );
    expect(sendMail).toHaveBeenNthCalledWith(
      2,
      expect.objectContaining({ text: expect.stringContaining('654321'), html: expect.stringContaining('654321') }),
    );
  });

  it('silently skips disabled delivery outside production', async () => {
    const { service } = setup({ 'email.enabled': false, nodeEnv: 'test' });

    await expect(service.sendVerificationCode('member@example.com', '123456', 10)).resolves.toBeUndefined();
    expect(nodemailer.createTransport).not.toHaveBeenCalled();
  });

  it('rejects disabled delivery in production', async () => {
    const { service } = setup({ 'email.enabled': false, nodeEnv: 'production' });
    await expect(service.sendVerificationCode('member@example.com', '123456', 10)).rejects.toThrow(
      'Email delivery is disabled',
    );
  });

  it('requires a configured sender and complete SMTP credentials', async () => {
    const missingSender = setup({ 'email.fromEmail': '' }).service;
    await expect(missingSender.sendVerificationCode('member@example.com', '123456', 10)).rejects.toThrow(
      'Email sender is not configured',
    );

    const missingSmtp = setup({ 'email.smtpHost': '', 'email.smtpUser': '', 'email.smtpPass': '' }).service;
    await expect(missingSmtp.sendVerificationCode('member@example.com', '123456', 10)).rejects.toThrow(
      'SMTP settings are not configured',
    );
  });

  it.each([
    ['email.smtpPort', undefined],
    ['email.smtpSecure', undefined],
    ['email.fromName', undefined],
  ])('uses a safe default when %s is absent', async (key, value) => {
    const { service } = setup({ [key]: value });
    await service.sendVerificationCode('member@example.com', '123456', 10);
    expect(nodemailer.createTransport).toHaveBeenCalled();
  });

  it('returns diagnostic SMTP context in development and a generic error elsewhere', async () => {
    const development = setup({ nodeEnv: 'development' });
    development.sendMail.mockRejectedValueOnce(Object.assign(new Error('connection refused'), { code: 'ECONNREFUSED' }));
    await expect(development.service.sendVerificationCode('member@example.com', '123456', 10)).rejects.toThrow(
      'ECONNREFUSED: connection refused',
    );

    const production = setup({ nodeEnv: 'production' });
    production.sendMail.mockRejectedValueOnce(new Error('secret provider detail'));
    await expect(production.service.sendVerificationCode('member@example.com', '123456', 10)).rejects.toEqual(
      expect.any(ServiceUnavailableException),
    );
    await expect(
      Promise.reject(new ServiceUnavailableException('Failed to send verification email')),
    ).rejects.toThrow('Failed to send verification email');
  });
});
