/**
 * Tests for scripts/lib/session-aliases.js
 *
 * These tests use a temporary directory to avoid touching
 * the real ~/.claude/session-aliases.json.
 *
 * Run with: node tests/lib/session-aliases.test.js
 */

const assert = require('assert');
const path = require('path');
const fs = require('fs');
const os = require('os');

// We need to mock getClaudeDir to point to a temp dir.
// The simplest approach: set HOME to a temp dir before requiring the module.
const tmpHome = path.join(os.tmpdir(), `ecc-alias-test-${Date.now()}`);
fs.mkdirSync(path.join(tmpHome, '.claude'), { recursive: true });
const origHome = process.env.HOME;
process.env.HOME = tmpHome;

const aliases = require('../../scripts/lib/session-aliases');

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

function resetAliases() {
  const aliasesPath = aliases.getAliasesPath();
  try {
    if (fs.existsSync(aliasesPath)) {
      fs.unlinkSync(aliasesPath);
    }
  } catch {
    // ignore
  }
}

function runTests() {
  console.log('\n=== Testing session-aliases.js ===\n');

  let passed = 0;
  let failed = 0;

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

  if (test('returns default structure when no file exists', () => {
    resetAliases();
    const data = aliases.loadAliases();
    assert.ok(data.aliases);
    assert.strictEqual(typeof data.aliases, 'object');
    assert.ok(data.version);
    assert.ok(data.metadata);
  })) passed++; else failed++;

  if (test('returns default structure for corrupted JSON', () => {
    const aliasesPath = aliases.getAliasesPath();
    fs.writeFileSync(aliasesPath, 'NOT VALID JSON!!!');
    const data = aliases.loadAliases();
    assert.ok(data.aliases);
    assert.strictEqual(typeof data.aliases, 'object');
    resetAliases();
  })) passed++; else failed++;

  if (test('returns default structure for invalid structure', () => {
    const aliasesPath = aliases.getAliasesPath();
    fs.writeFileSync(aliasesPath, JSON.stringify({ noAliasesKey: true }));
    const data = aliases.loadAliases();
    assert.ok(data.aliases);
    assert.strictEqual(Object.keys(data.aliases).length, 0);
    resetAliases();
  })) passed++; else failed++;

  // setAlias tests
  console.log('\nsetAlias:');

  if (test('creates a new alias', () => {
    resetAliases();
    const result = aliases.setAlias('my-session', '/path/to/session', 'Test Session');
    assert.strictEqual(result.success, true);
    assert.strictEqual(result.isNew, true);
    assert.strictEqual(result.alias, 'my-session');
  })) passed++; else failed++;

  if (test('updates an existing alias', () => {
    const result = aliases.setAlias('my-session', '/new/path', 'Updated');
    assert.strictEqual(result.success, true);
    assert.strictEqual(result.isNew, false);
  })) passed++; else failed++;

  if (test('rejects empty alias name', () => {
    const result = aliases.setAlias('', '/path');
    assert.strictEqual(result.success, false);
    assert.ok(result.error.includes('empty'));
  })) passed++; else failed++;

  if (test('rejects null alias name', () => {
    const result = aliases.setAlias(null, '/path');
    assert.strictEqual(result.success, false);
  })) passed++; else failed++;

  if (test('rejects invalid characters in alias', () => {
    const result = aliases.setAlias('my alias!', '/path');
    assert.strictEqual(result.success, false);
    assert.ok(result.error.includes('letters'));
  })) passed++; else failed++;

  if (test('rejects alias longer than 128 chars', () => {
    const result = aliases.setAlias('a'.repeat(129), '/path');
    assert.strictEqual(result.success, false);
    assert.ok(result.error.includes('128'));
  })) passed++; else failed++;

  if (test('rejects reserved alias names', () => {
    const reserved = ['list', 'help', 'remove', 'delete', 'create', 'set'];
    for (const name of reserved) {
      const result = aliases.setAlias(name, '/path');
      assert.strictEqual(result.success, false, `Should reject '${name}'`);
      assert.ok(result.error.includes('reserved'), `Should say reserved for '${name}'`);
    }
  })) passed++; else failed++;

  if (test('rejects empty session path', () => {
    const result = aliases.setAlias('valid-name', '');
    assert.strictEqual(result.success, false);
    assert.ok(result.error.includes('empty'));
  })) passed++; else failed++;

  if (test('accepts underscores and dashes in alias', () => {
    resetAliases();
    const result = aliases.setAlias('my_session-v2', '/path');
    assert.strictEqual(result.success, true);
  })) passed++; else failed++;

  // resolveAlias tests
  console.log('\nresolveAlias:');

  if (test('resolves existing alias', () => {
    resetAliases();
    aliases.setAlias('test-resolve', '/session/path', 'Title');
    const result = aliases.resolveAlias('test-resolve');
    assert.ok(result);
    assert.strictEqual(result.alias, 'test-resolve');
    assert.strictEqual(result.sessionPath, '/session/path');
    assert.strictEqual(result.title, 'Title');
  })) passed++; else failed++;

  if (test('returns null for non-existent alias', () => {
    const result = aliases.resolveAlias('nonexistent');
    assert.strictEqual(result, null);
  })) passed++; else failed++;

  if (test('returns null for null/undefined input', () => {
    assert.strictEqual(aliases.resolveAlias(null), null);
    assert.strictEqual(aliases.resolveAlias(undefined), null);
    assert.strictEqual(aliases.resolveAlias(''), null);
  })) passed++; else failed++;

  if (test('returns null for invalid alias characters', () => {
    assert.strictEqual(aliases.resolveAlias('invalid alias!'), null);
    assert.strictEqual(aliases.resolveAlias('path/traversal'), null);
  })) passed++; else failed++;

  // listAliases tests
  console.log('\nlistAliases:');

  if (test('lists all aliases sorted by recency', () => {
    resetAliases();
    // Manually create aliases with different timestamps to test sort
    const data = aliases.loadAliases();
    data.aliases['old-one'] = {
      sessionPath: '/path/old',
      createdAt: '2026-01-01T00:00:00.000Z',
      updatedAt: '2026-01-01T00:00:00.000Z',
      title: null
    };
    data.aliases['new-one'] = {
      sessionPath: '/path/new',
      createdAt: '2026-02-01T00:00:00.000Z',
      updatedAt: '2026-02-01T00:00:00.000Z',
      title: null
    };
    aliases.saveAliases(data);
    const list = aliases.listAliases();
    assert.strictEqual(list.length, 2);
    // Most recently updated should come first
    assert.strictEqual(list[0].name, 'new-one');
    assert.strictEqual(list[1].name, 'old-one');
  })) passed++; else failed++;

  if (test('filters aliases by search string', () => {
    const list = aliases.listAliases({ search: 'old' });
    assert.strictEqual(list.length, 1);
    assert.strictEqual(list[0].name, 'old-one');
  })) passed++; else failed++;

  if (test('limits number of results', () => {
    const list = aliases.listAliases({ limit: 1 });
    assert.strictEqual(list.length, 1);
  })) passed++; else failed++;

  if (test('returns empty array when no aliases exist', () => {
    resetAliases();
    const list = aliases.listAliases();
    assert.strictEqual(list.length, 0);
  })) passed++; else failed++;

  if (test('search is case-insensitive', () => {
    resetAliases();
    aliases.setAlias('MyProject', '/path');
    const list = aliases.listAliases({ search: 'myproject' });
    assert.strictEqual(list.length, 1);
  })) passed++; else failed++;

  // deleteAlias tests
  console.log('\ndeleteAlias:');

  if (test('deletes existing alias', () => {
    resetAliases();
    aliases.setAlias('to-delete', '/path');
    const result = aliases.deleteAlias('to-delete');
    assert.strictEqual(result.success, true);
    assert.strictEqual(result.alias, 'to-delete');

    // Verify it's gone
    assert.strictEqual(aliases.resolveAlias('to-delete'), null);
  })) passed++; else failed++;

  if (test('returns error for non-existent alias', () => {
    const result = aliases.deleteAlias('nonexistent');
    assert.strictEqual(result.success, false);
    assert.ok(result.error.includes('not found'));
  })) passed++; else failed++;

  // renameAlias tests
  console.log('\nrenameAlias:');

  if (test('renames existing alias', () => {
    resetAliases();
    aliases.setAlias('original', '/path', 'My Session');
    const result = aliases.renameAlias('original', 'renamed');
    assert.strictEqual(result.success, true);
    assert.strictEqual(result.oldAlias, 'original');
    assert.strictEqual(result.newAlias, 'renamed');

    // Verify old is gone, new exists
    assert.strictEqual(aliases.resolveAlias('original'), null);
    assert.ok(aliases.resolveAlias('renamed'));
  })) passed++; else failed++;

  if (test('rejects rename to existing alias', () => {
    resetAliases();
    aliases.setAlias('alias-a', '/path/a');
    aliases.setAlias('alias-b', '/path/b');
    const result = aliases.renameAlias('alias-a', 'alias-b');
    assert.strictEqual(result.success, false);
    assert.ok(result.error.includes('already exists'));
  })) passed++; else failed++;

  if (test('rejects rename of non-existent alias', () => {
    const result = aliases.renameAlias('nonexistent', 'new-name');
    assert.strictEqual(result.success, false);
    assert.ok(result.error.includes('not found'));
  })) passed++; else failed++;

  if (test('rejects rename to invalid characters', () => {
    resetAliases();
    aliases.setAlias('valid', '/path');
    const result = aliases.renameAlias('valid', 'invalid name!');
    assert.strictEqual(result.success, false);
  })) passed++; else failed++;

  // updateAliasTitle tests
  console.log('\nupdateAliasTitle:');

  if (test('updates title of existing alias', () => {
    resetAliases();
    aliases.setAlias('titled', '/path', 'Old Title');
    const result = aliases.updateAliasTitle('titled', 'New Title');
    assert.strictEqual(result.success, true);
    assert.strictEqual(result.title, 'New Title');
  })) passed++; else failed++;

  if (test('clears title with null', () => {
    const result = aliases.updateAliasTitle('titled', null);
    assert.strictEqual(result.success, true);
    const resolved = aliases.resolveAlias('titled');
    assert.strictEqual(resolved.title, null);
  })) passed++; else failed++;

  if (test('rejects non-string non-null title', () => {
    const result = aliases.updateAliasTitle('titled', 42);
    assert.strictEqual(result.success, false);
    assert.ok(result.error.includes('string'));
  })) passed++; else failed++;

  if (test('rejects title update for non-existent alias', () => {
    const result = aliases.updateAliasTitle('nonexistent', 'Title');
    assert.strictEqual(result.success, false);
    assert.ok(result.error.includes('not found'));
  })) passed++; else failed++;

  // resolveSessionAlias tests
  console.log('\nresolveSessionAlias:');

  if (test('resolves alias to session path', () => {
    resetAliases();
    aliases.setAlias('shortcut', '/sessions/my-session');
    const result = aliases.resolveSessionAlias('shortcut');
    assert.strictEqual(result, '/sessions/my-session');
  })) passed++; else failed++;

  if (test('returns input as-is when not an alias', () => {
    const result = aliases.resolveSessionAlias('/some/direct/path');
    assert.strictEqual(result, '/some/direct/path');
  })) passed++; else failed++;

  // getAliasesForSession tests
  console.log('\ngetAliasesForSession:');

  if (test('finds all aliases for a session path', () => {
    resetAliases();
    aliases.setAlias('alias-1', '/sessions/target');
    aliases.setAlias('alias-2', '/sessions/target');
    aliases.setAlias('other', '/sessions/different');

    const result = aliases.getAliasesForSession('/sessions/target');
    assert.strictEqual(result.length, 2);
    const names = result.map(a => a.name).sort();
    assert.deepStrictEqual(names, ['alias-1', 'alias-2']);
  })) passed++; else failed++;

  if (test('returns empty array for session with no aliases', () => {
    const result = aliases.getAliasesForSession('/sessions/no-aliases');
    assert.strictEqual(result.length, 0);
  })) passed++; else failed++;

  // cleanupAliases tests
  console.log('\ncleanupAliases:');

  if (test('removes aliases for non-existent sessions', () => {
    resetAliases();
    aliases.setAlias('exists', '/sessions/real');
    aliases.setAlias('gone', '/sessions/deleted');
    aliases.setAlias('also-gone', '/sessions/also-deleted');

    const result = aliases.cleanupAliases((path) => path === '/sessions/real');
    assert.strictEqual(result.removed, 2);
    assert.strictEqual(result.removedAliases.length, 2);

    // Verify surviving alias
    assert.ok(aliases.resolveAlias('exists'));
    assert.strictEqual(aliases.resolveAlias('gone'), null);
  })) passed++; else failed++;

  if (test('handles all sessions existing (no cleanup needed)', () => {
    resetAliases();
    aliases.setAlias('alive', '/sessions/alive');
    const result = aliases.cleanupAliases(() => true);
    assert.strictEqual(result.removed, 0);
  })) passed++; else failed++;

  if (test('rejects non-function sessionExists', () => {
    const result = aliases.cleanupAliases('not a function');
    assert.strictEqual(result.totalChecked, 0);
    assert.ok(result.error);
  })) passed++; else failed++;

  // saveAliases atomic write tests
  console.log('\nsaveAliases (atomic write):');

  if (test('persists data across load/save cycles', () => {
    resetAliases();
    const data = aliases.loadAliases();
    data.aliases['persist-test'] = {
      sessionPath: '/test/path',
      createdAt: new Date().toISOString(),
      updatedAt: new Date().toISOString(),
      title: 'Persistence Test'
    };
    const saved = aliases.saveAliases(data);
    assert.strictEqual(saved, true);

    const reloaded = aliases.loadAliases();
    assert.ok(reloaded.aliases['persist-test']);
    assert.strictEqual(reloaded.aliases['persist-test'].title, 'Persistence Test');
  })) passed++; else failed++;

  if (test('updates metadata on save', () => {
    resetAliases();
    aliases.setAlias('meta-test', '/path');
    const data = aliases.loadAliases();
    assert.strictEqual(data.metadata.totalCount, 1);
    assert.ok(data.metadata.lastUpdated);
  })) passed++; else failed++;

  // Cleanup
  process.env.HOME = origHome;
  try {
    fs.rmSync(tmpHome, { recursive: true, force: true });
  } catch {
    // best-effort
  }

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

runTests();
