/**
 * Edge Case Tests
 *
 * Tests boundary conditions, invalid inputs, and extreme scenarios
 * Validates system behavior under unusual or stressful conditions
 */

const request = require('supertest');
const fs = require('fs');
const path = require('path');
const os = require('os');

// Create test instance with isolated environment
const testServicesFile = path.join(os.tmpdir(), `edge-services-${Date.now()}.json`);
const testConfigFile = path.join(os.tmpdir(), `edge-config-${Date.now()}.json`);

// Set test environment
process.env.SERVICES_FILE = testServicesFile;
process.env.CONFIG_FILE = testConfigFile;
process.env.ENABLE_HEALTH_CHECKER = 'false';
process.env.NODE_ENV = 'test';

// Initialize test files
fs.writeFileSync(testServicesFile, '[]', 'utf8');
fs.writeFileSync(testConfigFile, '{}', 'utf8');

// Require app after environment setup
const app = require('../server');

describe('Edge Case Tests', () => {

  beforeEach(async () => {
    // Reset state through the API to respect file locks
    await request(app).put('/api/services').send([]);
    fs.writeFileSync(testConfigFile, '{}', 'utf8');
  });

  afterAll(() => {
    // Cleanup test files
    try {
      fs.unlinkSync(testServicesFile);
      fs.unlinkSync(testConfigFile);
    } catch (e) {
      // Ignore cleanup errors
    }
  });

  describe('Boundary Conditions', () => {
    test('should handle empty service ID', async () => {
      const res = await request(app)
        .post('/api/services')
        .send({ id: '', name: 'Empty ID Service' });

      // Should reject empty ID
      expect(res.statusCode).toBeGreaterThanOrEqual(400);
    });

    test('should handle very long service ID (1000 chars)', async () => {
      const longId = 'a'.repeat(1000);
      const res = await request(app)
        .post('/api/services')
        .send({ id: longId, name: 'Long ID' });

      // Might accept or reject depending on validation
      expect([200, 400, 413]).toContain(res.statusCode);
    });

    test('should handle very long service name (10000 chars)', async () => {
      const longName = 'Name '.repeat(2000);
      const res = await request(app)
        .post('/api/services')
        .send({ id: 'test', name: longName });

      // Should handle gracefully
      expect([200, 400, 413]).toContain(res.statusCode);
    });

    test('should handle service with exactly 0 properties', async () => {
      const res = await request(app)
        .post('/api/services')
        .send({});

      // Should reject - missing required fields
      expect(res.statusCode).toBe(400);
    });

    test('should handle service with 100+ properties', async () => {
      const service = { id: 'many-props', name: 'Many Props' };
      for (let i = 0; i < 100; i++) {
        service[`prop${i}`] = `value${i}`;
      }

      const res = await request(app)
        .post('/api/services')
        .send(service);

      // Should handle extra properties gracefully
      expect([200, 400]).toContain(res.statusCode);
    });
  });

  describe('Invalid Input Types', () => {
    test('should handle null service ID', async () => {
      const res = await request(app)
        .post('/api/services')
        .send({ id: null, name: 'Null ID' });

      expect(res.statusCode).toBeGreaterThanOrEqual(400);
    });

    test('should handle number as service ID', async () => {
      const res = await request(app)
        .post('/api/services')
        .send({ id: 12345, name: 'Number ID' });

      // Might convert to string or reject
      expect([200, 400]).toContain(res.statusCode);
    });

    test('should handle array as service ID', async () => {
      const res = await request(app)
        .post('/api/services')
        .send({ id: ['array', 'id'], name: 'Array ID' });

      expect(res.statusCode).toBeGreaterThanOrEqual(400);
    });

    test('should handle object as service ID', async () => {
      const res = await request(app)
        .post('/api/services')
        .send({ id: { nested: 'object' }, name: 'Object ID' });

      expect(res.statusCode).toBeGreaterThanOrEqual(400);
    });

    test('should handle boolean as service name', async () => {
      const res = await request(app)
        .post('/api/services')
        .send({ id: 'bool-test', name: true });

      // Might convert to string or reject
      expect([200, 400]).toContain(res.statusCode);
    });

    test('should handle undefined properties', async () => {
      const res = await request(app)
        .post('/api/services')
        .send({ id: 'test', name: undefined });

      expect(res.statusCode).toBeGreaterThanOrEqual(400);
    });
  });

  describe('Special Characters and Encoding', () => {
    test('should handle Unicode characters in service name', async () => {
      const res = await request(app)
        .post('/api/services')
        .send({ id: 'unicode', name: '🚀 Rocket Service 中文 العربية' });

      // Should handle Unicode properly
      expect([200, 400]).toContain(res.statusCode);

      if (res.statusCode === 200) {
        const services = JSON.parse(fs.readFileSync(testServicesFile, 'utf8'));
        expect(services[0].name).toContain('🚀');
      }
    });

    test('should handle special characters in ID', async () => {
      const res = await request(app)
        .post('/api/services')
        .send({ id: 'test!@#$%^&*()', name: 'Special ID' });

      // Might sanitize or reject
      expect([200, 400]).toContain(res.statusCode);
    });

    test('should handle newlines in service name', async () => {
      const res = await request(app)
        .post('/api/services')
        .send({ id: 'newline', name: 'Line 1\nLine 2\nLine 3' });

      expect([200, 400]).toContain(res.statusCode);
    });

    test('should handle SQL injection attempt in ID', async () => {
      const res = await request(app)
        .post('/api/services')
        .send({ id: "'; DROP TABLE services; --", name: 'SQL Injection' });

      // Should reject or sanitize
      expect([200, 400]).toContain(res.statusCode);

      // Verify file is still valid JSON
      const content = fs.readFileSync(testServicesFile, 'utf8');
      expect(() => JSON.parse(content)).not.toThrow();
    });

    test('should handle path traversal attempt in logo', async () => {
      const res = await request(app)
        .post('/api/services')
        .send({
          id: 'path-traversal',
          name: 'Path Traversal',
          logo: '../../../../../../etc/passwd'
        });

      // Should handle safely
      expect([200, 400]).toContain(res.statusCode);
    });

    test('should handle null bytes in input', async () => {
      const res = await request(app)
        .post('/api/services')
        .send({ id: 'null\x00byte', name: 'Test\x00Name' });

      // Should reject or sanitize
      expect([200, 400]).toContain(res.statusCode);
    });
  });

  describe('Large Datasets', () => {
    test('should handle 100 services', async () => {
      // Add 100 services
      for (let i = 0; i < 100; i++) {
        await request(app)
          .post('/api/services')
          .send({ id: `service-${i}`, name: `Service ${i}` });
      }

      // Verify all exist
      const res = await request(app).get('/api/services');
      expect(res.statusCode).toBe(200);
      expect(res.body.length).toBe(100);
    }, 60000);

    test('should handle deleting from large dataset', async () => {
      // Add 50 services
      for (let i = 0; i < 50; i++) {
        await request(app)
          .post('/api/services')
          .send({ id: `bulk-${i}`, name: `Bulk ${i}` });
      }

      // Delete 25 services
      for (let i = 0; i < 25; i++) {
        await request(app).delete(`/api/services/bulk-${i}`);
      }

      // Verify 25 remain
      const res = await request(app).get('/api/services');
      expect(res.body.length).toBe(25);
    }, 30000);

    test('should handle bulk import of 200 services', async () => {
      const bulkServices = Array.from({ length: 200 }, (_, i) => ({
        id: `bulk-${i}`,
        name: `Bulk Service ${i}`
      }));

      const res = await request(app)
        .put('/api/services')
        .send(bulkServices);

      expect(res.statusCode).toBe(200);

      // Verify all imported
      const getRes = await request(app).get('/api/services');
      expect(getRes.body.length).toBe(200);
    }, 10000); // Longer timeout

    test('should handle service with very large property value (1MB)', async () => {
      const largeData = 'x'.repeat(1024 * 1024); // 1MB string

      const res = await request(app)
        .post('/api/services')
        .send({
          id: 'large-data',
          name: 'Large Data',
          description: largeData
        });

      // Might reject due to size
      expect([200, 413]).toContain(res.statusCode);
    });
  });

  describe('Concurrent Operations and Race Conditions', () => {
    test('should handle 20 concurrent POSTs without corruption', async () => {
      const promises = Array.from({ length: 20 }, (_, i) =>
        request(app)
          .post('/api/services')
          .send({ id: `concurrent-${i}`, name: `Concurrent ${i}` })
      );

      const results = await Promise.all(promises);

      // With file locking, some may fail with 500 (lock contention) — that's expected
      const successes = results.filter(r => r.statusCode === 200);
      expect(successes.length).toBeGreaterThanOrEqual(1);

      // The critical check: file must be valid JSON (no corruption)
      const content = fs.readFileSync(testServicesFile, 'utf8');
      expect(() => JSON.parse(content)).not.toThrow();

      // And the count must match the number of successes
      const services = JSON.parse(content);
      expect(services.length).toBe(successes.length);
    });

    test('should handle concurrent add and delete of same service', async () => {
      // Add a service
      await request(app)
        .post('/api/services')
        .send({ id: 'race', name: 'Race Service' });

      // Simultaneously add again and delete
      const [addRes, deleteRes] = await Promise.all([
        request(app).post('/api/services').send({ id: 'race', name: 'Race 2' }),
        request(app).delete('/api/services/race')
      ]);

      // One should succeed, states should be consistent
      const services = JSON.parse(fs.readFileSync(testServicesFile, 'utf8'));
      expect(() => JSON.parse(fs.readFileSync(testServicesFile, 'utf8'))).not.toThrow();
    });

    test('should handle concurrent bulk imports', async () => {
      const set1 = [{ id: 's1', name: 'Set 1' }];
      const set2 = [{ id: 's2', name: 'Set 2' }];

      const [res1, res2] = await Promise.all([
        request(app).put('/api/services').send(set1),
        request(app).put('/api/services').send(set2)
      ]);

      // Both operations should complete
      expect([200]).toContain(res1.statusCode);
      expect([200]).toContain(res2.statusCode);

      // Final state should have one complete set (last write wins)
      const services = JSON.parse(fs.readFileSync(testServicesFile, 'utf8'));
      expect(services.length).toBeGreaterThanOrEqual(1);
    });
  });

  describe('File System Edge Cases', () => {
    test('should handle file with read-only after writing', async () => {
      // Add a service
      await request(app)
        .post('/api/services')
        .send({ id: 'readonly-test', name: 'Read Only' });

      // Make file read-only
      fs.chmodSync(testServicesFile, 0o444);

      // Try to add another service
      const res = await request(app)
        .post('/api/services')
        .send({ id: 'should-fail', name: 'Should Fail' });

      // Should fail with 500 error
      expect(res.statusCode).toBe(500);

      // Restore permissions for cleanup
      fs.chmodSync(testServicesFile, 0o666);
    });

    test('should handle missing services file gracefully', async () => {
      // Delete the file
      fs.unlinkSync(testServicesFile);

      // Try to get services
      const res = await request(app).get('/api/services');

      // Should either return empty array or create file
      expect([200, 500]).toContain(res.statusCode);

      // File should be recreated or error handled
      if (res.statusCode === 200) {
        expect(Array.isArray(res.body)).toBe(true);
      }
    });

    test('should handle empty file (0 bytes)', async () => {
      // Create empty file
      fs.writeFileSync(testServicesFile, '', 'utf8');

      const res = await request(app).get('/api/services');

      // Should handle gracefully
      expect([200, 500]).toContain(res.statusCode);
    });

    test('should handle file with only whitespace', async () => {
      fs.writeFileSync(testServicesFile, '   \n\t\r   ', 'utf8');

      const res = await request(app).get('/api/services');

      // Should handle gracefully
      expect([200, 500]).toContain(res.statusCode);
    });

    test('should handle file with BOM (Byte Order Mark)', async () => {
      const bomContent = '\uFEFF[]';
      fs.writeFileSync(testServicesFile, bomContent, 'utf8');

      const res = await request(app).get('/api/services');

      // BOM may cause JSON parse to fail (500) or be handled (200)
      expect([200, 500]).toContain(res.statusCode);
      if (res.statusCode === 200) {
        expect(Array.isArray(res.body)).toBe(true);
      }
    });
  });

  describe('API Request Edge Cases', () => {
    test('should handle missing Content-Type header', async () => {
      const res = await request(app)
        .post('/api/services')
        .set('Content-Type', '')
        .send('{"id":"test","name":"Test"}');

      // Should handle gracefully
      expect([200, 400]).toContain(res.statusCode);
    });

    test('should handle wrong Content-Type (text/plain)', async () => {
      const res = await request(app)
        .post('/api/services')
        .set('Content-Type', 'text/plain')
        .send('{"id":"test","name":"Test"}');

      // Might still parse or reject
      expect([200, 400, 415]).toContain(res.statusCode);
    });

    test('should handle extremely nested JSON (50 levels)', async () => {
      let nested = { value: 'deep' };
      for (let i = 0; i < 50; i++) {
        nested = { level: nested };
      }

      const res = await request(app)
        .post('/api/services')
        .send({ id: 'nested', name: 'Nested', data: nested });

      // Should handle or reject
      expect([200, 400]).toContain(res.statusCode);
    });

    test('should handle request with circular reference (if possible)', async () => {
      // Can't send actual circular JSON, but test large nested structure
      const data = { id: 'circular', name: 'Test' };

      const res = await request(app)
        .post('/api/services')
        .send(data);

      expect([200, 400]).toContain(res.statusCode);
    });

    test('should handle double-encoded JSON', async () => {
      const doubleEncoded = JSON.stringify(
        JSON.stringify({ id: 'double', name: 'Double Encoded' })
      );

      const res = await request(app)
        .post('/api/services')
        .set('Content-Type', 'application/json')
        .send(doubleEncoded);

      // Should reject - wrong format
      expect([400, 500]).toContain(res.statusCode);
    });
  });

  describe('Template Edge Cases', () => {
    test('should handle requesting template with special chars in ID', async () => {
      const res = await request(app).get('/api/apps/templates/test%20space');

      expect([404, 400]).toContain(res.statusCode);
    });

    test('should handle requesting template with very long ID', async () => {
      const longId = 'a'.repeat(1000);
      const res = await request(app).get(`/api/apps/templates/${longId}`);

      expect([404, 414]).toContain(res.statusCode);
    });

    test('should handle template with path traversal', async () => {
      const res = await request(app).get('/api/apps/templates/../../secrets');

      expect([404, 400]).toContain(res.statusCode);
    });
  });

  describe('Configuration Edge Cases', () => {
    test('should handle empty configuration object', async () => {
      const res = await request(app)
        .post('/api/config')
        .send({});

      expect(res.statusCode).toBe(200);

      // Verify empty config saved
      const config = JSON.parse(fs.readFileSync(testConfigFile, 'utf8'));
      expect(typeof config).toBe('object');
    });

    test('should handle configuration with 1000 properties', async () => {
      const largeConfig = {};
      for (let i = 0; i < 1000; i++) {
        largeConfig[`setting${i}`] = `value${i}`;
      }

      const res = await request(app)
        .post('/api/config')
        .send(largeConfig);

      expect([200, 413]).toContain(res.statusCode);
    });

    test('should handle configuration with nested arrays', async () => {
      const config = {
        nested: [[['deep', 'array'], ['values']], [['more']]]
      };

      const res = await request(app)
        .post('/api/config')
        .send(config);

      expect(res.statusCode).toBe(200);
    });
  });

  describe('Delete Edge Cases', () => {
    test('should handle deleting non-existent service', async () => {
      const res = await request(app).delete('/api/services/does-not-exist');

      expect(res.statusCode).toBe(404);
    });

    test('should handle deleting with special characters in ID', async () => {
      const res = await request(app).delete('/api/services/test%2Fslash');

      expect([404, 400]).toContain(res.statusCode);
    });

    test('should handle deleting same service twice simultaneously', async () => {
      // Add service
      await request(app)
        .post('/api/services')
        .send({ id: 'delete-me', name: 'Delete Me' });

      // Delete twice at once
      const [res1, res2] = await Promise.all([
        request(app).delete('/api/services/delete-me'),
        request(app).delete('/api/services/delete-me')
      ]);

      // One should succeed (200), one should fail (404)
      const statuses = [res1.statusCode, res2.statusCode].sort();
      expect(statuses).toContain(200);
      expect(statuses).toContain(404);
    });
  });

  describe('State Consistency Edge Cases', () => {
    test('should recover if file becomes corrupted mid-operation', async () => {
      // Add initial service
      await request(app)
        .post('/api/services')
        .send({ id: 'initial', name: 'Initial' });

      // Corrupt the file
      fs.writeFileSync(testServicesFile, '{corrupted', 'utf8');

      // Try to read
      const res = await request(app).get('/api/services');

      // Should handle error gracefully
      expect([200, 500]).toContain(res.statusCode);
    });

    test('should handle file replaced with directory', async () => {
      // Delete file
      fs.unlinkSync(testServicesFile);

      // Create directory with same name
      fs.mkdirSync(testServicesFile);

      // Try to read
      const res = await request(app).get('/api/services');

      expect(res.statusCode).toBe(500);

      // Cleanup
      fs.rmdirSync(testServicesFile);
    });
  });
});
