// Mock AWS SDK before requiring modules
jest.mock('@aws-sdk/client-ses', () => ({
  SESClient: jest.fn().mockImplementation(() => ({
    send: jest.fn(),
  })),
  SendEmailCommand: jest.fn(),
}));

jest.mock('../../../../config/aws', () => ({
  getAWSConfig: jest.fn(() => ({ region: 'us-east-1' })),
}));

jest.mock('../../../../services/email/core/emailUtils', () => ({
  htmlToPlainText: jest.fn((html) => html.replace(/<[^>]*>/g, '')),
}));

// Clear singleton between tests
beforeEach(() => {
  jest.clearAllMocks();
  // Reset the singleton instance
  const EmailClient = require('../../../../services/email/core/EmailClient');
  EmailClient.instance = null;
});

describe('EmailClient', () => {
  const { SESClient, SendEmailCommand } = require('@aws-sdk/client-ses');
  const { getAWSConfig } = require('../../../../config/aws');

  describe('constructor', () => {
    it('should create a new instance', () => {
      const EmailClient = require('../../../../services/email/core/EmailClient');
      EmailClient.instance = null;
      const client = new EmailClient();
      expect(client).toBeDefined();
      expect(client.sesClient).toBeNull();
      expect(client.initialized).toBe(false);
    });

    it('should return existing instance (singleton pattern)', () => {
      const EmailClient = require('../../../../services/email/core/EmailClient');
      EmailClient.instance = null;
      const client1 = new EmailClient();
      const client2 = new EmailClient();
      expect(client1).toBe(client2);
    });
  });

  describe('initialize', () => {
    it('should initialize SES client with AWS config', async () => {
      const EmailClient = require('../../../../services/email/core/EmailClient');
      EmailClient.instance = null;
      const client = new EmailClient();

      await client.initialize();

      expect(getAWSConfig).toHaveBeenCalled();
      expect(SESClient).toHaveBeenCalledWith({ region: 'us-east-1' });
      expect(client.initialized).toBe(true);
    });

    it('should not re-initialize if already initialized', async () => {
      const EmailClient = require('../../../../services/email/core/EmailClient');
      EmailClient.instance = null;
      const client = new EmailClient();

      await client.initialize();
      await client.initialize();

      expect(SESClient).toHaveBeenCalledTimes(1);
    });

    it('should wait for existing initialization if in progress', async () => {
      const EmailClient = require('../../../../services/email/core/EmailClient');
      EmailClient.instance = null;
      const client = new EmailClient();

      // Start two initializations concurrently
      const [result1, result2] = await Promise.all([
        client.initialize(),
        client.initialize(),
      ]);

      expect(SESClient).toHaveBeenCalledTimes(1);
    });

    it('should throw error if AWS config fails', async () => {
      getAWSConfig.mockImplementationOnce(() => {
        throw new Error('AWS config error');
      });

      const EmailClient = require('../../../../services/email/core/EmailClient');
      EmailClient.instance = null;
      const client = new EmailClient();

      await expect(client.initialize()).rejects.toThrow('AWS config error');
    });
  });

  describe('sendEmail', () => {
    const originalEnv = process.env;

    beforeEach(() => {
      process.env = {
        ...originalEnv,
        EMAIL_ENABLED: 'true',
        SES_FROM_EMAIL: 'noreply@villageshare.app',
        SES_FROM_NAME: 'Village Share',
      };
    });

    afterEach(() => {
      process.env = originalEnv;
    });

    it('should return early if EMAIL_ENABLED is not true', async () => {
      process.env.EMAIL_ENABLED = 'false';

      const EmailClient = require('../../../../services/email/core/EmailClient');
      EmailClient.instance = null;
      const client = new EmailClient();

      const result = await client.sendEmail(
        'test@example.com',
        'Test Subject',
        '<p>Hello</p>'
      );

      expect(result).toEqual({ success: true, messageId: 'disabled' });
    });

    it('should return early if EMAIL_ENABLED is not set', async () => {
      delete process.env.EMAIL_ENABLED;

      const EmailClient = require('../../../../services/email/core/EmailClient');
      EmailClient.instance = null;
      const client = new EmailClient();

      const result = await client.sendEmail(
        'test@example.com',
        'Test Subject',
        '<p>Hello</p>'
      );

      expect(result).toEqual({ success: true, messageId: 'disabled' });
    });

    it('should send email with correct parameters', async () => {
      const mockSend = jest.fn().mockResolvedValue({ MessageId: 'msg-123' });
      SESClient.mockImplementation(() => ({ send: mockSend }));

      const EmailClient = require('../../../../services/email/core/EmailClient');
      EmailClient.instance = null;
      const client = new EmailClient();

      const result = await client.sendEmail(
        'test@example.com',
        'Test Subject',
        '<p>Hello World</p>'
      );

      expect(SendEmailCommand).toHaveBeenCalledWith({
        Source: 'Village Share <noreply@villageshare.app>',
        Destination: {
          ToAddresses: ['test@example.com'],
        },
        Message: {
          Subject: {
            Data: 'Test Subject',
            Charset: 'UTF-8',
          },
          Body: {
            Html: {
              Data: '<p>Hello World</p>',
              Charset: 'UTF-8',
            },
            Text: {
              Data: expect.any(String),
              Charset: 'UTF-8',
            },
          },
        },
      });

      expect(result).toEqual({ success: true, messageId: 'msg-123' });
    });

    it('should send to multiple recipients', async () => {
      const mockSend = jest.fn().mockResolvedValue({ MessageId: 'msg-456' });
      SESClient.mockImplementation(() => ({ send: mockSend }));

      const EmailClient = require('../../../../services/email/core/EmailClient');
      EmailClient.instance = null;
      const client = new EmailClient();

      await client.sendEmail(
        ['user1@example.com', 'user2@example.com'],
        'Test Subject',
        '<p>Hello</p>'
      );

      expect(SendEmailCommand).toHaveBeenCalledWith(
        expect.objectContaining({
          Destination: {
            ToAddresses: ['user1@example.com', 'user2@example.com'],
          },
        })
      );
    });

    it('should use provided text content', async () => {
      const mockSend = jest.fn().mockResolvedValue({ MessageId: 'msg-789' });
      SESClient.mockImplementation(() => ({ send: mockSend }));

      const EmailClient = require('../../../../services/email/core/EmailClient');
      EmailClient.instance = null;
      const client = new EmailClient();

      await client.sendEmail(
        'test@example.com',
        'Test Subject',
        '<p>Hello</p>',
        'Custom plain text'
      );

      expect(SendEmailCommand).toHaveBeenCalledWith(
        expect.objectContaining({
          Message: expect.objectContaining({
            Body: expect.objectContaining({
              Text: {
                Data: 'Custom plain text',
                Charset: 'UTF-8',
              },
            }),
          }),
        })
      );
    });

    it('should add reply-to address if configured', async () => {
      process.env.SES_REPLY_TO_EMAIL = 'support@villageshare.app';
      const mockSend = jest.fn().mockResolvedValue({ MessageId: 'msg-000' });
      SESClient.mockImplementation(() => ({ send: mockSend }));

      const EmailClient = require('../../../../services/email/core/EmailClient');
      EmailClient.instance = null;
      const client = new EmailClient();

      await client.sendEmail(
        'test@example.com',
        'Test Subject',
        '<p>Hello</p>'
      );

      expect(SendEmailCommand).toHaveBeenCalledWith(
        expect.objectContaining({
          ReplyToAddresses: ['support@villageshare.app'],
        })
      );
    });

    it('should return error if send fails', async () => {
      const mockSend = jest.fn().mockRejectedValue(new Error('SES send failed'));
      SESClient.mockImplementation(() => ({ send: mockSend }));

      const EmailClient = require('../../../../services/email/core/EmailClient');
      EmailClient.instance = null;
      const client = new EmailClient();

      const result = await client.sendEmail(
        'test@example.com',
        'Test Subject',
        '<p>Hello</p>'
      );

      expect(result).toEqual({ success: false, error: 'SES send failed' });
    });

    it('should auto-initialize if not initialized', async () => {
      const mockSend = jest.fn().mockResolvedValue({ MessageId: 'msg-auto' });
      SESClient.mockImplementation(() => ({ send: mockSend }));

      const EmailClient = require('../../../../services/email/core/EmailClient');
      EmailClient.instance = null;
      const client = new EmailClient();

      expect(client.initialized).toBe(false);

      await client.sendEmail(
        'test@example.com',
        'Test Subject',
        '<p>Hello</p>'
      );

      expect(client.initialized).toBe(true);
    });
  });
});
