import { describe, it, expect, beforeAll, beforeEach, vi } from '@jest/globals';
import { PushService } from '@shieldai/shared-notifications';
import type { PushNotification } from '@shieldai/shared-notifications';
import * as admin from 'firebase-admin';

// Mock firebase-admin
vi.mock('firebase-admin', () => {
  const mockMessaging = {
    send: vi.fn().mockResolvedValue('push-token-123'),
  };

  const mockCredential = {
    cert: vi.fn().mockReturnValue({
      projectId: 'test-project',
      clientEmail: 'test@test-project.iam.gserviceaccount.com',
      privateKey: 'test-key',
    }),
  };

  const mockApp = {
    options: {},
  };

  return {
    default: {
      initializeApp: vi.fn().mockReturnValue(mockApp),
      credential: {
        cert: mockCredential,
      },
      messaging: vi.fn().mockReturnValue(mockMessaging),
    },
    messaging: vi.fn().mockReturnValue(mockMessaging),
    app: {
      App: Object,
    },
    credential: {
      cert: mockCredential,
    },
  };
});

describe('PushService Integration Tests', () => {
  let pushService: PushService;
  let mockMessaging: any;

  beforeAll(() => {
    pushService = PushService.getInstance();
    mockMessaging = (require('firebase-admin').messaging as any).mock.instances[0];
  });

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

  describe('send', () => {
    it('should successfully send push notification', async () => {
      const mockResponse = 'fcm-message-id-123';
      (require('firebase-admin').messaging as any).mock.instances[0].send.mockResolvedValueOnce(mockResponse);

      const notification: PushNotification = {
        channel: 'push',
        userId: 'user-device-token-123',
        title: 'Test Title',
        body: 'Test Body',
      };

      const result = await pushService.send(notification);

      expect(result.status).toBe('sent');
      expect(result.channel).toBe('push');
      expect(result.externalId).toBe(mockResponse);
      expect(result.notificationId).toContain('push-');
      expect(result.deliveredAt).toBeInstanceOf(Date);
    });

    it('should include notification data', async () => {
      const mockResponse = 'data-push-456';
      (require('firebase-admin').messaging as any).mock.instances[0].send.mockResolvedValueOnce(mockResponse);

      const notification: PushNotification = {
        channel: 'push',
        userId: 'user-device-token-456',
        title: 'Test',
        body: 'Test',
        data: { key1: 'value1', key2: 'value2' },
      };

      await pushService.send(notification);

      expect((require('firebase-admin').messaging as any).mock.instances[0].send).toHaveBeenCalledWith(
        expect.objectContaining({
          data: expect.objectContaining({
            key1: 'value1',
            key2: 'value2',
          }),
        })
      );
    });

    it('should include badge and sound settings', async () => {
      const mockResponse = 'apns-push-789';
      (require('firebase-admin').messaging as any).mock.instances[0].send.mockResolvedValueOnce(mockResponse);

      const notification: PushNotification = {
        channel: 'push',
        userId: 'user-device-token-789',
        title: 'Test',
        body: 'Test',
        badge: 5,
        sound: 'custom-sound.caf',
        category: 'ALERT_CATEGORY',
      };

      await pushService.send(notification);

      expect((require('firebase-admin').messaging as any).mock.instances[0].send).toHaveBeenCalledWith(
        expect.objectContaining({
          apns: expect.objectContaining({
            payload: expect.objectContaining({
              aps: expect.objectContaining({
                badge: 5,
                sound: 'custom-sound.caf',
                category: 'ALERT_CATEGORY',
              }),
            }),
          }),
        })
      );
    });

    it('should handle undefined data gracefully', async () => {
      const mockResponse = 'no-data-push-101';
      (require('firebase-admin').messaging as any).mock.instances[0].send.mockResolvedValueOnce(mockResponse);

      const notification: PushNotification = {
        channel: 'push',
        userId: 'user-device-token-101',
        title: 'Test',
        body: 'Test',
      };

      await pushService.send(notification);

      expect((require('firebase-admin').messaging as any).mock.instances[0].send).toHaveBeenCalledWith(
        expect.objectContaining({
          data: undefined,
        })
      );
    });

    it('should handle FCM API error', async () => {
      (require('firebase-admin').messaging as any).mock.instances[0].send.mockRejectedValueOnce(
        new Error('Invalid registration token')
      );

      const notification: PushNotification = {
        channel: 'push',
        userId: 'invalid-token',
        title: 'Test',
        body: 'Test',
      };

      const result = await pushService.send(notification);

      expect(result.status).toBe('failed');
      expect(result.error).toBe('Invalid registration token');
    });

    it('should enforce push rate limiting', async () => {
      process.env.PUSH_RATE_LIMIT = '2';
      vi.clearAllMocks();
      pushService = PushService.getInstance();

      const notification: PushNotification = {
        channel: 'push',
        userId: 'rate-test-user',
        title: 'Test',
        body: 'Test',
      };

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

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

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

    it('should handle network timeout', async () => {
      (require('firebase-admin').messaging as any).mock.instances[0].send.mockRejectedValueOnce(
        new Error('Network timeout')
      );

      const notification: PushNotification = {
        channel: 'push',
        userId: 'timeout-user',
        title: 'Test',
        body: 'Test',
      };

      const result = await pushService.send(notification);

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

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

    it('should send multiple push notifications successfully', async () => {
      (require('firebase-admin').messaging as any).mock.instances[0].send
        .mockResolvedValueOnce('batch-push-1')
        .mockResolvedValueOnce('batch-push-2')
        .mockResolvedValueOnce('batch-push-3');

      const notifications: PushNotification[] = [
        {
          channel: 'push',
          userId: 'user-token-1',
          title: 'Batch 1',
          body: 'Test 1',
        },
        {
          channel: 'push',
          userId: 'user-token-2',
          title: 'Batch 2',
          body: 'Test 2',
        },
        {
          channel: 'push',
          userId: 'user-token-3',
          title: 'Batch 3',
          body: 'Test 3',
        },
      ];

      const results = await pushService.sendBatch(notifications);

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

    it('should handle partial failures in batch', async () => {
      (require('firebase-admin').messaging as any).mock.instances[0].send
        .mockResolvedValueOnce('partial-push-1')
        .mockRejectedValueOnce(new Error('Invalid token'));

      const notifications: PushNotification[] = [
        {
          channel: 'push',
          userId: 'valid-token',
          title: 'Valid',
          body: 'Valid',
        },
        {
          channel: 'push',
          userId: 'invalid-token',
          title: 'Invalid',
          body: 'Invalid',
        },
      ];

      const results = await pushService.sendBatch(notifications);

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

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

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

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

  describe('APNs configuration', () => {
    beforeEach(() => {
      vi.clearAllMocks();
      pushService = PushService.getInstance();
    });

    it('should configure APNs payload correctly', async () => {
      const mockResponse = 'apns-config-test';
      (require('firebase-admin').messaging as any).mock.instances[0].send.mockResolvedValueOnce(mockResponse);

      const notification: PushNotification = {
        channel: 'push',
        userId: 'ios-device-token',
        title: 'iOS Test',
        body: 'iOS Body',
        badge: 10,
        sound: 'notification.caf',
        category: 'MESSAGE_CATEGORY',
      };

      await pushService.send(notification);

      const callArg = (require('firebase-admin').messaging as any).mock.instances[0].send.mock.calls[0][0];
      
      expect(call_arg.apns).toEqual(
        expect.objectContaining({
          payload: expect.objectContaining({
            aps: expect.objectContaining({
              badge: 10,
              sound: 'notification.caf',
              category: 'MESSAGE_CATEGORY',
            }),
          }),
        })
      );
    });
  });
});
