/**
 * Tests for scripts/lib/hook-flags.js
 *
 * Run with: node tests/hooks/hook-flags.test.js
 */

const assert = require('assert');

// Import the module
const {
  VALID_PROFILES,
  normalizeId,
  getHookProfile,
  getDisabledHookIds,
  parseProfiles,
  isHookEnabled,
} = require('../../scripts/lib/hook-flags');

// Test helper
function test(name, fn) {
  try {
    fn();
    console.log(`  ✓ ${name}`);
    return true;
  } catch (err) {
    console.log(`  ✗ ${name}`);
    console.log(`    Error: ${err.message}`);
    return false;
  }
}

// Helper to save and restore env vars
function withEnv(vars, fn) {
  const saved = {};
  for (const key of Object.keys(vars)) {
    saved[key] = process.env[key];
    if (vars[key] === undefined) {
      delete process.env[key];
    } else {
      process.env[key] = vars[key];
    }
  }
  try {
    fn();
  } finally {
    for (const key of Object.keys(saved)) {
      if (saved[key] === undefined) {
        delete process.env[key];
      } else {
        process.env[key] = saved[key];
      }
    }
  }
}

// Test suite
function runTests() {
  console.log('\n=== Testing hook-flags.js ===\n');

  let passed = 0;
  let failed = 0;

  // VALID_PROFILES tests
  console.log('VALID_PROFILES:');

  if (test('is a Set', () => {
    assert.ok(VALID_PROFILES instanceof Set);
  })) passed++; else failed++;

  if (test('contains minimal, standard, strict', () => {
    assert.ok(VALID_PROFILES.has('minimal'));
    assert.ok(VALID_PROFILES.has('standard'));
    assert.ok(VALID_PROFILES.has('strict'));
  })) passed++; else failed++;

  if (test('contains exactly 3 profiles', () => {
    assert.strictEqual(VALID_PROFILES.size, 3);
  })) passed++; else failed++;

  // normalizeId tests
  console.log('\nnormalizeId:');

  if (test('returns empty string for null', () => {
    assert.strictEqual(normalizeId(null), '');
  })) passed++; else failed++;

  if (test('returns empty string for undefined', () => {
    assert.strictEqual(normalizeId(undefined), '');
  })) passed++; else failed++;

  if (test('returns empty string for empty string', () => {
    assert.strictEqual(normalizeId(''), '');
  })) passed++; else failed++;

  if (test('trims whitespace', () => {
    assert.strictEqual(normalizeId('  hello  '), 'hello');
  })) passed++; else failed++;

  if (test('converts to lowercase', () => {
    assert.strictEqual(normalizeId('MyHook'), 'myhook');
  })) passed++; else failed++;

  if (test('handles mixed case with whitespace', () => {
    assert.strictEqual(normalizeId('  My-Hook-ID  '), 'my-hook-id');
  })) passed++; else failed++;

  if (test('converts numbers to string', () => {
    assert.strictEqual(normalizeId(123), '123');
  })) passed++; else failed++;

  if (test('returns empty string for whitespace-only input', () => {
    assert.strictEqual(normalizeId('   '), '');
  })) passed++; else failed++;

  // getHookProfile tests
  console.log('\ngetHookProfile:');

  if (test('defaults to standard when env var not set', () => {
    withEnv({ ECC_HOOK_PROFILE: undefined }, () => {
      assert.strictEqual(getHookProfile(), 'standard');
    });
  })) passed++; else failed++;

  if (test('returns minimal when set to minimal', () => {
    withEnv({ ECC_HOOK_PROFILE: 'minimal' }, () => {
      assert.strictEqual(getHookProfile(), 'minimal');
    });
  })) passed++; else failed++;

  if (test('returns standard when set to standard', () => {
    withEnv({ ECC_HOOK_PROFILE: 'standard' }, () => {
      assert.strictEqual(getHookProfile(), 'standard');
    });
  })) passed++; else failed++;

  if (test('returns strict when set to strict', () => {
    withEnv({ ECC_HOOK_PROFILE: 'strict' }, () => {
      assert.strictEqual(getHookProfile(), 'strict');
    });
  })) passed++; else failed++;

  if (test('is case-insensitive', () => {
    withEnv({ ECC_HOOK_PROFILE: 'STRICT' }, () => {
      assert.strictEqual(getHookProfile(), 'strict');
    });
  })) passed++; else failed++;

  if (test('trims whitespace from env var', () => {
    withEnv({ ECC_HOOK_PROFILE: '  minimal  ' }, () => {
      assert.strictEqual(getHookProfile(), 'minimal');
    });
  })) passed++; else failed++;

  if (test('defaults to standard for invalid value', () => {
    withEnv({ ECC_HOOK_PROFILE: 'invalid' }, () => {
      assert.strictEqual(getHookProfile(), 'standard');
    });
  })) passed++; else failed++;

  if (test('defaults to standard for empty string', () => {
    withEnv({ ECC_HOOK_PROFILE: '' }, () => {
      assert.strictEqual(getHookProfile(), 'standard');
    });
  })) passed++; else failed++;

  // getDisabledHookIds tests
  console.log('\ngetDisabledHookIds:');

  if (test('returns empty Set when env var not set', () => {
    withEnv({ ECC_DISABLED_HOOKS: undefined }, () => {
      const result = getDisabledHookIds();
      assert.ok(result instanceof Set);
      assert.strictEqual(result.size, 0);
    });
  })) passed++; else failed++;

  if (test('returns empty Set for empty string', () => {
    withEnv({ ECC_DISABLED_HOOKS: '' }, () => {
      assert.strictEqual(getDisabledHookIds().size, 0);
    });
  })) passed++; else failed++;

  if (test('returns empty Set for whitespace-only string', () => {
    withEnv({ ECC_DISABLED_HOOKS: '   ' }, () => {
      assert.strictEqual(getDisabledHookIds().size, 0);
    });
  })) passed++; else failed++;

  if (test('parses single hook id', () => {
    withEnv({ ECC_DISABLED_HOOKS: 'my-hook' }, () => {
      const result = getDisabledHookIds();
      assert.strictEqual(result.size, 1);
      assert.ok(result.has('my-hook'));
    });
  })) passed++; else failed++;

  if (test('parses multiple comma-separated hook ids', () => {
    withEnv({ ECC_DISABLED_HOOKS: 'hook-a,hook-b,hook-c' }, () => {
      const result = getDisabledHookIds();
      assert.strictEqual(result.size, 3);
      assert.ok(result.has('hook-a'));
      assert.ok(result.has('hook-b'));
      assert.ok(result.has('hook-c'));
    });
  })) passed++; else failed++;

  if (test('trims whitespace around hook ids', () => {
    withEnv({ ECC_DISABLED_HOOKS: ' hook-a , hook-b ' }, () => {
      const result = getDisabledHookIds();
      assert.strictEqual(result.size, 2);
      assert.ok(result.has('hook-a'));
      assert.ok(result.has('hook-b'));
    });
  })) passed++; else failed++;

  if (test('normalizes hook ids to lowercase', () => {
    withEnv({ ECC_DISABLED_HOOKS: 'MyHook,ANOTHER' }, () => {
      const result = getDisabledHookIds();
      assert.ok(result.has('myhook'));
      assert.ok(result.has('another'));
    });
  })) passed++; else failed++;

  if (test('filters out empty entries from trailing commas', () => {
    withEnv({ ECC_DISABLED_HOOKS: 'hook-a,,hook-b,' }, () => {
      const result = getDisabledHookIds();
      assert.strictEqual(result.size, 2);
      assert.ok(result.has('hook-a'));
      assert.ok(result.has('hook-b'));
    });
  })) passed++; else failed++;

  // parseProfiles tests
  console.log('\nparseProfiles:');

  if (test('returns fallback for null input', () => {
    const result = parseProfiles(null);
    assert.deepStrictEqual(result, ['standard', 'strict']);
  })) passed++; else failed++;

  if (test('returns fallback for undefined input', () => {
    const result = parseProfiles(undefined);
    assert.deepStrictEqual(result, ['standard', 'strict']);
  })) passed++; else failed++;

  if (test('uses custom fallback when provided', () => {
    const result = parseProfiles(null, ['minimal']);
    assert.deepStrictEqual(result, ['minimal']);
  })) passed++; else failed++;

  if (test('parses comma-separated string', () => {
    const result = parseProfiles('minimal,strict');
    assert.deepStrictEqual(result, ['minimal', 'strict']);
  })) passed++; else failed++;

  if (test('parses single string value', () => {
    const result = parseProfiles('strict');
    assert.deepStrictEqual(result, ['strict']);
  })) passed++; else failed++;

  if (test('parses array of profiles', () => {
    const result = parseProfiles(['minimal', 'standard']);
    assert.deepStrictEqual(result, ['minimal', 'standard']);
  })) passed++; else failed++;

  if (test('filters invalid profiles from string', () => {
    const result = parseProfiles('minimal,invalid,strict');
    assert.deepStrictEqual(result, ['minimal', 'strict']);
  })) passed++; else failed++;

  if (test('filters invalid profiles from array', () => {
    const result = parseProfiles(['minimal', 'bogus', 'strict']);
    assert.deepStrictEqual(result, ['minimal', 'strict']);
  })) passed++; else failed++;

  if (test('returns fallback when all string values are invalid', () => {
    const result = parseProfiles('invalid,bogus');
    assert.deepStrictEqual(result, ['standard', 'strict']);
  })) passed++; else failed++;

  if (test('returns fallback when all array values are invalid', () => {
    const result = parseProfiles(['invalid', 'bogus']);
    assert.deepStrictEqual(result, ['standard', 'strict']);
  })) passed++; else failed++;

  if (test('is case-insensitive for string input', () => {
    const result = parseProfiles('MINIMAL,STRICT');
    assert.deepStrictEqual(result, ['minimal', 'strict']);
  })) passed++; else failed++;

  if (test('is case-insensitive for array input', () => {
    const result = parseProfiles(['MINIMAL', 'STRICT']);
    assert.deepStrictEqual(result, ['minimal', 'strict']);
  })) passed++; else failed++;

  if (test('trims whitespace in string input', () => {
    const result = parseProfiles(' minimal , strict ');
    assert.deepStrictEqual(result, ['minimal', 'strict']);
  })) passed++; else failed++;

  if (test('handles null values in array', () => {
    const result = parseProfiles([null, 'strict']);
    assert.deepStrictEqual(result, ['strict']);
  })) passed++; else failed++;

  // isHookEnabled tests
  console.log('\nisHookEnabled:');

  if (test('returns true by default for a hook (standard profile)', () => {
    withEnv({ ECC_HOOK_PROFILE: undefined, ECC_DISABLED_HOOKS: undefined }, () => {
      assert.strictEqual(isHookEnabled('my-hook'), true);
    });
  })) passed++; else failed++;

  if (test('returns true for empty hookId', () => {
    withEnv({ ECC_HOOK_PROFILE: undefined, ECC_DISABLED_HOOKS: undefined }, () => {
      assert.strictEqual(isHookEnabled(''), true);
    });
  })) passed++; else failed++;

  if (test('returns true for null hookId', () => {
    withEnv({ ECC_HOOK_PROFILE: undefined, ECC_DISABLED_HOOKS: undefined }, () => {
      assert.strictEqual(isHookEnabled(null), true);
    });
  })) passed++; else failed++;

  if (test('returns false when hook is in disabled list', () => {
    withEnv({ ECC_HOOK_PROFILE: undefined, ECC_DISABLED_HOOKS: 'my-hook' }, () => {
      assert.strictEqual(isHookEnabled('my-hook'), false);
    });
  })) passed++; else failed++;

  if (test('disabled check is case-insensitive', () => {
    withEnv({ ECC_HOOK_PROFILE: undefined, ECC_DISABLED_HOOKS: 'MY-HOOK' }, () => {
      assert.strictEqual(isHookEnabled('my-hook'), false);
    });
  })) passed++; else failed++;

  if (test('returns true when hook is not in disabled list', () => {
    withEnv({ ECC_HOOK_PROFILE: undefined, ECC_DISABLED_HOOKS: 'other-hook' }, () => {
      assert.strictEqual(isHookEnabled('my-hook'), true);
    });
  })) passed++; else failed++;

  if (test('returns false when current profile is not in allowed profiles', () => {
    withEnv({ ECC_HOOK_PROFILE: 'minimal', ECC_DISABLED_HOOKS: undefined }, () => {
      assert.strictEqual(isHookEnabled('my-hook', { profiles: 'strict' }), false);
    });
  })) passed++; else failed++;

  if (test('returns true when current profile is in allowed profiles', () => {
    withEnv({ ECC_HOOK_PROFILE: 'strict', ECC_DISABLED_HOOKS: undefined }, () => {
      assert.strictEqual(isHookEnabled('my-hook', { profiles: 'standard,strict' }), true);
    });
  })) passed++; else failed++;

  if (test('returns true when current profile matches single allowed profile', () => {
    withEnv({ ECC_HOOK_PROFILE: 'minimal', ECC_DISABLED_HOOKS: undefined }, () => {
      assert.strictEqual(isHookEnabled('my-hook', { profiles: 'minimal' }), true);
    });
  })) passed++; else failed++;

  if (test('disabled hooks take precedence over profile match', () => {
    withEnv({ ECC_HOOK_PROFILE: 'strict', ECC_DISABLED_HOOKS: 'my-hook' }, () => {
      assert.strictEqual(isHookEnabled('my-hook', { profiles: 'strict' }), false);
    });
  })) passed++; else failed++;

  if (test('uses default profiles (standard, strict) when none specified', () => {
    withEnv({ ECC_HOOK_PROFILE: 'minimal', ECC_DISABLED_HOOKS: undefined }, () => {
      assert.strictEqual(isHookEnabled('my-hook'), false);
    });
  })) passed++; else failed++;

  if (test('allows standard profile by default', () => {
    withEnv({ ECC_HOOK_PROFILE: 'standard', ECC_DISABLED_HOOKS: undefined }, () => {
      assert.strictEqual(isHookEnabled('my-hook'), true);
    });
  })) passed++; else failed++;

  if (test('allows strict profile by default', () => {
    withEnv({ ECC_HOOK_PROFILE: 'strict', ECC_DISABLED_HOOKS: undefined }, () => {
      assert.strictEqual(isHookEnabled('my-hook'), true);
    });
  })) passed++; else failed++;

  if (test('accepts array profiles option', () => {
    withEnv({ ECC_HOOK_PROFILE: 'minimal', ECC_DISABLED_HOOKS: undefined }, () => {
      assert.strictEqual(isHookEnabled('my-hook', { profiles: ['minimal', 'standard'] }), true);
    });
  })) passed++; else failed++;

  console.log(`\nResults: Passed: ${passed}, Failed: ${failed}`);
  process.exit(failed > 0 ? 1 : 0);
}

runTests();
