/**
 * Integration Tests: Config Wizard End-to-End Flows
 *
 * Feature: config-wizard
 *
 * Tests filesystem interactions, real-world data parsing, and end-to-end
 * function composition from `configure.js`.
 *
 * Validates: Requirements 1.4, 1.5, 6.5, 6.6, 9.6, 9.7, 14.4, 16.2, 16.4, 16.6
 */

const os = require('os');
const fs = require('fs');
const path = require('path');
const {
  VARIABLE_DESCRIPTORS,
  GROUP_ORDER,
  OPTIONAL_GROUPS,
  parseEnvFile,
  parseDockerCompose,
  generateEnvContent,
  writeEnvFile,
  createBackup,
  detectInfraState,
  shouldSkipFrontendBuild,
  checkNodeVersion,
  checkProjectRoot,
} = require('../../configure.js');

/**
 * Create a temporary directory for test isolation.
 * Returns the path to the created directory.
 */
function createTempDir() {
  return fs.mkdtempSync(path.join(os.tmpdir(), 'config-wizard-test-'));
}

/**
 * Recursively remove a directory and its contents.
 */
function removeTempDir(dirPath) {
  fs.rmSync(dirPath, { recursive: true, force: true });
}

// ─────────────────────────────────────────────────────────────────────────────
// Test 1: Full wizard run with all defaults — verify correct files written
// ─────────────────────────────────────────────────────────────────────────────
describe('Full wizard run with all defaults', () => {
  let tmpDir;

  beforeEach(() => {
    tmpDir = createTempDir();
  });

  afterEach(() => {
    removeTempDir(tmpDir);
  });

  test('generateEnvContent + writeEnvFile produces valid backend .env with all required defaults', () => {
    const values = new Map();
    values.set('PORT', '3001');
    values.set('API_HOST', 'localhost');
    values.set('CORS_ORIGINS', 'http://localhost:3000');
    values.set('DATABASE_URL', 'postgresql://steam:pass@localhost:5433/cve_dashboard');
    values.set('SESSION_SECRET', 'a-very-long-secret-key-here-1234');
    values.set('REACT_APP_API_BASE', 'http://localhost:3001/api');
    values.set('REACT_APP_API_HOST', 'http://localhost:3001');

    const backendDescriptors = VARIABLE_DESCRIPTORS.filter(d => d.target === 'backend');
    const content = generateEnvContent(values, GROUP_ORDER, backendDescriptors, []);

    const filePath = path.join(tmpDir, '.env');
    writeEnvFile(filePath, content);

    expect(fs.existsSync(filePath)).toBe(true);
    const written = fs.readFileSync(filePath, 'utf8');

    // Verify key values are present
    expect(written).toContain('PORT=3001');
    expect(written).toContain('API_HOST=localhost');
    expect(written).toContain('CORS_ORIGINS=http://localhost:3000');
    expect(written).toContain('SESSION_SECRET=a-very-long-secret-key-here-1234');
    // DATABASE_URL contains special chars, should be quoted
    expect(written).toContain('DATABASE_URL=');
    // Ends with newline
    expect(written.endsWith('\n')).toBe(true);
  });

  test('generateEnvContent + writeEnvFile produces valid frontend .env with defaults', () => {
    const values = new Map();
    values.set('REACT_APP_API_BASE', 'http://localhost:3001/api');
    values.set('REACT_APP_API_HOST', 'http://localhost:3001');

    const frontendDescriptors = VARIABLE_DESCRIPTORS.filter(d => d.target === 'frontend');
    const content = generateEnvContent(values, GROUP_ORDER, frontendDescriptors, []);

    const filePath = path.join(tmpDir, 'frontend.env');
    writeEnvFile(filePath, content);

    const written = fs.readFileSync(filePath, 'utf8');
    expect(written).toContain('REACT_APP_API_BASE=http://localhost:3001/api');
    expect(written).toContain('REACT_APP_API_HOST=http://localhost:3001');
    expect(written).toContain('# --- Frontend Settings ---');
  });
});

