/**
 * Integration Tests
 *
 * Tests multi-component workflows and end-to-end scenarios
 * Validates that all DashCaddy components work together correctly
 */

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(), `integration-services-${Date.now()}.json`);
const testConfigFile = path.join(os.tmpdir(), `integration-config-${Date.now()}.json`);
const testDnsCredsFile = path.join(os.tmpdir(), `integration-dns-${Date.now()}.json`);
const testCaddyfile = path.join(os.tmpdir(), `integration-Caddyfile-${Date.now()}`);

// Set test environment
process.env.SERVICES_FILE = testServicesFile;
process.env.CONFIG_FILE = testConfigFile;
process.env.DNS_CREDENTIALS_FILE = testDnsCredsFile;
process.env.CADDYFILE_PATH = testCaddyfile;
process.env.CADDY_ADMIN_URL = 'http://localhost:2019';
process.env.ENABLE_HEALTH_CHECKER = 'false';
process.env.NODE_ENV = 'test';

// Initialize test files
fs.writeFileSync(testServicesFile, '[]', 'utf8');
fs.writeFileSync(testConfigFile, '{"domain": "test.local"}', 'utf8');
fs.writeFileSync(testDnsCredsFile, '{}', 'utf8');
fs.writeFileSync(testCaddyfile, '# Test Caddyfile\n', 'utf8');

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

