import { describe, it, expect, beforeAll, beforeEach, vi } from '@jest/globals';
import { NotificationService } from '@shieldai/shared-notifications';
import { EmailService } from '@shieldai/shared-notifications';
import { SMSService } from '@shieldai/shared-notifications';
import { PushService } from '@shieldai/shared-notifications';
import type { Notification, DeduplicationKey } from '@shieldai/shared-notifications';

// Mock individual services
vi.mock('@shieldai/shared-notifications', async () => {
  const actual = await vi.importActual('@shieldai/shared-notifications');
  
  return {
    ...(actual as object),
    EmailService: {
      getInstance: vi.fn(() => ({
        send: vi.fn(async (notification: any) => ({
          notificationId: `email-${Date.now()}`,
          channel: 'email',
          status: 'sent',
          externalId: 'resend-mock-id',
        })),
      })),
    },
    SMSService: {
      getInstance: vi.fn(() => ({
        send: vi.fn(async (notification: any) => ({
          notificationId: `sms-${Date.now()}`,
          channel: 'sms',
          status: 'sent',
          externalId: 'twilio-mock-id',
        })),
      })),
    },
    PushService: {
      getInstance: vi.fn(() => ({
        send: vi.fn(async (notification: any) => ({
          notificationId: `push-${Date.now()}`,
          channel: 'push',
          status: 'sent',
          externalId: 'fcm-mock-id',
        })),
      })),
    },
  };
});