// ─────────────────────────────────────────────────────────────────────────────
// Test 2: Wizard with existing .env files — values pre-filled correctly
// ─────────────────────────────────────────────────────────────────────────────
describe('Wizard with existing .env files', () => {
  let tmpDir;

  beforeEach(() => {
    tmpDir = createTempDir();
  });

  afterEach(() => {
    removeTempDir(tmpDir);
  });

  test('parseEnvFile reads existing values correctly', () => {
    const envContent = [
      'PORT=4000',
      'API_HOST=192.168.1.100',
      'CORS_ORIGINS=http://myhost:3000',
      'DATABASE_URL="postgresql://user:pass@localhost:5433/mydb"',
      'SESSION_SECRET=my-super-secret-session-key-123',
      'MY_CUSTOM_VAR=preserved',
    ].join('\n');

    const filePath = path.join(tmpDir, '.env');
    fs.writeFileSync(filePath, envContent, 'utf8');

    const result = parseEnvFile(filePath);

    expect(result.managed.get('PORT')).toBe('4000');
    expect(result.managed.get('API_HOST')).toBe('192.168.1.100');
    expect(result.managed.get('CORS_ORIGINS')).toBe('http://myhost:3000');
    expect(result.managed.get('DATABASE_URL')).toBe('postgresql://user:pass@localhost:5433/mydb');
    expect(result.managed.get('SESSION_SECRET')).toBe('my-super-secret-session-key-123');
    expect(result.unmanaged).toContain('MY_CUSTOM_VAR=preserved');
  });

  test('parseEnvFile handles quoted values with spaces', () => {
    const envContent = 'CORS_ORIGINS="http://localhost:3000, http://localhost:8080"\n';
    const filePath = path.join(tmpDir, '.env');
    fs.writeFileSync(filePath, envContent, 'utf8');

    const result = parseEnvFile(filePath);
    expect(result.managed.get('CORS_ORIGINS')).toBe('http://localhost:3000, http://localhost:8080');
  });

  test('parseEnvFile returns empty maps for non-existent file', () => {
    const result = parseEnvFile(path.join(tmpDir, 'nonexistent.env'));
    expect(result.managed.size).toBe(0);
    expect(result.unmanaged.length).toBe(0);
  });
});

// ─────────────────────────────────────────────────────────────────────────────
// Test 3: Wizard with skipped groups — groups absent from output
// ─────────────────────────────────────────────────────────────────────────────
describe('Wizard with skipped groups', () => {
  test('generateEnvContent excludes variables from skipped groups', () => {
    const values = new Map();
    values.set('PORT', '3001');
    values.set('API_HOST', 'localhost');
    values.set('CORS_ORIGINS', 'http://localhost:3000');
    values.set('DATABASE_URL', 'postgresql://steam:pass@localhost:5433/cve_dashboard');
    values.set('SESSION_SECRET', 'a-very-long-secret-key-here-1234');
    // Intentionally NOT setting any Ivanti, Atlas, Jira, CARD, GitLab, NVD values

    const backendDescriptors = VARIABLE_DESCRIPTORS.filter(d => d.target === 'backend');
    const content = generateEnvContent(values, GROUP_ORDER, backendDescriptors, []);

    // Skipped groups should not appear in output
    expect(content).not.toContain('# --- NVD API ---');
    expect(content).not.toContain('# --- Ivanti Integration ---');
    expect(content).not.toContain('# --- Atlas Integration ---');
    expect(content).not.toContain('# --- Jira Integration ---');
    expect(content).not.toContain('# --- CARD Integration ---');
    expect(content).not.toContain('# --- GitLab Integration ---');

    // Required groups should still be present
    expect(content).toContain('# --- Core Settings ---');
    expect(content).toContain('# --- Database ---');
    expect(content).toContain('# --- Session ---');
  });

  test('generateEnvContent includes optional group when values are provided', () => {
    const values = new Map();
    values.set('PORT', '3001');
    values.set('API_HOST', 'localhost');
    values.set('CORS_ORIGINS', 'http://localhost:3000');
    values.set('DATABASE_URL', 'postgresql://steam:pass@localhost:5433/cve_dashboard');
    values.set('SESSION_SECRET', 'a-very-long-secret-key-here-1234');
    values.set('NVD_API_KEY', 'my-nvd-key-12345');

    const backendDescriptors = VARIABLE_DESCRIPTORS.filter(d => d.target === 'backend');
    const content = generateEnvContent(values, GROUP_ORDER, backendDescriptors, []);

    expect(content).toContain('# --- NVD API ---');
    expect(content).toContain('NVD_API_KEY=my-nvd-key-12345');
  });
});

