const crypto = require('crypto');
const backupManager = require('../backup-manager');

beforeEach(() => {
  // Reset singleton state
  backupManager.history = [];
  backupManager.config = { backups: {}, defaultRetention: { keep: 7 } };
  backupManager.running = false;
  for (const [, job] of backupManager.scheduledJobs.entries()) {
    clearInterval(job);
  }
  backupManager.scheduledJobs.clear();
});

afterAll(() => {
  backupManager.stop();
});

describe('calculateChecksum', () => {
  test('returns SHA-256 hex string', () => {
    const data = Buffer.from('test data');
    const checksum = backupManager.calculateChecksum(data);
    expect(checksum).toMatch(/^[0-9a-f]{64}$/);
  });

  test('same data produces same checksum', () => {
    const data = Buffer.from('consistent');
    expect(backupManager.calculateChecksum(data)).toBe(backupManager.calculateChecksum(data));
  });

  test('different data produces different checksum', () => {
    const a = backupManager.calculateChecksum(Buffer.from('aaa'));
    const b = backupManager.calculateChecksum(Buffer.from('bbb'));
    expect(a).not.toBe(b);
  });
});

describe('compressBackup / decompressBackup', () => {
  test('round-trip preserves data', async () => {
    const original = { services: [{ id: 'test', name: 'Test' }], config: { theme: 'dark' } };
    const compressed = await backupManager.compressBackup(original);
    const decompressed = await backupManager.decompressBackup(compressed);
    expect(decompressed).toEqual(original);
  });

  test('compressed output is a Buffer', async () => {
    const compressed = await backupManager.compressBackup({ test: true });
    expect(Buffer.isBuffer(compressed)).toBe(true);
  });
});

describe('encryptBackup / decryptBackup', () => {
  const testKey = crypto.randomBytes(32).toString('hex');

  test('round-trip preserves data with valid key', async () => {
    const original = Buffer.from('backup data here');
    const encrypted = await backupManager.encryptBackup(original, testKey);
    const decrypted = await backupManager.decryptBackup(encrypted, testKey);
    expect(decrypted.toString()).toBe(original.toString());
  });

  test('produces a non-empty buffer', async () => {
    const original = Buffer.from('backup data here');
    const encrypted = await backupManager.encryptBackup(original, testKey);
    expect(Buffer.isBuffer(encrypted)).toBe(true);
    expect(encrypted.length).toBeGreaterThan(0);
  });

  test('output differs from input', async () => {
    const original = Buffer.from('backup data here');
    const encrypted = await backupManager.encryptBackup(original, testKey);
    expect(encrypted.toString()).not.toBe(original.toString());
  });

  test('throws on invalid encrypted format', async () => {
    await expect(backupManager.decryptBackup(Buffer.from('bad'), testKey)).rejects.toThrow();
  });

  test('throws on wrong key', async () => {
    const original = Buffer.from('secret data');
    const encrypted = await backupManager.encryptBackup(original, testKey);
    const wrongKey = crypto.randomBytes(32).toString('hex');
    await expect(backupManager.decryptBackup(encrypted, wrongKey)).rejects.toThrow();
  });
});

describe('scheduleBackup', () => {
  beforeEach(() => {
    jest.useFakeTimers();
  });

  afterEach(() => {
    jest.useRealTimers();
  });

  test('parses hourly schedule', () => {
    backupManager.scheduleBackup('test', { schedule: 'hourly' });
    expect(backupManager.scheduledJobs.has('test')).toBe(true);
  });

  test('parses daily schedule', () => {
    backupManager.scheduleBackup('test', { schedule: 'daily' });
    expect(backupManager.scheduledJobs.has('test')).toBe(true);
  });

  test('parses weekly schedule', () => {
    backupManager.scheduleBackup('test', { schedule: 'weekly' });
    expect(backupManager.scheduledJobs.has('test')).toBe(true);
  });

  test('parses monthly schedule', () => {
    backupManager.scheduleBackup('test', { schedule: 'monthly' });
    expect(backupManager.scheduledJobs.has('test')).toBe(true);
  });

  test('parses custom numeric minute schedule', () => {
    backupManager.scheduleBackup('test', { schedule: '30' });
    expect(backupManager.scheduledJobs.has('test')).toBe(true);
  });

  test('logs error for invalid schedule', () => {
    backupManager.scheduleBackup('test', { schedule: 'invalid' });
    expect(backupManager.scheduledJobs.has('test')).toBe(false);
  });
});

describe('addToHistory', () => {
  test('appends entry to history', () => {
    backupManager.addToHistory({ id: 'b1', status: 'success' });
    expect(backupManager.history).toHaveLength(1);
  });

  test('trims history to 100 entries', () => {
    for (let i = 0; i < 105; i++) {
      backupManager.addToHistory({ id: `b${i}`, status: 'success' });
    }
    expect(backupManager.history.length).toBeLessThanOrEqual(100);
  });
});

describe('getHistory', () => {
  test('returns entries in reverse order', () => {
    backupManager.addToHistory({ id: 'first' });
    backupManager.addToHistory({ id: 'second' });
    const history = backupManager.getHistory();
    expect(history[0].id).toBe('second');
    expect(history[1].id).toBe('first');
  });

  test('respects limit parameter', () => {
    for (let i = 0; i < 10; i++) {
      backupManager.addToHistory({ id: `b${i}` });
    }
    expect(backupManager.getHistory(3)).toHaveLength(3);
  });
});

describe('getConfig / updateConfig', () => {
  test('getConfig returns current config', () => {
    const config = backupManager.getConfig();
    expect(config).toHaveProperty('backups');
  });

  test('updateConfig merges new config', () => {
    backupManager.updateConfig({ backups: { daily: { enabled: true, schedule: 'daily' } } });
    expect(backupManager.config.backups.daily).toBeDefined();
  });
});

describe('start / stop', () => {
  test('start sets running flag', () => {
    backupManager.start();
    expect(backupManager.running).toBe(true);
    backupManager.stop();
  });

  test('start is idempotent', () => {
    backupManager.start();
    backupManager.start();
    expect(backupManager.running).toBe(true);
    backupManager.stop();
  });

  test('stop clears running flag and jobs', () => {
    backupManager.start();
    backupManager.stop();
    expect(backupManager.running).toBe(false);
    expect(backupManager.scheduledJobs.size).toBe(0);
  });
});

describe('cleanupOldBackups', () => {
  test('keeps configured number of backups', async () => {
    // Add 5 successful backups for 'daily'
    for (let i = 0; i < 5; i++) {
      backupManager.history.push({
        id: `daily-${i}`,
        name: 'daily',
        status: 'success',
        timestamp: new Date(Date.now() - i * 86400000).toISOString(),
        locations: [{ type: 'local', path: `/tmp/fake-${i}.backup` }]
      });
    }

    await backupManager.cleanupOldBackups('daily', { keep: 3 });
    const remaining = backupManager.history.filter(b => b.name === 'daily' && b.status === 'success');
    expect(remaining.length).toBe(3);
  });
});
