// credential-manager depends on keychain-manager and crypto-utils (both singletons).
// crypto-utils is already initialized via jest.setup.js env var.
// keychain-manager may not have OS keychain available in test env.

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

const credentialManager = require('../credential-manager');

// Use a temp file for credentials in tests
const TEMP_CREDS_FILE = path.join(os.tmpdir(), 'dashcaddy-test-creds.json');

beforeEach(() => {
  // Reset singleton state
  credentialManager.cache.clear();
  // Clean up temp file
  if (fs.existsSync(TEMP_CREDS_FILE)) {
    fs.unlinkSync(TEMP_CREDS_FILE);
  }
});

afterAll(() => {
  if (fs.existsSync(TEMP_CREDS_FILE)) {
    fs.unlinkSync(TEMP_CREDS_FILE);
  }
});

describe('store', () => {
  test('rejects invalid key (null)', async () => {
    const result = await credentialManager.store(null, 'value');
    expect(result).toBe(false);
  });

  test('rejects invalid key (non-string)', async () => {
    const result = await credentialManager.store(123, 'value');
    expect(result).toBe(false);
  });

  test('rejects invalid value (null)', async () => {
    const result = await credentialManager.store('key', null);
    expect(result).toBe(false);
  });

  test('rejects invalid value (non-string)', async () => {
    const result = await credentialManager.store('key', 123);
    expect(result).toBe(false);
  });

  test('stores credential and caches it', async () => {
    const result = await credentialManager.store('test.key', 'secret123');
    expect(result).toBe(true);
    expect(credentialManager.cache.get('test.key')).toBe('secret123');
  });
});

describe('retrieve', () => {
  test('returns cached value when available', async () => {
    credentialManager.cache.set('cached.key', 'cached-value');
    const result = await credentialManager.retrieve('cached.key');
    expect(result).toBe('cached-value');
  });

  test('returns null for non-existent key', async () => {
    const result = await credentialManager.retrieve('nonexistent');
    expect(result).toBeNull();
  });
});

describe('store + retrieve round-trip', () => {
  test('retrieves what was stored', async () => {
    await credentialManager.store('roundtrip.key', 'my-secret');
    // Clear cache to force file read
    credentialManager.cache.clear();
    const result = await credentialManager.retrieve('roundtrip.key');
    expect(result).toBe('my-secret');
  });
});

describe('delete', () => {
  test('removes from cache', async () => {
    await credentialManager.store('delete.key', 'value');
    expect(credentialManager.cache.has('delete.key')).toBe(true);
    await credentialManager.delete('delete.key');
    expect(credentialManager.cache.has('delete.key')).toBe(false);
  });

  test('deleted credential cannot be retrieved', async () => {
    await credentialManager.store('delete2.key', 'value');
    await credentialManager.delete('delete2.key');
    credentialManager.cache.clear();
    const result = await credentialManager.retrieve('delete2.key');
    expect(result).toBeNull();
  });
});

describe('list', () => {
  test('returns array of credential keys', async () => {
    await credentialManager.store('list.a', 'val1');
    await credentialManager.store('list.b', 'val2');
    const keys = await credentialManager.list();
    expect(keys).toContain('list.a');
    expect(keys).toContain('list.b');
  });

  test('returns empty array when no credentials', async () => {
    const keys = await credentialManager.list();
    expect(Array.isArray(keys)).toBe(true);
  });
});

describe('getMetadata', () => {
  test('returns metadata for existing key', async () => {
    await credentialManager.store('meta.key', 'val', { description: 'Test credential' });
    const meta = await credentialManager.getMetadata('meta.key');
    expect(meta).toEqual({ description: 'Test credential' });
  });

  test('returns null for non-existent key', async () => {
    const meta = await credentialManager.getMetadata('nonexistent');
    expect(meta).toBeNull();
  });
});

describe('exportBackup / importBackup', () => {
  test('export returns encrypted string', async () => {
    await credentialManager.store('backup.key', 'backup-value');
    const backup = await credentialManager.exportBackup();
    expect(typeof backup).toBe('string');
    expect(backup.split(':').length).toBe(3); // iv:authTag:ciphertext
  });

  test('import restores credentials from backup', async () => {
    await credentialManager.store('backup.key', 'backup-value');
    const backup = await credentialManager.exportBackup();

    // Clear everything
    await credentialManager.delete('backup.key');
    credentialManager.cache.clear();

    // Import backup
    const result = await credentialManager.importBackup(backup);
    expect(result).toBe(true);

    // Verify restored
    const keys = await credentialManager.list();
    expect(keys).toContain('backup.key');
  });

  test('importBackup rejects unsupported version', async () => {
    const cryptoUtils = require('../crypto-utils');
    const badBackup = cryptoUtils.encrypt(JSON.stringify({ version: '99.0', credentials: {} }));
    const result = await credentialManager.importBackup(badBackup);
    expect(result).toBe(false);
  });
});

describe('migrateToEncrypted', () => {
  test('returns migration count', async () => {
    const result = await credentialManager.migrateToEncrypted();
    expect(result).toHaveProperty('migrated');
    expect(result).toHaveProperty('skipped');
    expect(result).toHaveProperty('total');
  });
});