// ─────────────────────────────────────────────────────────────────────────────
// Test 4: Missing project structure — error exit
// ─────────────────────────────────────────────────────────────────────────────
describe('Missing project structure', () => {
  let tmpDir;
  let originalCwd;
  let mockExit;

  beforeEach(() => {
    tmpDir = createTempDir();
    originalCwd = process.cwd();
    mockExit = jest.spyOn(process, 'exit').mockImplementation(() => {
      throw new Error('process.exit called');
    });
  });

  afterEach(() => {
    process.chdir(originalCwd);
    mockExit.mockRestore();
    removeTempDir(tmpDir);
  });

  test('checkProjectRoot exits when backend/ is missing', () => {
    // Create only frontend/
    fs.mkdirSync(path.join(tmpDir, 'frontend'));
    process.chdir(tmpDir);

    expect(() => checkProjectRoot()).toThrow('process.exit called');
    expect(mockExit).toHaveBeenCalledWith(1);
  });

  test('checkProjectRoot exits when frontend/ is missing', () => {
    // Create only backend/
    fs.mkdirSync(path.join(tmpDir, 'backend'));
    process.chdir(tmpDir);

    expect(() => checkProjectRoot()).toThrow('process.exit called');
    expect(mockExit).toHaveBeenCalledWith(1);
  });

  test('checkProjectRoot exits when both are missing', () => {
    process.chdir(tmpDir);

    expect(() => checkProjectRoot()).toThrow('process.exit called');
    expect(mockExit).toHaveBeenCalledWith(1);
  });

  test('checkProjectRoot succeeds when both directories exist', () => {
    fs.mkdirSync(path.join(tmpDir, 'backend'));
    fs.mkdirSync(path.join(tmpDir, 'frontend'));
    process.chdir(tmpDir);

    expect(() => checkProjectRoot()).not.toThrow();
  });
});

// ─────────────────────────────────────────────────────────────────────────────
// Test 5: File write permission error — graceful failure
// ─────────────────────────────────────────────────────────────────────────────
describe('File write permission error', () => {
  let tmpDir;

  beforeEach(() => {
    tmpDir = createTempDir();
  });

  afterEach(() => {
    // Restore permissions before cleanup
    try {
      fs.chmodSync(path.join(tmpDir, 'readonly'), 0o755);
    } catch { /* ignore */ }
    removeTempDir(tmpDir);
  });

  test('writeEnvFile throws on invalid path (non-existent nested directory)', () => {
    // Use a deeply nested non-existent path that will fail regardless of user
    const filePath = path.join(tmpDir, 'no', 'such', 'deep', 'path', '.env');
    expect(() => writeEnvFile(filePath, 'PORT=3001\n')).toThrow();
  });

  test('writeEnvFile succeeds on valid writable path', () => {
    const filePath = path.join(tmpDir, '.env');
    expect(() => writeEnvFile(filePath, 'PORT=3001\n')).not.toThrow();
    expect(fs.readFileSync(filePath, 'utf8')).toBe('PORT=3001\n');
  });
});

