const request = require('supertest');
const app = require('../server-improved');

// Mock the bot functions to avoid actual WhatsApp calls during testing
jest.mock('../bot', () => ({
  startBot: jest.fn(),
  sendTextMessage: jest.fn().mockResolvedValue(true),
  sendMediaMessage: jest.fn().mockResolvedValue(true)
}));

// Mock cron to avoid starting cron jobs during testing
jest.mock('../cron', () => {});

// Mock winston logger to avoid file operations during testing
jest.mock('winston', () => {
  const mockLogger = {
    info: jest.fn(),
    error: jest.fn(),
    warn: jest.fn(),
    debug: jest.fn()
  };

  return {
    createLogger: jest.fn(() => mockLogger),
    format: {
      combine: jest.fn(),
      timestamp: jest.fn(),
      errors: jest.fn(),
      json: jest.fn(),
      simple: jest.fn()
    },
    transports: {
      File: jest.fn(),
      Console: jest.fn()
    }
  };
});

describe('Pamsika WhatsApp Bot API', () => {
  const validApiKey = 'test-api-key';
  const validPhone = '265991234567';
  const validMessage = 'Test message';
  const validMediaUrl = 'https://example.com/image.jpg';
  const validOrderId = 'ORD123456';
  const validStatus = 'Paid';

  beforeAll(() => {
    // Set test environment variables
    process.env.API_KEY = validApiKey;
    process.env.NODE_ENV = 'test';
    process.env.LOG_LEVEL = 'error'; // Reduce logging noise during tests
  });

  afterAll(() => {
    // Clean up environment variables
    delete process.env.API_KEY;
    delete process.env.NODE_ENV;
    delete process.env.LOG_LEVEL;
  });

  describe('Health Check Endpoint', () => {
    test('GET /health should return health status', async () => {
      const response = await request(app)
        .get('/health')
        .expect(200);

      expect(response.body).toMatchObject({
        status: 'healthy',
        timestamp: expect.any(String),
        uptime: expect.any(Number),
        memory: {
          used: expect.any(Number),
          total: expect.any(Number)
        },
        version: expect.any(String),
        environment: 'test'
      });
    });

    test('GET /health should handle rate limiting', async () => {
      // Make multiple requests to test rate limiting
      const promises = Array(35).fill().map(() =>
        request(app).get('/health')
      );

      const responses = await Promise.all(promises);
      const rateLimitedResponses = responses.filter(res => res.status === 429);

      expect(rateLimitedResponses.length).toBeGreaterThan(0);
    });
  });

  describe('Authentication', () => {
    test('POST /send-message should reject requests without API key', async () => {
      const response = await request(app)
        .post('/send-message')
        .send({
          phone: validPhone,
          message: validMessage
        })
        .expect(401);

      expect(response.body).toMatchObject({
        success: false,
        error: {
          code: 401,
          message: 'API key required'
        }
      });
    });

    test('POST /send-message should reject requests with invalid API key', async () => {
      const response = await request(app)
        .post('/send-message')
        .set('x-api-key', 'invalid-key')
        .send({
          phone: validPhone,
          message: validMessage
        })
        .expect(401);

      expect(response.body).toMatchObject({
        success: false,
        error: {
          code: 401,
          message: 'Invalid API key'
        }
      });
    });

    test('Should accept API key in Authorization header', async () => {
      const response = await request(app)
        .post('/send-message')
        .set('Authorization', `Bearer ${validApiKey}`)
        .send({
          phone: validPhone,
          message: validMessage
        })
        .expect(200);

      expect(response.body.success).toBe(true);
    });
  });

  describe('POST /send-message', () => {
    test('Should send text message successfully', async () => {
      const response = await request(app)
        .post('/send-message')
        .set('x-api-key', validApiKey)
        .send({
          phone: validPhone,
          message: validMessage
        })
        .expect(200);

      expect(response.body).toMatchObject({
        success: true,
        message: 'Message sent successfully',
        timestamp: expect.any(String)
      });
    });

    test('Should validate phone number format', async () => {
      const invalidPhones = [
        '123',           // Too short
        '0123456789',    // Starts with 0
        'abc123456789',  // Contains letters
        '+265991234567', // Contains +
        ''               // Empty
      ];

      for (const phone of invalidPhones) {
        const response = await request(app)
          .post('/send-message')
          .set('x-api-key', validApiKey)
          .send({
            phone: phone,
            message: validMessage
          })
          .expect(400);

        expect(response.body.success).toBe(false);
        expect(response.body.error.code).toBe(400);
      }
    });

    test('Should validate message content', async () => {
      // Test empty message
      const response1 = await request(app)
        .post('/send-message')
        .set('x-api-key', validApiKey)
        .send({
          phone: validPhone,
          message: ''
        })
        .expect(400);

      expect(response1.body.success).toBe(false);

      // Test too long message (> 4096 characters)
      const longMessage = 'a'.repeat(4097);
      const response2 = await request(app)
        .post('/send-message')
        .set('x-api-key', validApiKey)
        .send({
          phone: validPhone,
          message: longMessage
        })
        .expect(400);

      expect(response2.body.success).toBe(false);
    });

    test('Should handle missing required fields', async () => {
      // Missing phone
      const response1 = await request(app)
        .post('/send-message')
        .set('x-api-key', validApiKey)
        .send({
          message: validMessage
        })
        .expect(400);

      expect(response1.body.success).toBe(false);

      // Missing message
      const response2 = await request(app)
        .post('/send-message')
        .set('x-api-key', validApiKey)
        .send({
          phone: validPhone
        })
        .expect(400);

      expect(response2.body.success).toBe(false);
    });
  });

  describe('POST /send-media', () => {
    test('Should send media message successfully', async () => {
      const response = await request(app)
        .post('/send-media')
        .set('x-api-key', validApiKey)
        .send({
          phone: validPhone,
          mediaUrl: validMediaUrl,
          caption: 'Test caption'
        })
        .expect(200);

      expect(response.body).toMatchObject({
        success: true,
        message: 'Media sent successfully',
        timestamp: expect.any(String)
      });
    });

    test('Should work without caption', async () => {
      const response = await request(app)
        .post('/send-media')
        .set('x-api-key', validApiKey)
        .send({
          phone: validPhone,
          mediaUrl: validMediaUrl
        })
        .expect(200);

      expect(response.body.success).toBe(true);
    });

    test('Should validate media URL format', async () => {
      const invalidUrls = [
        'not-a-url',
        'ftp://example.com/file.jpg',
        'javascript:alert(1)',
        ''
      ];

      for (const url of invalidUrls) {
        const response = await request(app)
          .post('/send-media')
          .set('x-api-key', validApiKey)
          .send({
            phone: validPhone,
            mediaUrl: url
          })
          .expect(400);

        expect(response.body.success).toBe(false);
      }
    });

    test('Should validate caption length', async () => {
      const longCaption = 'a'.repeat(1025); // > 1024 characters

      const response = await request(app)
        .post('/send-media')
        .set('x-api-key', validApiKey)
        .send({
          phone: validPhone,
          mediaUrl: validMediaUrl,
          caption: longCaption
        })
        .expect(400);

      expect(response.body.success).toBe(false);
    });

    test('Should handle missing required fields', async () => {
      // Missing phone
      const response1 = await request(app)
        .post('/send-media')
        .set('x-api-key', validApiKey)
        .send({
          mediaUrl: validMediaUrl
        })
        .expect(400);

      expect(response1.body.success).toBe(false);

      // Missing mediaUrl
      const response2 = await request(app)
        .post('/send-media')
        .set('x-api-key', validApiKey)
        .send({
          phone: validPhone
        })
        .expect(400);

      expect(response2.body.success).toBe(false);
    });
  });

  describe('POST /payment-update', () => {
    test('Should send payment update notification successfully', async () => {
      const response = await request(app)
        .post('/payment-update')
        .set('x-api-key', validApiKey)
        .send({
          orderId: validOrderId,
          status: validStatus,
          phone: validPhone
        })
        .expect(200);

      expect(response.body).toMatchObject({
        success: true,
        message: 'Payment update notification sent',
        timestamp: expect.any(String)
      });
    });

    test('Should validate order ID format', async () => {
      const invalidOrderIds = [
        'OR', // Too short
        'a'.repeat(51), // Too long
        'ORD-123!@#', // Contains special characters
        '', // Empty
        'ORD 123' // Contains space
      ];

      for (const orderId of invalidOrderIds) {
        const response = await request(app)
          .post('/payment-update')
          .set('x-api-key', validApiKey)
          .send({
            orderId: orderId,
            status: validStatus,
            phone: validPhone
          })
          .expect(400);

        expect(response.body.success).toBe(false);
      }
    });

    test('Should validate payment status values', async () => {
      const invalidStatuses = [
        'InvalidStatus',
        'PAID', // Case sensitive
        'pending', // Case sensitive
        '',
        'Completed' // Not in allowed list
      ];

      for (const status of invalidStatuses) {
        const response = await request(app)
          .post('/payment-update')
          .set('x-api-key', validApiKey)
          .send({
            orderId: validOrderId,
            status: status,
            phone: validPhone
          })
          .expect(400);

        expect(response.body.success).toBe(false);
      }
    });

    test('Should accept valid payment statuses', async () => {
      const validStatuses = ['Pending', 'Paid', 'Failed', 'Cancelled', 'Refunded'];

      for (const status of validStatuses) {
        const response = await request(app)
          .post('/payment-update')
          .set('x-api-key', validApiKey)
          .send({
            orderId: validOrderId,
            status: status,
            phone: validPhone
          })
          .expect(200);

        expect(response.body.success).toBe(true);
      }
    });

    test('Should handle missing required fields', async () => {
      // Test all combinations of missing fields
      const testCases = [
        { status: validStatus, phone: validPhone }, // Missing orderId
        { orderId: validOrderId, phone: validPhone }, // Missing status
        { orderId: validOrderId, status: validStatus }, // Missing phone
        {} // Missing all
      ];

      for (const testCase of testCases) {
        const response = await request(app)
          .post('/payment-update')
          .set('x-api-key', validApiKey)
          .send(testCase)
          .expect(400);

        expect(response.body.success).toBe(false);
      }
    });
  });

  describe('Rate Limiting', () => {
    test('Should enforce rate limits on send-message endpoint', async () => {
      // Make requests up to the limit
      const promises = Array(101).fill().map(() =>
        request(app)
          .post('/send-message')
          .set('x-api-key', validApiKey)
          .send({
            phone: validPhone,
            message: validMessage
          })
      );

      const responses = await Promise.all(promises);
      const rateLimitedResponses = responses.filter(res => res.status === 429);

      expect(rateLimitedResponses.length).toBeGreaterThan(0);
    });

    test('Should have stricter rate limits on send-media endpoint', async () => {
      // Make requests up to the strict limit (50)
      const promises = Array(51).fill().map(() =>
        request(app)
          .post('/send-media')
          .set('x-api-key', validApiKey)
          .send({
            phone: validPhone,
            mediaUrl: validMediaUrl
          })
      );

      const responses = await Promise.all(promises);
      const rateLimitedResponses = responses.filter(res => res.status === 429);

      expect(rateLimitedResponses.length).toBeGreaterThan(0);
    });
  });

  describe('Error Handling', () => {
    test('Should handle 404 for unknown routes', async () => {
      const response = await request(app)
        .get('/unknown-route')
        .expect(404);

      expect(response.body).toMatchObject({
        success: false,
        error: {
          code: 404,
          message: 'Route not found'
        }
      });
    });

    test('Should handle invalid JSON', async () => {
      const response = await request(app)
        .post('/send-message')
        .set('x-api-key', validApiKey)
        .set('Content-Type', 'application/json')
        .send('invalid json')
        .expect(400);

      // Express will handle this before our middleware
      expect(response.status).toBe(400);
    });

    test('Should handle oversized request bodies', async () => {
      const largeBody = {
        phone: validPhone,
        message: 'x'.repeat(11 * 1024 * 1024) // 11MB > 10MB limit
      };

      const response = await request(app)
        .post('/send-message')
        .set('x-api-key', validApiKey)
        .send(largeBody)
        .expect(413);
    });
  });

  describe('Response Format', () => {
    test('All successful responses should have consistent format', async () => {
      const response = await request(app)
        .post('/send-message')
        .set('x-api-key', validApiKey)
        .send({
          phone: validPhone,
          message: validMessage
        })
        .expect(200);

      expect(response.body).toEqual({
        success: true,
        message: expect.any(String),
        timestamp: expect.stringMatching(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z$/)
      });
    });

    test('All error responses should have consistent format', async () => {
      const response = await request(app)
        .post('/send-message')
        .send({
          phone: 'invalid',
          message: validMessage
        })
        .expect(401);

      expect(response.body).toEqual({
        success: false,
        error: {
          code: expect.any(Number),
          message: expect.any(String),
          timestamp: expect.stringMatching(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z$/)
        }
      });
    });
  });

  describe('Security Headers', () => {
    test('Should include security headers', async () => {
      const response = await request(app)
        .get('/health');

      expect(response.headers).toHaveProperty('x-content-type-options');
      expect(response.headers).toHaveProperty('x-frame-options');
      expect(response.headers).toHaveProperty('x-xss-protection');
    });
  });
});

