import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { mkdtempSync, rmSync, mkdirSync, writeFileSync, existsSync, readFileSync } from 'fs';
import { tmpdir } from 'os';
import { join, resolve } from 'path';
import { createBackup, restoreBackup, listBackups, verifyBackup } from './backup.js';

let tmpDir: string;

beforeEach(() => {
  tmpDir = mkdtempSync(join(tmpdir(), 'backup-test-'));
  // Seed some data directories with content
  const dirs = ['config', 'characters', 'templates', 'queue'];
  for (const d of dirs) {
    mkdirSync(resolve(tmpDir, d), { recursive: true });
  }
  writeFileSync(resolve(tmpDir, 'config', 'settings.json'), JSON.stringify({ version: 1 }));
  writeFileSync(resolve(tmpDir, 'characters', 'char1.json'), JSON.stringify({ id: 'char1' }));
  writeFileSync(resolve(tmpDir, 'templates', 'tpl1.json'), JSON.stringify({ id: 'tpl1' }));
});

afterEach(() => {
  rmSync(tmpDir, { recursive: true, force: true });
});

describe('createBackup', () => {
  it('should create a ZIP file in the backups directory', async () => {
    const info = await createBackup(tmpDir);
    expect(info.filename).toMatch(/^backup-.*\.zip$/);
    expect(existsSync(info.path)).toBe(true);
    expect(info.sizeBytes).toBeGreaterThan(0);
    expect(info.checksum).toHaveLength(64); // SHA-256 hex
  });

  it('should create a .sha256 checksum file alongside the ZIP', async () => {
    const info = await createBackup(tmpDir);
    const checksumPath = `${info.path}.sha256`;
    expect(existsSync(checksumPath)).toBe(true);
    const storedChecksum = readFileSync(checksumPath, 'utf-8').trim();
    expect(storedChecksum).toBe(info.checksum);
  });
});

describe('restoreBackup', () => {
  it('should restore data from a backup ZIP', async () => {
    const info = await createBackup(tmpDir);

    // Modify data after backup
    writeFileSync(resolve(tmpDir, 'config', 'settings.json'), JSON.stringify({ version: 999 }));

    await restoreBackup(info.path, tmpDir);

    // Data should be restored to original
    const restored = JSON.parse(readFileSync(resolve(tmpDir, 'config', 'settings.json'), 'utf-8'));
    expect(restored.version).toBe(1);
  });

  it('should throw on checksum mismatch', async () => {
    const info = await createBackup(tmpDir);

    // Corrupt the checksum file
    writeFileSync(`${info.path}.sha256`, 'badhash', 'utf-8');

    await expect(restoreBackup(info.path, tmpDir)).rejects.toThrow('Backup checksum verification failed');
  });

  it('should rollback on extraction failure', async () => {
    // Create a backup first so we have valid data
    await createBackup(tmpDir);

    // Write known data
    writeFileSync(resolve(tmpDir, 'config', 'settings.json'), JSON.stringify({ version: 42 }));

    // Try to restore from a non-existent/invalid zip
    const badZip = resolve(tmpDir, 'bad.zip');
    writeFileSync(badZip, 'not a zip');

    await expect(restoreBackup(badZip, tmpDir)).rejects.toThrow();

    // Original data should be rolled back
    const data = JSON.parse(readFileSync(resolve(tmpDir, 'config', 'settings.json'), 'utf-8'));
    expect(data.version).toBe(42);
  });
});

describe('listBackups', () => {
  it('should return empty array when no backups exist', async () => {
    const list = await listBackups(tmpDir);
    expect(list).toEqual([]);
  });

  it('should list all backups sorted newest first', async () => {
    const b1 = await createBackup(tmpDir);
    // Small delay to ensure different timestamps
    await new Promise(r => setTimeout(r, 50));
    const b2 = await createBackup(tmpDir);

    const list = await listBackups(tmpDir);
    expect(list).toHaveLength(2);
    // Newest first
    expect(list[0].filename).toBe(b2.filename);
    expect(list[1].filename).toBe(b1.filename);
  });
});

describe('verifyBackup', () => {
  it('should return true for a valid backup', async () => {
    const info = await createBackup(tmpDir);
    const valid = await verifyBackup(info.path);
    expect(valid).toBe(true);
  });

  it('should return false when checksum file is missing', async () => {
    const info = await createBackup(tmpDir);
    rmSync(`${info.path}.sha256`);
    const valid = await verifyBackup(info.path);
    expect(valid).toBe(false);
  });

  it('should return false when checksum does not match', async () => {
    const info = await createBackup(tmpDir);
    writeFileSync(`${info.path}.sha256`, 'tampered', 'utf-8');
    const valid = await verifyBackup(info.path);
    expect(valid).toBe(false);
  });
});