// ─────────────────────────────────────────────────────────────────────────────
// Test 6: Infrastructure state detection
// ─────────────────────────────────────────────────────────────────────────────
describe('Infrastructure state detection', () => {
  let tmpDir;
  let originalCwd;

  beforeEach(() => {
    tmpDir = createTempDir();
    originalCwd = process.cwd();
  });

  afterEach(() => {
    process.chdir(originalCwd);
    removeTempDir(tmpDir);
  });

  test('detectInfraState returns correct values based on filesystem state', () => {
    // Set up a minimal project structure
    fs.mkdirSync(path.join(tmpDir, 'backend'));
    fs.mkdirSync(path.join(tmpDir, 'frontend'));
    fs.mkdirSync(path.join(tmpDir, 'backend', 'node_modules'));
    fs.writeFileSync(path.join(tmpDir, 'backend', '.env'), 'PORT=3001\n');
    fs.writeFileSync(path.join(tmpDir, 'backend', 'db-schema.sql'), 'CREATE TABLE test();');
    // No frontend node_modules, no frontend .env, no frontend build

    process.chdir(tmpDir);
    const state = detectInfraState();

    expect(state.backendNodeModules).toBe(true);
    expect(state.frontendNodeModules).toBe(false);
    expect(state.backendEnvExists).toBe(true);
    expect(state.frontendEnvExists).toBe(false);
    expect(state.frontendBuildExists).toBe(false);
    expect(state.schemaFileExists).toBe(true);
    expect(state.sqliteDbExists).toBe(false);
    // npmAvailable should be true in test environment
    expect(typeof state.npmAvailable).toBe('boolean');
    expect(typeof state.dockerAvailable).toBe('boolean');
    expect(typeof state.psqlAvailable).toBe('boolean');
    expect(typeof state.postgresRunning).toBe('boolean');
  });

  test('detectInfraState detects SQLite database when present', () => {
    fs.mkdirSync(path.join(tmpDir, 'backend'));
    fs.mkdirSync(path.join(tmpDir, 'frontend'));
    fs.writeFileSync(path.join(tmpDir, 'backend', 'cve_database.db'), '');

    process.chdir(tmpDir);
    const state = detectInfraState();

    expect(state.sqliteDbExists).toBe(true);
  });

  test('detectInfraState detects frontend build when present', () => {
    fs.mkdirSync(path.join(tmpDir, 'backend'));
    fs.mkdirSync(path.join(tmpDir, 'frontend'));
    fs.mkdirSync(path.join(tmpDir, 'frontend', 'build'), { recursive: true });
    fs.writeFileSync(path.join(tmpDir, 'frontend', 'build', 'index.html'), '<html></html>');

    process.chdir(tmpDir);
    const state = detectInfraState();

    expect(state.frontendBuildExists).toBe(true);
  });
});

