import { describe, it, expect, beforeAll, beforeEach, afterAll, vi } from '@jest/globals';
import { EmailService } from '@shieldai/shared-notifications';
import type { EmailNotification } from '@shieldai/shared-notifications';

// Mock Resend
vi.mock('resend', () => {
  return {
    Resend: vi.fn().mockImplementation(() => ({
      emails: {
        send: vi.fn().mockResolvedValue({
          data: { id: 'resend-mock-123' },
          error: undefined,
        }),
      },
    })),
  };
});

describe('EmailService Integration Tests', () => {
  let emailService: EmailService;
  let mockResend: any;

  beforeAll(() => {
    emailService = EmailService.getInstance();
    mockResend = (require('resend').Resend as any).mock.instances[0];
  });

  beforeEach(() => {
    vi.clearAllMocks();
  });

  describe('send', () => {
    it('should successfully send email notification', async () => {
      mockResend.emails.send.mockResolvedValueOnce({
        data: { id: 'test-email-123' },
        error: undefined,
      });

      const notification: EmailNotification = {
        channel: 'email',
        to: 'test@example.com',
        subject: 'Test Subject',
        htmlBody: '<h1>Test</h1>',
        textBody: 'Test',
      };

      const result = await emailService.send(notification);

      expect(result.status).toBe('sent');
      expect(result.channel).toBe('email');
      expect(result.externalId).toBe('test-email-123');
      expect(result.notificationId).toContain('email-');
      expect(result.deliveredAt).toBeInstanceOf(Date);
    });

    it('should handle invalid email format', async () => {
      const notification: EmailNotification = {
        channel: 'email',
        to: 'invalid-email',
        subject: 'Test',
        htmlBody: '<p>Test</p>',
      };

      const result = await emailService.send(notification);

      expect(result.status).toBe('failed');
      expect(result.error).toContain('Invalid email format');
    });

    it('should handle Resend API error', async () => {
      mockResend.emails.send.mockResolvedValueOnce({
        data: { id: 'error-email-456' },
        error: { message: 'API rate limit exceeded' },
      });

      const notification: EmailNotification = {
        channel: 'email',
        to: 'test@example.com',
        subject: 'Test',
        htmlBody: '<p>Test</p>',
      };

      const result = await emailService.send(notification);

      expect(result.status).toBe('failed');
      expect(result.error).toBe('API rate limit exceeded');
    });

    it('should handle network error', async () => {
      mockResend.emails.send.mockRejectedValueOnce(
        new Error('Network timeout')
      );

      const notification: EmailNotification = {
        channel: 'email',
        to: 'test@example.com',
        subject: 'Test',
        htmlBody: '<p>Test</p>',
      };

      const result = await emailService.send(notification);

      expect(result.status).toBe('failed');
      expect(result.error).toBe('Network timeout');
    });

    it('should include metadata in email', async () => {
      mockResend.emails.send.mockResolvedValueOnce({
        data: { id: 'meta-email-789' },
        error: undefined,
      });

      const notification: EmailNotification = {
        channel: 'email',
        to: 'test@example.com',
        subject: 'Test',
        htmlBody: '<p>Test</p>',
        metadata: { userId: 'user-123', campaign: 'welcome' },
      };

      await emailService.send(notification);

      expect(mockResend.emails.send).toHaveBeenCalledWith(
        expect.objectContaining({
          metadata: { userId: 'user-123', campaign: 'welcome' },
        })
      );
    });

    it('should include attachments in email', async () => {
      mockResend.emails.send.mockResolvedValueOnce({
        data: { id: 'attach-email-101' },
        error: undefined,
      });

      const notification: EmailNotification = {
        channel: 'email',
        to: 'test@example.com',
        subject: 'Test',
        htmlBody: '<p>Test</p>',
        attachments: [
          {
            filename: 'report.pdf',
            content: Buffer.from('PDF content'),
            mimeType: 'application/pdf',
          },
        ],
      };

      await emailService.send(notification);

      expect(mockResend.emails.send).toHaveBeenCalledWith(
        expect.objectContaining({
          attachments: expect.arrayContaining([
            expect.objectContaining({
              filename: 'report.pdf',
              contentType: 'application/pdf',
            }),
          ]),
        })
      );
    });

    it('should use default from address when not provided', async () => {
      mockResend.emails.send.mockResolvedValueOnce({
        data: { id: 'default-from-202' },
        error: undefined,
      });

      const notification: EmailNotification = {
        channel: 'email',
        to: 'test@example.com',
        subject: 'Test',
        htmlBody: '<p>Test</p>',
      };

      await emailService.send(notification);

      expect(mockResend.emails.send).toHaveBeenCalledWith(
        expect.objectContaining({
          from: 'ShieldAI <noreply@shieldai.com>',
        })
      );
    });

    it('should use custom from address when provided', async () => {
      mockResend.emails.send.mockResolvedValueOnce({
        data: { id: 'custom-from-303' },
        error: undefined,
      });

      const notification: EmailNotification = {
        channel: 'email',
        to: 'test@example.com',
        from: 'custom@shieldai.com',
        subject: 'Test',
        htmlBody: '<p>Test</p>',
      };

      await emailService.send(notification);

      expect(mockResend.emails.send).toHaveBeenCalledWith(
        expect.objectContaining({
          from: 'custom@shieldai.com',
        })
      );
    });

    it('should handle both html and text body', async () => {
      mockResend.emails.send.mockResolvedValueOnce({
        data: { id: 'both-body-404' },
        error: undefined,
      });

      const notification: EmailNotification = {
        channel: 'email',
        to: 'test@example.com',
        subject: 'Test',
        htmlBody: '<h1>HTML</h1>',
        textBody: 'Plain text',
      };

      await emailService.send(notification);

      expect(mockResend.emails.send).toHaveBeenCalledWith(
        expect.objectContaining({
          html: '<h1>HTML</h1>',
          text: 'Plain text',
        })
      );
    });

    it('should enforce email rate limiting', async () => {
      // Set rate limit to 2 for testing
      process.env.EMAIL_RATE_LIMIT = '2';
      
      // Clear the service instance to pick up new config
      vi.clearAllMocks();
      emailService = EmailService.getInstance();

      const notification: EmailNotification = {
        channel: 'email',
        to: 'rate-test@example.com',
        subject: 'Test',
        htmlBody: '<p>Test</p>',
      };

      // First two should succeed
      const result1 = await emailService.send(notification);
      const result2 = await emailService.send(notification);

      expect(result1.status).toBe('sent');
      expect(result2.status).toBe('sent');

      // Third should throw due to rate limit
      await expect(emailService.send(notification)).rejects.toThrow(
        'Email rate limit exceeded'
      );
    });
  });

  describe('sendWithTemplate', () => {
    beforeEach(() => {
      vi.clearAllMocks();
      emailService = EmailService.getInstance();
    });

    it('should send email with resolved template', async () => {
      mockResend.emails.send.mockResolvedValueOnce({
        data: { id: 'template-email-505' },
        error: undefined,
      });

      const result = await emailService.sendWithTemplate('test@example.com', {
        templateId: 'welcome-email',
        locale: 'en',
        variables: { name: 'John' },
      });

      expect(result.status).toBe('sent');
      expect(result.channel).toBe('email');
    });

    it('should handle missing template', async () => {
      mockResend.emails.send.mockResolvedValueOnce({
        data: { id: 'missing-template-606' },
        error: undefined,
      });

      const result = await emailService.sendWithTemplate('test@example.com', {
        templateId: 'non-existent-template',
        locale: 'en',
        variables: {},
      });

      expect(result.status).toBe('failed');
      expect(result.error).toContain('Template not found');
    });

    it('should handle template channel mismatch', async () => {
      mockResend.emails.send.mockResolvedValueOnce({
        data: { id: 'channel-mismatch-707' },
        error: undefined,
      });

      const result = await emailService.sendWithTemplate('test@example.com', {
        templateId: 'sms-template',
        locale: 'en',
        variables: {},
        channel: 'email',
      });

      expect(result.status).toBe('failed');
      expect(result.error).toContain('is for channel');
    });
  });

  describe('sendBatch', () => {
    beforeEach(() => {
      vi.clearAllMocks();
      emailService = EmailService.getInstance();
    });

    it('should send multiple emails successfully', async () => {
      mockResend.emails.send
        .mockResolvedValueOnce({ data: { id: 'batch-1' }, error: undefined })
        .mockResolvedValueOnce({ data: { id: 'batch-2' }, error: undefined })
        .mockResolvedValueOnce({ data: { id: 'batch-3' }, error: undefined });

      const notifications: EmailNotification[] = [
        {
          channel: 'email',
          to: 'user1@example.com',
          subject: 'Batch 1',
          htmlBody: '<p>Test 1</p>',
        },
        {
          channel: 'email',
          to: 'user2@example.com',
          subject: 'Batch 2',
          htmlBody: '<p>Test 2</p>',
        },
        {
          channel: 'email',
          to: 'user3@example.com',
          subject: 'Batch 3',
          htmlBody: '<p>Test 3</p>',
        },
      ];

      const results = await emailService.sendBatch(notifications);

      expect(results).toHaveLength(3);
      expect(results.every(r => r.status === 'sent')).toBe(true);
      expect(results.map(r => r.externalId)).toEqual(['batch-1', 'batch-2', 'batch-3']);
    });

    it('should handle partial failures in batch', async () => {
      mockResend.emails.send
        .mockResolvedValueOnce({ data: { id: 'partial-1' }, error: undefined })
        .mockResolvedValueOnce({ data: { id: 'partial-2' }, error: undefined });

      const notifications: EmailNotification[] = [
        {
          channel: 'email',
          to: 'valid@example.com',
          subject: 'Valid',
          htmlBody: '<p>Valid</p>',
        },
        {
          channel: 'email',
          to: 'invalid-email',
          subject: 'Invalid',
          htmlBody: '<p>Invalid</p>',
        },
      ];

      const results = await emailService.sendBatch(notifications);

      expect(results).toHaveLength(2);
      expect(results[0].status).toBe('sent');
      expect(results[1].status).toBe('failed');
    });
  });

  describe('getRateLimitStatus', () => {
    beforeEach(() => {
      vi.clearAllMocks();
      emailService = EmailService.getInstance();
    });

    it('should return rate limit status', () => {
      const status = emailService.getRateLimitStatus();

      expect(status).toHaveProperty('remaining');
      expect(status).toHaveProperty('limit');
      expect(status.limit).toBeGreaterThan(0);
      expect(status.remaining).toBeLessThanOrEqual(status.limit);
    });
  });
});