describe('Integration Tests', () => {

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

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

  describe('End-to-End Service Deployment', () => {
    test('should complete full service lifecycle: add → configure → verify → delete', async () => {
      // Step 1: Add a new service
      const newService = {
        id: 'test-app',
        name: 'Test Application',
        logo: '/assets/test.png',
        url: 'https://test.test.local'
      };

      const addRes = await request(app)
        .post('/api/services')
        .send(newService);

      expect(addRes.statusCode).toBe(200);
      expect(addRes.body.success).toBe(true);

      // Step 2: Verify service appears in list
      const listRes = await request(app).get('/api/services');
      expect(listRes.statusCode).toBe(200);
      expect(listRes.body.length).toBe(1);
      expect(listRes.body[0].id).toBe('test-app');

      // Step 3: Update service configuration
      const updatedServices = [{
        ...newService,
        status: 'online',
        responseTime: 150
      }];

      const updateRes = await request(app)
        .put('/api/services')
        .send(updatedServices);

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

      // Step 4: Verify update
      const verifyRes = await request(app).get('/api/services');
      expect(verifyRes.body[0].status).toBe('online');

      // Step 5: Delete service
      const deleteRes = await request(app).delete('/api/services/test-app');
      expect(deleteRes.statusCode).toBe(200);

      // Step 6: Verify deletion
      const finalRes = await request(app).get('/api/services');
      expect(finalRes.body.length).toBe(0);
    });

    test('should handle app deployment workflow: template → configure → deploy', async () => {
      // Step 1: Get app template
      const templateRes = await request(app).get('/api/apps/templates/jellyfin');
      expect(templateRes.statusCode).toBe(200);
      expect(templateRes.body.success).toBe(true);
      const template = templateRes.body.template;

      // Step 2: Configure app from template
      const appConfig = {
        id: 'jellyfin',
        name: template.name,
        logo: template.logo,
        port: 8096,
        subdomain: 'jellyfin'
      };

      // Step 3: Add configured service
      const deployRes = await request(app)
        .post('/api/services')
        .send(appConfig);

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

      // Step 4: Verify service is listed
      const servicesRes = await request(app).get('/api/services');
      expect(servicesRes.body).toContainEqual(
        expect.objectContaining({ id: 'jellyfin' })
      );
    });
  });

  describe('Multi-Service Management', () => {
    test('should handle multiple services concurrently', async () => {
      // Deploy 5 services simultaneously (reduced from 10 to avoid overwhelming)
      const services = Array.from({ length: 5 }, (_, i) => ({
        id: `concurrent-${i}`,
        name: `Concurrent Service ${i}`,
        logo: `/assets/service-${i}.png`
      }));

      const deployPromises = services.map(service =>
        request(app).post('/api/services').send(service)
      );

      const results = await Promise.all(deployPromises);

      // All deployments should succeed
      results.forEach((res, index) => {
        if (res.statusCode !== 200) {
          console.log(`Service ${index} failed:`, res.body);
        }
        expect(res.statusCode).toBe(200);
      });

      // Verify all services are listed
      const listRes = await request(app).get('/api/services');
      expect(listRes.body.length).toBe(5);
    });

    test('should handle bulk import and individual updates', async () => {
      // Step 1: Bulk import services
      const bulkServices = [
        { id: 'plex', name: 'Plex' },
        { id: 'jellyfin', name: 'Jellyfin' },
        { id: 'emby', name: 'Emby' }
      ];

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

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

      // Step 2: Update individual service
      const updatedServices = [
        { id: 'plex', name: 'Plex', status: 'online' },
        { id: 'jellyfin', name: 'Jellyfin' },
        { id: 'emby', name: 'Emby' }
      ];

      await request(app).put('/api/services').send(updatedServices);

      // Step 3: Verify specific service was updated
      const services = JSON.parse(fs.readFileSync(testServicesFile, 'utf8'));
      const plexService = services.find(s => s.id === 'plex');
      expect(plexService.status).toBe('online');
    });

    test('should maintain data consistency across operations', async () => {
      // Perform series of operations
      await request(app).post('/api/services').send({ id: 's1', name: 'Service 1' });
      await request(app).post('/api/services').send({ id: 's2', name: 'Service 2' });
      await request(app).post('/api/services').send({ id: 's3', name: 'Service 3' });

      // Verify count
      let services = JSON.parse(fs.readFileSync(testServicesFile, 'utf8'));
      expect(services.length).toBe(3);

      // Delete one
      await request(app).delete('/api/services/s2');

      // Verify count and content
      services = JSON.parse(fs.readFileSync(testServicesFile, 'utf8'));
      expect(services.length).toBe(2);
      expect(services.find(s => s.id === 's2')).toBeUndefined();
      expect(services.find(s => s.id === 's1')).toBeDefined();
      expect(services.find(s => s.id === 's3')).toBeDefined();
    });
  });

  describe('Configuration Management Integration', () => {
    test('should coordinate config changes with service updates', async () => {
      // Step 1: Set initial config
      const config = {
        domain: 'example.local',
        theme: 'dark',
        enableHealthCheck: false
      };

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

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

      // Step 2: Add service that uses config
      const service = {
        id: 'test',
        name: 'Test Service',
        subdomain: 'test'
      };

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

      // Step 3: Verify config persists
      const getConfigRes = await request(app).get('/api/config');
      expect(getConfigRes.body.domain).toBe('example.local');

      // Step 4: Update config
      const newConfig = { ...config, theme: 'light' };
      await request(app).post('/api/config').send(newConfig);

      // Step 5: Verify service still exists and config updated
      const servicesRes = await request(app).get('/api/services');
      const configCheckRes = await request(app).get('/api/config');

      expect(servicesRes.body.length).toBe(1);
      expect(configCheckRes.body.theme).toBe('light');
    });
  });

  describe('Template Discovery and Deployment', () => {
    test('should list templates, select one, and deploy', async () => {
      // Step 1: Get all templates
      const templatesRes = await request(app).get('/api/apps/templates');
      expect(templatesRes.statusCode).toBe(200);
      expect(Object.keys(templatesRes.body.templates).length).toBeGreaterThan(50);

      // Step 2: Verify categories exist (format may vary)
      expect(templatesRes.body).toHaveProperty('categories');
      const categories = templatesRes.body.categories;
      // Categories might be an array or object depending on implementation
      expect(categories).toBeTruthy();

      // Step 3: Select a specific template
      const templateIds = Object.keys(templatesRes.body.templates);
      const firstTemplateId = templateIds[0];

      const singleTemplateRes = await request(app)
        .get(`/api/apps/templates/${firstTemplateId}`);

      expect(singleTemplateRes.statusCode).toBe(200);
      expect(singleTemplateRes.body.template).toHaveProperty('name');
      expect(singleTemplateRes.body.template).toHaveProperty('docker');

      // Step 4: Deploy service from template
      const service = {
        id: firstTemplateId,
        name: singleTemplateRes.body.template.name,
        logo: singleTemplateRes.body.template.logo
      };

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

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

    test('should handle template with complex configuration', async () => {
      // Get a complex template (Plex has environment variables, volumes, etc.)
      const templateRes = await request(app).get('/api/apps/templates/plex');
      expect(templateRes.statusCode).toBe(200);

      const template = templateRes.body.template;

      // Verify template has complex config
      expect(template.docker).toHaveProperty('image');
      expect(template.docker).toHaveProperty('environment');
      expect(template.docker).toHaveProperty('volumes');

      // Deploy with configuration
      const service = {
        id: 'plex-prod',
        name: 'Plex Production',
        logo: template.logo,
        port: 32400,
        subdomain: 'plex'
      };

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

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

      // Verify service exists
      const servicesRes = await request(app).get('/api/services');
      expect(servicesRes.body).toContainEqual(
        expect.objectContaining({ id: 'plex-prod' })
      );
    });
  });

  describe('Error Recovery and Resilience', () => {
    test('should recover from invalid service data', async () => {
      // Add valid service
      await request(app)
        .post('/api/services')
        .send({ id: 'valid', name: 'Valid Service' });

      // Try to add invalid service
      const invalidRes = await request(app)
        .post('/api/services')
        .send({ id: 'invalid' }); // Missing required 'name' field

      expect(invalidRes.statusCode).toBe(400);

      // Verify valid service still exists
      const servicesRes = await request(app).get('/api/services');
      expect(servicesRes.body.length).toBe(1);
      expect(servicesRes.body[0].id).toBe('valid');
    });

    test('should handle file corruption gracefully', async () => {
      // Add some services
      await request(app)
        .post('/api/services')
        .send({ id: 's1', name: 'Service 1' });

      // Simulate file corruption (invalid JSON)
      fs.writeFileSync(testServicesFile, '{ invalid json }', 'utf8');

      // API should handle this gracefully
      const res = await request(app).get('/api/services');

      // Should either return error or empty array (depending on implementation)
      expect([200, 500]).toContain(res.statusCode);
    });

    test('should maintain consistency during concurrent modifications', async () => {
      // Start with empty state
      const initialServices = [
        { id: 'base1', name: 'Base 1' },
        { id: 'base2', name: 'Base 2' }
      ];

      await request(app).put('/api/services').send(initialServices);

      // Perform concurrent operations
      const operations = [
        request(app).post('/api/services').send({ id: 'new1', name: 'New 1' }),
        request(app).post('/api/services').send({ id: 'new2', name: 'New 2' }),
        request(app).delete('/api/services/base1'),
        request(app).post('/api/services').send({ id: 'new3', name: 'New 3' })
      ];

      await Promise.all(operations);

      // Verify final state is consistent
      const services = JSON.parse(fs.readFileSync(testServicesFile, 'utf8'));

      // Should have base2 + 3 new services = 4 total
      expect(services.length).toBe(4);
      expect(services.find(s => s.id === 'base1')).toBeUndefined();
      expect(services.find(s => s.id === 'base2')).toBeDefined();
      expect(services.find(s => s.id === 'new1')).toBeDefined();
      expect(services.find(s => s.id === 'new2')).toBeDefined();
      expect(services.find(s => s.id === 'new3')).toBeDefined();
    });
  });

  describe('Health Check Integration', () => {
    test('should verify API health before operations', async () => {
      // Check health
      const healthRes = await request(app).get('/api/health');
      expect(healthRes.statusCode).toBe(200);
      expect(healthRes.body.status).toBe('ok');

      // Perform operation
      const addRes = await request(app)
        .post('/api/services')
        .send({ id: 'test', name: 'Test' });

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

      // Check health again
      const healthRes2 = await request(app).get('/api/health');
      expect(healthRes2.statusCode).toBe(200);
    });
  });

  describe('Real-World Workflow Scenarios', () => {
    test('Scenario: User discovers and deploys multiple media apps', async () => {
      // Step 1: Browse templates
      const templatesRes = await request(app).get('/api/apps/templates');
      const templates = templatesRes.body.templates;

      // Step 2: Find media apps
      const mediaApps = ['plex', 'jellyfin', 'emby'];
      const selectedApps = mediaApps.map(id => ({
        id,
        name: templates[id].name,
        logo: templates[id].logo
      }));

      // Step 3: Deploy all media apps
      for (const serviceConfig of selectedApps) {
        const res = await request(app)
          .post('/api/services')
          .send(serviceConfig);
        expect(res.statusCode).toBe(200);
      }

      // Step 4: Verify all deployed
      const servicesRes = await request(app).get('/api/services');
      expect(servicesRes.body.length).toBe(3);

      mediaApps.forEach(appId => {
        expect(servicesRes.body.find(s => s.id === appId)).toBeDefined();
      });
    });

    test('Scenario: Admin configures system and imports existing services', async () => {
      // Step 1: Set system configuration
      const config = {
        domain: 'homelab.local',
        theme: 'dark',
        enableHealthCheck: true
      };

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

      // Step 2: Import existing services from backup
      const existingServices = [
        { id: 'router', name: 'Router', logo: '/assets/router.png' },
        { id: 'nas', name: 'NAS', logo: '/assets/nas.png' },
        { id: 'pihole', name: 'Pi-hole', logo: '/assets/pihole.png' }
      ];

      await request(app).put('/api/services').send(existingServices);

      // Step 3: Add new service
      await request(app)
        .post('/api/services')
        .send({ id: 'newapp', name: 'New App' });

      // Step 4: Verify all services
      const servicesRes = await request(app).get('/api/services');
      expect(servicesRes.body.length).toBe(4);

      // Step 5: Verify config persisted
      const configRes = await request(app).get('/api/config');
      expect(configRes.body.domain).toBe('homelab.local');
    });

    test('Scenario: User reorganizes services (delete old, add new)', async () => {
      // Step 1: Start with existing services
      const oldServices = [
        { id: 'old1', name: 'Old Service 1' },
        { id: 'old2', name: 'Old Service 2' },
        { id: 'keep', name: 'Keep This' }
      ];

      await request(app).put('/api/services').send(oldServices);

      // Step 2: Delete old services
      await request(app).delete('/api/services/old1');
      await request(app).delete('/api/services/old2');

      // Step 3: Add new services
      await request(app)
        .post('/api/services')
        .send({ id: 'new1', name: 'New Service 1' });

      await request(app)
        .post('/api/services')
        .send({ id: 'new2', name: 'New Service 2' });

      // Step 4: Verify final state
      const servicesRes = await request(app).get('/api/services');
      expect(servicesRes.body.length).toBe(3);

      const serviceIds = servicesRes.body.map(s => s.id);
      expect(serviceIds).toContain('keep');
      expect(serviceIds).toContain('new1');
      expect(serviceIds).toContain('new2');
      expect(serviceIds).not.toContain('old1');
      expect(serviceIds).not.toContain('old2');
    });
  });

  describe('Data Persistence and State Management', () => {
    test('should persist data across multiple operations', async () => {
      // Create initial state
      await request(app)
        .post('/api/services')
        .send({ id: 'persistent', name: 'Persistent Service' });

      // Read file directly
      const services1 = JSON.parse(fs.readFileSync(testServicesFile, 'utf8'));
      expect(services1.length).toBe(1);

      // Modify through API
      await request(app)
        .post('/api/services')
        .send({ id: 'another', name: 'Another Service' });

      // Read file again
      const services2 = JSON.parse(fs.readFileSync(testServicesFile, 'utf8'));
      expect(services2.length).toBe(2);

      // Verify through API
      const apiRes = await request(app).get('/api/services');
      expect(apiRes.body.length).toBe(2);

      // All three methods should show same data
      expect(services2).toEqual(apiRes.body);
    });

    test('should handle rapid sequential operations', async () => {
      // Perform 10 rapid operations (sequential, not parallel)
      for (let i = 0; i < 10; i++) {
        const res = await request(app)
          .post('/api/services')
          .send({ id: `rapid-${i}`, name: `Rapid ${i}` });

        if (res.statusCode !== 200) {
          console.log(`Rapid operation ${i} failed:`, res.body);
        }
        expect(res.statusCode).toBe(200);
      }

      // Verify all 10 services exist
      const services = JSON.parse(fs.readFileSync(testServicesFile, 'utf8'));
      expect(services.length).toBe(10);
    });
  });
});