// ─────────────────────────────────────────────────────────────────────────────
// Test 7: Frontend build skip on unchanged REACT_APP_* values
// ─────────────────────────────────────────────────────────────────────────────
describe('Frontend build skip on unchanged REACT_APP_* values', () => {
  test('shouldSkipFrontendBuild returns true when REACT_APP_* values are identical', () => {
    const oldEnv = new Map([
      ['REACT_APP_API_BASE', 'http://localhost:3001/api'],
      ['REACT_APP_API_HOST', 'http://localhost:3001'],
    ]);
    const newEnv = new Map([
      ['REACT_APP_API_BASE', 'http://localhost:3001/api'],
      ['REACT_APP_API_HOST', 'http://localhost:3001'],
    ]);

    expect(shouldSkipFrontendBuild(oldEnv, newEnv)).toBe(true);
  });

  test('shouldSkipFrontendBuild returns false when REACT_APP_* values differ', () => {
    const oldEnv = new Map([
      ['REACT_APP_API_BASE', 'http://localhost:3001/api'],
      ['REACT_APP_API_HOST', 'http://localhost:3001'],
    ]);
    const newEnv = new Map([
      ['REACT_APP_API_BASE', 'http://192.168.1.100:4000/api'],
      ['REACT_APP_API_HOST', 'http://192.168.1.100:4000'],
    ]);

    expect(shouldSkipFrontendBuild(oldEnv, newEnv)).toBe(false);
  });

  test('shouldSkipFrontendBuild returns false when oldFrontendEnv is null', () => {
    const newEnv = new Map([
      ['REACT_APP_API_BASE', 'http://localhost:3001/api'],
      ['REACT_APP_API_HOST', 'http://localhost:3001'],
    ]);

    expect(shouldSkipFrontendBuild(null, newEnv)).toBe(false);
  });

  test('shouldSkipFrontendBuild returns false when one REACT_APP_* key differs', () => {
    const oldEnv = new Map([
      ['REACT_APP_API_BASE', 'http://localhost:3001/api'],
      ['REACT_APP_API_HOST', 'http://localhost:3001'],
    ]);
    const newEnv = new Map([
      ['REACT_APP_API_BASE', 'http://localhost:3001/api'],
      ['REACT_APP_API_HOST', 'http://newhost:3001'],
    ]);

    expect(shouldSkipFrontendBuild(oldEnv, newEnv)).toBe(false);
  });
});

// ─────────────────────────────────────────────────────────────────────────────
// Test 8: Node.js version check
// ─────────────────────────────────────────────────────────────────────────────
describe('Node.js version check', () => {
  let mockExit;

  beforeEach(() => {
    mockExit = jest.spyOn(process, 'exit').mockImplementation(() => {
      throw new Error('process.exit called');
    });
  });

  afterEach(() => {
    mockExit.mockRestore();
  });

  test('checkNodeVersion does not exit on current Node.js version (>= 18)', () => {
    // Current test environment should be Node 18+
    expect(() => checkNodeVersion()).not.toThrow();
  });

  test('checkNodeVersion would exit on Node < 18 (simulated via version override)', () => {
    const originalVersion = process.version;
    Object.defineProperty(process, 'version', { value: 'v16.20.0', writable: true });

    try {
      expect(() => checkNodeVersion()).toThrow('process.exit called');
      expect(mockExit).toHaveBeenCalledWith(1);
    } finally {
      Object.defineProperty(process, 'version', { value: originalVersion, writable: true });
    }
  });
});

// ─────────────────────────────────────────────────────────────────────────────
// Test 9: parseDockerCompose with real docker-compose.yml
// ─────────────────────────────────────────────────────────────────────────────
describe('parseDockerCompose with real docker-compose.yml', () => {
  test('correctly parses the project actual docker-compose.yml', () => {
    const composePath = path.join(__dirname, '..', '..', 'docker-compose.yml');
    const result = parseDockerCompose(composePath);

    expect(result).not.toBeNull();
    expect(result.user).toBe('steam');
    expect(result.password).toBe('sV4xmC9xAUCFop0ypxMVS056QgPqGrX');
    expect(result.database).toBe('cve_dashboard');
    expect(result.port).toBe('5433');
  });

  test('parseDockerCompose returns null for non-existent file', () => {
    const result = parseDockerCompose('/nonexistent/docker-compose.yml');
    expect(result).toBeNull();
  });

  test('parseDockerCompose returns null for invalid YAML content', () => {
    const tmpDir = createTempDir();
    const filePath = path.join(tmpDir, 'docker-compose.yml');
    fs.writeFileSync(filePath, 'this is not valid yaml at all\nno services here\n');

    const result = parseDockerCompose(filePath);
    expect(result).toBeNull();

    removeTempDir(tmpDir);
  });

  test('parseDockerCompose handles compose file with shell variable defaults', () => {
    const tmpDir = createTempDir();
    const filePath = path.join(tmpDir, 'docker-compose.yml');
    const content = [
      'services:',
      '  postgres:',
      '    image: postgres:16-alpine',
      '    environment:',
      '      POSTGRES_DB: testdb',
      '      POSTGRES_USER: testuser',
      '      POSTGRES_PASSWORD: ${PG_PASS:-mysecretpass}',
      '    ports:',
      '      - "5434:5432"',
    ].join('\n');
    fs.writeFileSync(filePath, content, 'utf8');

    const result = parseDockerCompose(filePath);
    expect(result).not.toBeNull();
    expect(result.user).toBe('testuser');
    expect(result.password).toBe('mysecretpass');
    expect(result.database).toBe('testdb');
    expect(result.port).toBe('5434');

    removeTempDir(tmpDir);
  });
});

