/**
 * 邮件服务测试
 * 
 * 功能测试：
 * - 邮件服务初始化
 * - 邮件发送功能
 * - 验证码邮件发送
 * - 欢迎邮件发送
 * - 邮件模板生成
 * - 连接验证
 * 
 * 职责分离：
 * - 单元测试：测试各个方法的功能正确性
 * - Mock测试：模拟外部依赖进行隔离测试
 * - 异常测试：验证错误处理机制
 * 
 * 最近修改：
 * - 2026-01-07: 代码规范优化 - 完善文件头注释和修改记录
 * 
 * @author moyin
 * @version 1.0.1
 * @since 2025-12-17
 * @lastModified 2026-01-07
 */

import { Test, TestingModule } from '@nestjs/testing';
import { ConfigService } from '@nestjs/config';
import { EmailService, EmailOptions, VerificationEmailOptions } from './email.service';
import * as nodemailer from 'nodemailer';

// Mock nodemailer
jest.mock('nodemailer');
const mockedNodemailer = nodemailer as jest.Mocked<typeof nodemailer>;

describe('EmailService', () => {
  let service: EmailService;
  let configService: jest.Mocked<ConfigService>;
  let mockTransporter: any;

  beforeEach(async () => {
    // 创建 mock transporter
    mockTransporter = {
      sendMail: jest.fn(),
      verify: jest.fn(),
      options: {}
    };

    // Mock ConfigService
    const mockConfigService = {
      get: jest.fn(),
    };

    // Mock nodemailer.createTransport
    mockedNodemailer.createTransport.mockReturnValue(mockTransporter);

    const module: TestingModule = await Test.createTestingModule({
      providers: [
        EmailService,
        {
          provide: ConfigService,
          useValue: mockConfigService,
        },
      ],
    }).compile();

    service = module.get<EmailService>(EmailService);
    configService = module.get(ConfigService);
  });

  afterEach(() => {
    jest.clearAllMocks();
  });

  describe('初始化测试', () => {
    it('应该正确初始化邮件服务', () => {
      expect(service).toBeDefined();
      expect(mockedNodemailer.createTransport).toHaveBeenCalled();
    });

    it('应该在没有配置时使用测试模式', () => {
      configService.get.mockReturnValue(undefined);
      
      // 重新创建服务实例来测试测试模式
      new EmailService(configService);
      
      expect(mockedNodemailer.createTransport).toHaveBeenCalledWith({
        streamTransport: true,
        newline: 'unix',
        buffer: true
      });
    });

    it('应该在有配置时使用真实SMTP', () => {
      configService.get
        .mockReturnValueOnce('smtp.gmail.com') // EMAIL_HOST
        .mockReturnValueOnce(587) // EMAIL_PORT
        .mockReturnValueOnce(false) // EMAIL_SECURE
        .mockReturnValueOnce('test@gmail.com') // EMAIL_USER
        .mockReturnValueOnce('password'); // EMAIL_PASS

      new EmailService(configService);

      expect(mockedNodemailer.createTransport).toHaveBeenCalledWith({
        host: 'smtp.gmail.com',
        port: 587,
        secure: false,
        auth: {
          user: 'test@gmail.com',
          pass: 'password',
        },
      });
    });
  });

  describe('sendEmail', () => {
    it('应该成功发送邮件', async () => {
      const emailOptions: EmailOptions = {
        to: 'test@example.com',
        subject: '测试邮件',
        html: '<p>测试内容</p>',
        text: '测试内容'
      };

      mockTransporter.sendMail.mockResolvedValue({ messageId: 'test-id' });
      configService.get.mockReturnValue('"Test Sender" <noreply@test.com>');

      const result = await service.sendEmail(emailOptions);

      expect(result.success).toBe(true);
      expect(result.isTestMode).toBe(false);
      expect(mockTransporter.sendMail).toHaveBeenCalledWith({
        from: '"Test Sender" <noreply@test.com>',
        to: 'test@example.com',
        subject: '测试邮件',
        html: '<p>测试内容</p>',
        text: '测试内容',
      });
    });

    it('应该在发送失败时返回false', async () => {
      const emailOptions: EmailOptions = {
        to: 'test@example.com',
        subject: '测试邮件',
        html: '<p>测试内容</p>'
      };

      mockTransporter.sendMail.mockRejectedValue(new Error('发送失败'));

      const result = await service.sendEmail(emailOptions);

      expect(result.success).toBe(false);
      expect(result.error).toBe('发送失败');
    });

    it('应该在测试模式下输出邮件内容', async () => {
      const emailOptions: EmailOptions = {
        to: 'test@example.com',
        subject: '测试邮件',
        html: '<p>测试内容</p>',
        text: '测试内容'
      };

      // Mock transporter with streamTransport option
      const testTransporter = {
        ...mockTransporter,
        options: { streamTransport: true },
        sendMail: jest.fn().mockResolvedValue({ messageId: 'test-id' })
      };
      
      // Mock the service to use test transporter
      const loggerSpy = jest.spyOn(service['logger'], 'warn').mockImplementation();
      service['transporter'] = testTransporter;

      const result = await service.sendEmail(emailOptions);

      expect(result.success).toBe(true);
      expect(result.isTestMode).toBe(true);
      expect(loggerSpy).toHaveBeenCalledWith('=== 邮件发送（测试模式 - 邮件未真实发送） ===');
      
      loggerSpy.mockRestore();
    });
  });

  describe('sendVerificationCode', () => {
    it('应该成功发送邮箱验证码', async () => {
      const options: VerificationEmailOptions = {
        email: 'test@example.com',
        code: '123456',
        nickname: '测试用户',
        purpose: 'email_verification'
      };

      mockTransporter.sendMail.mockResolvedValue({ messageId: 'test-id' });
      configService.get.mockReturnValue('"Test Sender" <noreply@test.com>');

      const result = await service.sendVerificationCode(options);

      expect(result.success).toBe(true);
      expect(result.isTestMode).toBe(false);
      expect(mockTransporter.sendMail).toHaveBeenCalledWith(
        expect.objectContaining({
          to: 'test@example.com',
          subject: '【Whale Town】邮箱验证码',
          text: '您的验证码是：123456，5分钟内有效，请勿泄露给他人。'
        })
      );
    });

    it('应该成功发送密码重置验证码', async () => {
      const options: VerificationEmailOptions = {
        email: 'test@example.com',
        code: '654321',
        nickname: '测试用户',
        purpose: 'password_reset'
      };

      mockTransporter.sendMail.mockResolvedValue({ messageId: 'test-id' });
      configService.get.mockReturnValue('"Test Sender" <noreply@test.com>');

      const result = await service.sendVerificationCode(options);

      expect(result.success).toBe(true);
      expect(result.isTestMode).toBe(false);
      expect(mockTransporter.sendMail).toHaveBeenCalledWith(
        expect.objectContaining({
          to: 'test@example.com',
          subject: '【Whale Town】密码重置验证码',
          text: '您的验证码是：654321，5分钟内有效，请勿泄露给他人。'
        })
      );
    });

    it('应该成功发送登录验证码', async () => {
      const options: VerificationEmailOptions = {
        email: 'test@example.com',
        code: '789012',
        nickname: '测试用户',
        purpose: 'login_verification'
      };

      mockTransporter.sendMail.mockResolvedValue({ messageId: 'test-id' });
      configService.get.mockReturnValue('"Test Sender" <noreply@test.com>');

      const result = await service.sendVerificationCode(options);

      expect(result.success).toBe(true);
      expect(result.isTestMode).toBe(false);
      expect(mockTransporter.sendMail).toHaveBeenCalledWith(
        expect.objectContaining({
          to: 'test@example.com',
          subject: '【Whale Town】登录验证码',
          text: '您的验证码是：789012，5分钟内有效，请勿泄露给他人。'
        })
      );
    });

    it('应该在发送失败时返回false', async () => {
      const options: VerificationEmailOptions = {
        email: 'test@example.com',
        code: '123456',
        purpose: 'email_verification'
      };

      mockTransporter.sendMail.mockRejectedValue(new Error('发送失败'));

      const result = await service.sendVerificationCode(options);

      expect(result.success).toBe(false);
      expect(result.error).toBe('发送失败');
    });
  });

  describe('sendWelcomeEmail', () => {
    it('应该成功发送欢迎邮件', async () => {
      mockTransporter.sendMail.mockResolvedValue({ messageId: 'test-id' });
      configService.get.mockReturnValue('"Test Sender" <noreply@test.com>');

      const result = await service.sendWelcomeEmail('test@example.com', '测试用户');

      expect(result.success).toBe(true);
      expect(result.isTestMode).toBe(false);
      expect(mockTransporter.sendMail).toHaveBeenCalledWith(
        expect.objectContaining({
          to: 'test@example.com',
          subject: '🎮 欢迎加入 Whale Town！',
          text: '欢迎 测试用户 加入 Whale Town 像素游戏世界！'
        })
      );
    });

    it('应该在发送失败时返回false', async () => {
      mockTransporter.sendMail.mockRejectedValue(new Error('发送失败'));

      const result = await service.sendWelcomeEmail('test@example.com', '测试用户');

      expect(result.success).toBe(false);
      expect(result.error).toBe('发送失败');
    });
  });

  describe('verifyConnection', () => {
    it('应该在连接成功时返回true', async () => {
      mockTransporter.verify.mockResolvedValue(true);

      const result = await service.verifyConnection();

      expect(result).toBe(true);
      expect(mockTransporter.verify).toHaveBeenCalled();
    });

    it('应该在连接失败时返回false', async () => {
      mockTransporter.verify.mockRejectedValue(new Error('连接失败'));

      const result = await service.verifyConnection();

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

  describe('邮件模板测试', () => {
    it('应该生成包含验证码的邮箱验证模板', async () => {
      const options: VerificationEmailOptions = {
        email: 'test@example.com',
        code: '123456',
        nickname: '测试用户',
        purpose: 'email_verification'
      };

      mockTransporter.sendMail.mockImplementation((mailOptions: any) => {
        expect(mailOptions.html).toContain('123456');
        expect(mailOptions.html).toContain('测试用户');
        expect(mailOptions.html).toContain('邮箱验证');
        expect(mailOptions.html).toContain('Whale Town');
        return Promise.resolve({ messageId: 'test-id' });
      });

      await service.sendVerificationCode(options);
    });

    it('应该生成包含验证码的密码重置模板', async () => {
      const options: VerificationEmailOptions = {
        email: 'test@example.com',
        code: '654321',
        nickname: '测试用户',
        purpose: 'password_reset'
      };

      mockTransporter.sendMail.mockImplementation((mailOptions: any) => {
        expect(mailOptions.html).toContain('654321');
        expect(mailOptions.html).toContain('测试用户');
        expect(mailOptions.html).toContain('密码重置');
        expect(mailOptions.html).toContain('🔐');
        return Promise.resolve({ messageId: 'test-id' });
      });

      await service.sendVerificationCode(options);
    });

    it('应该生成包含验证码的登录验证模板', async () => {
      const options: VerificationEmailOptions = {
        email: 'test@example.com',
        code: '789012',
        nickname: '测试用户',
        purpose: 'login_verification'
      };

      mockTransporter.sendMail.mockImplementation((mailOptions: any) => {
        expect(mailOptions.html).toContain('789012');
        expect(mailOptions.html).toContain('测试用户');
        expect(mailOptions.html).toContain('登录验证码');
        expect(mailOptions.html).toContain('您正在使用验证码登录');
        expect(mailOptions.html).toContain('🔐');
        return Promise.resolve({ messageId: 'test-id' });
      });

      await service.sendVerificationCode(options);
    });

    it('应该生成包含用户昵称的欢迎邮件模板', async () => {
      mockTransporter.sendMail.mockImplementation((mailOptions: any) => {
        expect(mailOptions.html).toContain('测试用户');
        expect(mailOptions.html).toContain('欢迎加入 Whale Town');
        expect(mailOptions.html).toContain('🎮');
        expect(mailOptions.html).toContain('建造与创造');
        expect(mailOptions.html).toContain('社交互动');
        expect(mailOptions.html).toContain('任务挑战');
        return Promise.resolve({ messageId: 'test-id' });
      });

      await service.sendWelcomeEmail('test@example.com', '测试用户');
    });

    it('应该在没有昵称时正确处理模板', async () => {
      const options: VerificationEmailOptions = {
        email: 'test@example.com',
        code: '123456',
        purpose: 'email_verification'
      };

      mockTransporter.sendMail.mockImplementation((mailOptions: any) => {
        expect(mailOptions.html).toContain('你好！');
        expect(mailOptions.html).not.toContain('你好 undefined');
        return Promise.resolve({ messageId: 'test-id' });
      });

      await service.sendVerificationCode(options);
    });
  });

  describe('错误处理测试', () => {
    it('应该正确处理网络错误', async () => {
      const emailOptions: EmailOptions = {
        to: 'test@example.com',
        subject: '测试邮件',
        html: '<p>测试内容</p>'
      };

      mockTransporter.sendMail.mockRejectedValue(new Error('ECONNREFUSED'));

      const result = await service.sendEmail(emailOptions);

      expect(result.success).toBe(false);
      expect(result.error).toBe('ECONNREFUSED');
    });

    it('应该正确处理认证错误', async () => {
      const emailOptions: EmailOptions = {
        to: 'test@example.com',
        subject: '测试邮件',
        html: '<p>测试内容</p>'
      };

      mockTransporter.sendMail.mockRejectedValue(new Error('Invalid login'));

      const result = await service.sendEmail(emailOptions);

      expect(result.success).toBe(false);
      expect(result.error).toBe('Invalid login');
    });

    it('应该正确处理连接验证错误', async () => {
      mockTransporter.verify.mockRejectedValue(new Error('Connection timeout'));

      const result = await service.verifyConnection();

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

  describe('配置测试', () => {
    it('应该使用默认配置值', () => {
      configService.get
        .mockReturnValueOnce(undefined) // EMAIL_HOST
        .mockReturnValueOnce(undefined) // EMAIL_PORT
        .mockReturnValueOnce(undefined) // EMAIL_SECURE
        .mockReturnValueOnce(undefined) // EMAIL_USER
        .mockReturnValueOnce(undefined); // EMAIL_PASS

      new EmailService(configService);

      expect(configService.get).toHaveBeenCalledWith('EMAIL_HOST', 'smtp.gmail.com');
      expect(configService.get).toHaveBeenCalledWith('EMAIL_PORT', 587);
      expect(configService.get).toHaveBeenCalledWith('EMAIL_SECURE', false);
    });

    it('应该使用自定义配置值', () => {
      configService.get
        .mockReturnValueOnce('smtp.163.com') // EMAIL_HOST
        .mockReturnValueOnce(25) // EMAIL_PORT
        .mockReturnValueOnce(true) // EMAIL_SECURE
        .mockReturnValueOnce('custom@163.com') // EMAIL_USER
        .mockReturnValueOnce('custompass'); // EMAIL_PASS

      new EmailService(configService);

      expect(mockedNodemailer.createTransport).toHaveBeenCalledWith({
        host: 'smtp.163.com',
        port: 25,
        secure: true,
        auth: {
          user: 'custom@163.com',
          pass: 'custompass',
        },
      });
    });
  });
});