describe('NotificationService Integration Tests', () => {
  let notificationService: NotificationService;

  beforeAll(() => {
    notificationService = NotificationService.getInstance();
  });

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

  describe('send', () => {
    it('should send email notification', async () => {
      const notification: Notification = {
        channel: 'email',
        to: 'test@example.com',
        subject: 'Test',
        htmlBody: '<p>Test</p>',
      };

      const result = await notificationService.send(notification);

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

    it('should send SMS notification', async () => {
      const notification: Notification = {
        channel: 'sms',
        to: '+14155552672',
        body: 'Test SMS',
      };

      const result = await notificationService.send(notification);

      expect(result.channel).toBe('sms');
      expect(result.status).toBe('sent');
      expect(result.notificationId).toContain('sms-');
    });

    it('should send push notification', async () => {
      const notification: Notification = {
        channel: 'push',
        userId: 'user-device-token',
        title: 'Test Title',
        body: 'Test Body',
      };

      const result = await notificationService.send(notification);

      expect(result.channel).toBe('push');
      expect(result.status).toBe('sent');
      expect(result.notificationId).toContain('push-');
    });

    it('should throw for unknown channel', async () => {
      const notification = {
        channel: 'unknown' as any,
        to: 'test@example.com',
      } as Notification;

      await expect(notificationService.send(notification)).rejects.toThrow(
        'Unknown notification channel'
      );
    });
  });

  describe('sendWithDeduplication', () => {
    beforeEach(() => {
      vi.clearAllMocks();
      notificationService = NotificationService.getInstance();
    });

    it('should allow first notification', async () => {
      const notification: Notification = {
        channel: 'email',
        to: 'test@example.com',
        subject: 'Test',
        htmlBody: '<p>Test</p>',
      };

      const dedupKey: DeduplicationKey = {
        userId: 'user-123',
        templateId: 'welcome-email',
        key: 'initial',
      };

      const result = await notificationService.sendWithDeduplication(
        notification,
        dedupKey
      );

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

    it('should mark duplicate as pending', async () => {
      const notification: Notification = {
        channel: 'email',
        to: 'test@example.com',
        subject: 'Test',
        htmlBody: '<p>Test</p>',
      };

      const dedupKey: DeduplicationKey = {
        userId: 'user-456',
        templateId: 'alert-email',
        key: 'same-key',
      };

      // First call
      await notificationService.sendWithDeduplication(notification, dedupKey);

      // Second call with same key - should be pending
      const result = await notificationService.sendWithDeduplication(
        { ...notification, subject: 'Updated' },
        dedupKey
      );

      expect(result.status).toBe('pending');
      expect(result.error).toContain('Duplicate notification');
    });

    it('should use custom deduplication window', async () => {
      const notification: Notification = {
        channel: 'sms',
        to: '+14155552672',
        body: 'Test',
      };

      const dedupKey: DeduplicationKey = {
        userId: 'user-789',
        templateId: 'sms-template',
        key: 'custom-window',
        windowSeconds: 60,
      };

      const result = await notificationService.sendWithDeduplication(
        notification,
        dedupKey
      );

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

  describe('setPreference and getPreference', () => {
    beforeEach(() => {
      vi.clearAllMocks();
      notificationService = NotificationService.getInstance();
    });

    it('should set notification preference', async () => {
      const userId = 'user-pref-123';
      const channel = 'email' as const;
      const enabled = true;
      const categories = ['alerts', 'updates'];

      const preference = await notificationService.setPreference(
        userId,
        channel,
        enabled,
        categories
      );

      expect(preference.userId).toBe(userId);
      expect(preference.channel).toBe(channel);
      expect(preference.enabled).toBe(enabled);
      expect(preference.categories).toEqual(categories);
    });

    it('should get notification preference', async () => {
      const userId = 'user-pref-456';
      const channel = 'push' as const;

      await notificationService.setPreference(userId, channel, true, ['notifications']);

      const preference = await notificationService.getPreference(userId, channel);

      expect(preference).not.toBeNull();
      expect(preference?.userId).toBe(userId);
      expect(preference?.enabled).toBe(true);
    });

    it('should return null for non-existent preference', async () => {
      const preference = await notificationService.getPreference(
        'non-existent-user',
        'email'
      );

      expect(preference).toBeNull();
    });
  });

  describe('shouldSend', () => {
    beforeEach(() => {
      vi.clearAllMocks();
      notificationService = NotificationService.getInstance();
    });

    it('should allow send when no preference set', async () => {
      const result = await notificationService.shouldSend(
        'new-user',
        'email',
        'alerts'
      );

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

    it('should allow send when preference enabled', async () => {
      await notificationService.setPreference('enabled-user', 'sms', true, ['marketing']);

      const result = await notificationService.shouldSend(
        'enabled-user',
        'sms',
        'marketing'
      );

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

    it('should block send when preference disabled', async () => {
      await notificationService.setPreference('disabled-user', 'push', false, ['alerts']);

      const result = await notificationService.shouldSend(
        'disabled-user',
        'push',
        'alerts'
      );

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

    it('should block send when category not in allowed list', async () => {
      await notificationService.setPreference(
        'category-user',
        'email',
        true,
        ['alerts', 'updates']
      );

      const result = await notificationService.shouldSend(
        'category-user',
        'email',
        'marketing'
      );

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

    it('should allow send when categories list is empty', async () => {
      await notificationService.setPreference('empty-cats-user', 'sms', true, []);

      const result = await notificationService.shouldSend(
        'empty-cats-user',
        'sms',
        'any-category'
      );

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

  describe('sendWithPreferences', () => {
    beforeEach(() => {
      vi.clearAllMocks();
      notificationService = NotificationService.getInstance();
    });

    it('should send when preference allows', async () => {
      await notificationService.setPreference(
        'pref-user-1',
        'email',
        true,
        ['alerts']
      );

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

      const result = await notificationService.sendWithPreferences(
        notification,
        'alerts'
      );

      expect(result?.status).toBe('sent');
    });

    it('should return pending when preference disabled', async () => {
      await notificationService.setPreference(
        'pref-user-2',
        'sms',
        false,
        ['marketing']
      );

      const notification: Notification = {
        channel: 'sms',
        to: '+14155552672',
        body: 'Test',
      };

      const result = await notificationService.sendWithPreferences(
        notification,
        'marketing'
      );

      expect(result?.status).toBe('pending');
      expect(result?.error).toContain('Notification disabled');
    });
  });

  describe('checkRateLimit', () => {
    beforeEach(async () => {
      vi.clearAllMocks();
      notificationService = NotificationService.getInstance();
    });

    it('should allow within rate limit', async () => {
      const result = await notificationService.checkRateLimit(
        'rate-user-1',
        'email'
      );

      expect(result.allowed).toBe(true);
      expect(result.limit).toBeGreaterThan(0);
      expect(result.remaining).toBeLessThan(result.limit);
    });

    it('should track multiple identifiers independently', async () => {
      await notificationService.checkRateLimit('rate-user-2a', 'email');
      await notificationService.checkRateLimit('rate-user-2b', 'email');

      const resultA = await notificationService.checkRateLimit('rate-user-2a', 'email');
      const resultB = await notificationService.checkRateLimit('rate-user-2b', 'email');

      expect(resultA.currentCount).toBe(2);
      expect(resultB.currentCount).toBe(2);
    });

    it('should track different channels independently', async () => {
      await notificationService.checkRateLimit('rate-user-3', 'email');
      await notificationService.checkRateLimit('rate-user-3', 'sms');

      const emailResult = await notificationService.checkRateLimit('rate-user-3', 'email');
      const smsResult = await notificationService.checkRateLimit('rate-user-3', 'sms');

      expect(emailResult.currentCount).toBe(2);
      expect(smsResult.currentCount).toBe(2);
    });

    it('should use custom limit', async () => {
      const result = await notificationService.checkRateLimit(
        'rate-user-4',
        'email',
        5
      );

      expect(result.limit).toBe(5);
    });

    it('should use custom window', async () => {
      const result = await notificationService.checkRateLimit(
        'rate-user-5',
        'email',
        10,
        120
      );

      expect(result.resetInSeconds).toBeLessThanOrEqual(120);
    });
  });

  describe('deduplicateNotification', () => {
    beforeEach(async () => {
      vi.clearAllMocks();
      notificationService = NotificationService.getInstance();
    });

    it('should return true for first notification', async () => {
      const wasSet = await notificationService.deduplicateNotification({
        userId: 'dedup-user-1',
        templateId: 'test-template',
        key: 'unique-key',
      });

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

    it('should return false for duplicate', async () => {
      await notificationService.deduplicateNotification({
        userId: 'dedup-user-2',
        templateId: 'test-template',
        key: 'duplicate-key',
      });

      const wasSet = await notificationService.deduplicateNotification({
        userId: 'dedup-user-2',
        templateId: 'test-template',
        key: 'duplicate-key',
      });

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

    it('should use custom window', async () => {
      const wasSet = await notificationService.deduplicateNotification(
        {
          userId: 'dedup-user-3',
          templateId: 'test-template',
          key: 'custom-window-key',
        },
        60
      );

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

    it('should use windowSeconds from key', async () => {
      const wasSet = await notificationService.deduplicateNotification({
        userId: 'dedup-user-4',
        templateId: 'test-template',
        key: 'key-window',
        windowSeconds: 120,
      });

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

  describe('getRateLimitConfig', () => {
    beforeEach(() => {
      vi.clearAllMocks();
      notificationService = NotificationService.getInstance();
    });

    it('should return rate limit configuration', () => {
      const config = notificationService.getRateLimitConfig();

      expect(config).toHaveProperty('emailPerMinute');
      expect(config).toHaveProperty('smsPerMinute');
      expect(config).toHaveProperty('pushPerMinute');
      expect(config).toHaveProperty('windowSeconds');
      expect(config.emailPerMinute).toBeGreaterThan(0);
      expect(config.smsPerMinute).toBeGreaterThan(0);
      expect(config.pushPerMinute).toBeGreaterThan(0);
    });
  });

  describe('getTemplateService', () => {
    beforeEach(() => {
      vi.clearAllMocks();
      notificationService = NotificationService.getInstance();
    });

    it('should return template service instance', () => {
      const templateService = notificationService.getTemplateService();

      expect(templateService).toBeDefined();
    });
  });
});