// ─────────────────────────────────────────────────────────────────────────────
// Test 10: parseEnvFile round-trip — write and re-read produces identical values
// ─────────────────────────────────────────────────────────────────────────────
describe('parseEnvFile round-trip', () => {
  let tmpDir;

  beforeEach(() => {
    tmpDir = createTempDir();
  });

  afterEach(() => {
    removeTempDir(tmpDir);
  });

  test('writing and re-reading produces identical managed values', () => {
    const values = new Map();
    values.set('PORT', '3001');
    values.set('API_HOST', 'localhost');
    values.set('CORS_ORIGINS', 'http://localhost:3000');
    values.set('DATABASE_URL', 'postgresql://steam:sV4xmC9xAUCFop0ypxMVS056QgPqGrX@localhost:5433/cve_dashboard');
    values.set('SESSION_SECRET', 'my-session-secret-at-least-16-chars');

    const backendDescriptors = VARIABLE_DESCRIPTORS.filter(d => d.target === 'backend');
    const content = generateEnvContent(values, GROUP_ORDER, backendDescriptors, []);

    const filePath = path.join(tmpDir, '.env');
    writeEnvFile(filePath, content);

    const parsed = parseEnvFile(filePath);

    // All values we set should be recovered
    for (const [key, value] of values) {
      const descriptor = VARIABLE_DESCRIPTORS.find(d => d.name === key);
      if (descriptor && descriptor.target === 'backend') {
        expect(parsed.managed.get(key)).toBe(value);
      }
    }
  });

  test('round-trip preserves values with special characters', () => {
    const values = new Map();
    values.set('PORT', '3001');
    values.set('API_HOST', 'localhost');
    values.set('CORS_ORIGINS', 'http://localhost:3000');
    values.set('DATABASE_URL', 'postgresql://user:p@ss$word@localhost:5433/db');
    values.set('SESSION_SECRET', 'secret with spaces and #hash and $dollar');

    const backendDescriptors = VARIABLE_DESCRIPTORS.filter(d => d.target === 'backend');
    const content = generateEnvContent(values, GROUP_ORDER, backendDescriptors, []);

    const filePath = path.join(tmpDir, '.env');
    writeEnvFile(filePath, content);

    const parsed = parseEnvFile(filePath);

    expect(parsed.managed.get('PORT')).toBe('3001');
    expect(parsed.managed.get('API_HOST')).toBe('localhost');
    // Values with special chars are quoted, parseEnvFile strips quotes
    expect(parsed.managed.get('SESSION_SECRET')).toBe('secret with spaces and #hash and $dollar');
  });
});