// Helper function to test concurrent requests
async function makeConcurrentRequests(app, endpoint, data, apiKey, count = 10) {
  const promises = Array(count).fill().map(() =>
    request(app)
      .post(endpoint)
      .set('x-api-key', apiKey)
      .send(data)
  );

  return Promise.all(promises);
}

// Performance tests (optional, run separately)
describe('Performance Tests', () => {
  const validApiKey = 'test-api-key';
  const validPhone = '265991234567';
  const validMessage = 'Performance test message';

  beforeAll(() => {
    process.env.API_KEY = validApiKey;
    process.env.NODE_ENV = 'test';
  });

  afterAll(() => {
    delete process.env.API_KEY;
    delete process.env.NODE_ENV;
  });

  test('Should handle concurrent requests efficiently', async () => {
    const startTime = Date.now();

    const responses = await makeConcurrentRequests(
      app,
      '/send-message',
      { phone: validPhone, message: validMessage },
      validApiKey,
      10
    );

    const endTime = Date.now();
    const duration = endTime - startTime;

    // Should complete 10 concurrent requests within 5 seconds
    expect(duration).toBeLessThan(5000);

    // All requests should succeed (assuming WhatsApp mock works)
    const successfulRequests = responses.filter(res => res.status === 200);
    expect(successfulRequests.length).toBeGreaterThan(0);
  }, 10000);
});