// ─────────────────────────────────────────────────────────────────────────────
// Test 11: generateEnvContent with all groups — complete output format
// ─────────────────────────────────────────────────────────────────────────────
describe('generateEnvContent with all groups', () => {
  test('produces complete output with all group headers and values', () => {
    const values = new Map();
    // Core Settings
    values.set('PORT', '3001');
    values.set('API_HOST', 'localhost');
    values.set('CORS_ORIGINS', 'http://localhost:3000');
    // Database
    values.set('DATABASE_URL', 'postgresql://steam:pass@localhost:5433/cve_dashboard');
    // Session
    values.set('SESSION_SECRET', 'a-very-long-secret-key-here-1234');
    // NVD API
    values.set('NVD_API_KEY', 'nvd-key-123');
    // Ivanti
    values.set('IVANTI_API_KEY', 'ivanti-key-456');
    values.set('IVANTI_CLIENT_ID', '1550');
    // Atlas
    values.set('ATLAS_API_URL', 'https://atlas.example.com');
    values.set('ATLAS_API_USER', 'atlasuser');
    values.set('ATLAS_API_PASS', 'atlaspass');
    // Jira
    values.set('JIRA_BASE_URL', 'https://jira.example.com');
    values.set('JIRA_AUTH_METHOD', 'basic');
    values.set('JIRA_API_USER', 'jirauser');
    values.set('JIRA_API_TOKEN', 'jira-token-789');
    // CARD
    values.set('CARD_API_URL', 'https://card.example.com');
    values.set('CARD_API_USER', 'carduser');
    values.set('CARD_API_PASS', 'cardpass');
    // GitLab
    values.set('GITLAB_URL', 'http://steam-gitlab.charterlab.com');
    values.set('GITLAB_PROJECT_ID', '42');
    values.set('GITLAB_PAT', 'glpat-abc123');

    const backendDescriptors = VARIABLE_DESCRIPTORS.filter(d => d.target === 'backend');
    const content = generateEnvContent(values, GROUP_ORDER, backendDescriptors, []);

    // Verify all group headers present
    expect(content).toContain('# --- Core Settings ---');
    expect(content).toContain('# --- Database ---');
    expect(content).toContain('# --- Session ---');
    expect(content).toContain('# --- NVD API ---');
    expect(content).toContain('# --- Ivanti Integration ---');
    expect(content).toContain('# --- Atlas Integration ---');
    expect(content).toContain('# --- Jira Integration ---');
    expect(content).toContain('# --- CARD Integration ---');
    expect(content).toContain('# --- GitLab Integration ---');

    // Verify values present
    expect(content).toContain('PORT=3001');
    expect(content).toContain('NVD_API_KEY=nvd-key-123');
    expect(content).toContain('IVANTI_CLIENT_ID=1550');
    expect(content).toContain('GITLAB_PROJECT_ID=42');

    // Verify LF line endings (no \r)
    expect(content).not.toContain('\r');
    // Verify trailing newline
    expect(content.endsWith('\n')).toBe(true);
  });
});

// ─────────────────────────────────────────────────────────────────────────────
// Test 12: generateEnvContent with skipped groups — excluded from output
// ─────────────────────────────────────────────────────────────────────────────
describe('generateEnvContent with skipped groups', () => {
  test('skipped groups produce no KEY=value lines in output', () => {
    const values = new Map();
    // Only set required group values
    values.set('PORT', '3001');
    values.set('API_HOST', 'localhost');
    values.set('CORS_ORIGINS', 'http://localhost:3000');
    values.set('DATABASE_URL', 'postgresql://steam:pass@localhost:5433/cve_dashboard');
    values.set('SESSION_SECRET', 'a-very-long-secret-key-here-1234');

    const backendDescriptors = VARIABLE_DESCRIPTORS.filter(d => d.target === 'backend');
    const content = generateEnvContent(values, GROUP_ORDER, backendDescriptors, []);

    // Verify no optional group variables appear
    const optionalVarNames = VARIABLE_DESCRIPTORS
      .filter(d => OPTIONAL_GROUPS.includes(d.group))
      .map(d => d.name);

    for (const varName of optionalVarNames) {
      // Should not have any KEY= line for these variables
      const regex = new RegExp(`^${varName}=`, 'm');
      expect(content).not.toMatch(regex);
    }
  });

  test('partial optional groups — only configured groups appear', () => {
    const values = new Map();
    values.set('PORT', '3001');
    values.set('API_HOST', 'localhost');
    values.set('CORS_ORIGINS', 'http://localhost:3000');
    values.set('DATABASE_URL', 'postgresql://steam:pass@localhost:5433/cve_dashboard');
    values.set('SESSION_SECRET', 'a-very-long-secret-key-here-1234');
    // Only configure Jira
    values.set('JIRA_BASE_URL', 'https://jira.example.com');
    values.set('JIRA_API_USER', 'user');
    values.set('JIRA_API_TOKEN', 'token-value-here');

    const backendDescriptors = VARIABLE_DESCRIPTORS.filter(d => d.target === 'backend');
    const content = generateEnvContent(values, GROUP_ORDER, backendDescriptors, []);

    expect(content).toContain('# --- Jira Integration ---');
    expect(content).toContain('JIRA_BASE_URL=https://jira.example.com');
    // Other optional groups should not appear
    expect(content).not.toContain('# --- NVD API ---');
    expect(content).not.toContain('# --- Ivanti Integration ---');
    expect(content).not.toContain('# --- Atlas Integration ---');
    expect(content).not.toContain('# --- CARD Integration ---');
    expect(content).not.toContain('# --- GitLab Integration ---');
  });
});

// ─────────────────────────────────────────────────────────────────────────────
// Test 13: createBackup — backup file creation with timestamp naming
// ─────────────────────────────────────────────────────────────────────────────
describe('createBackup', () => {
  let tmpDir;

  beforeEach(() => {
    tmpDir = createTempDir();
  });

  afterEach(() => {
    removeTempDir(tmpDir);
  });

  test('creates backup file with timestamp naming', () => {
    const originalPath = path.join(tmpDir, '.env');
    fs.writeFileSync(originalPath, 'PORT=3001\nAPI_HOST=localhost\n');

    const backupPath = createBackup(originalPath);

    expect(fs.existsSync(backupPath)).toBe(true);
    // Backup should match pattern: .env.backup.YYYYMMDD_HHmmss
    expect(backupPath).toMatch(/\.env\.backup\.\d{8}_\d{6}$/);
    // Content should be identical
    const originalContent = fs.readFileSync(originalPath, 'utf8');
    const backupContent = fs.readFileSync(backupPath, 'utf8');
    expect(backupContent).toBe(originalContent);
  });

  test('creates numbered backup when timestamp backup already exists', () => {
    const originalPath = path.join(tmpDir, '.env');
    fs.writeFileSync(originalPath, 'PORT=3001\n');

    // Create first backup
    const firstBackup = createBackup(originalPath);
    expect(fs.existsSync(firstBackup)).toBe(true);

    // Modify original
    fs.writeFileSync(originalPath, 'PORT=4000\n');

    // Create second backup — since timestamp is same second, it should use .bak.N
    // We simulate by creating the expected timestamp backup manually
    const now = new Date();
    const timestamp = now.getFullYear().toString() +
      String(now.getMonth() + 1).padStart(2, '0') +
      String(now.getDate()).padStart(2, '0') + '_' +
      String(now.getHours()).padStart(2, '0') +
      String(now.getMinutes()).padStart(2, '0') +
      String(now.getSeconds()).padStart(2, '0');
    const expectedTimestampPath = `${originalPath}.backup.${timestamp}`;

    // If the timestamp backup already exists (from first call), second call uses .bak.N
    if (fs.existsSync(expectedTimestampPath)) {
      const secondBackup = createBackup(originalPath);
      expect(secondBackup).toMatch(/\.bak\.\d+$/);
      expect(fs.existsSync(secondBackup)).toBe(true);
      const content = fs.readFileSync(secondBackup, 'utf8');
      expect(content).toBe('PORT=4000\n');
    }
  });

  test('backup preserves file content exactly', () => {
    const originalPath = path.join(tmpDir, '.env');
    const content = '# --- Core Settings ---\nPORT=3001\nAPI_HOST=localhost\n\n# Custom\nMY_VAR=hello\n';
    fs.writeFileSync(originalPath, content);

    const backupPath = createBackup(originalPath);
    const backupContent = fs.readFileSync(backupPath, 'utf8');
    expect(backupContent).toBe(content);
  });
});
