/**
 * Tests for createBackup job
 * Tests the creation of compressed backups
 */

import { BaseTest } from "../base/BaseTest.ts";
import { createBackup } from "../../lib/server/jobs/logic/createBackup.ts";
import { assertEquals } from "@std/assert";

class CreateBackupTest extends BaseTest {
  /**
   * Test 1: Basic successful backup
   */
  testBasicSuccessfulBackup(): void {
    this.test("basic successful backup", async (context) => {
      // Setup: Create source directory with test files
      const sourceDir = `${context.tempDir}/source`;
      await Deno.mkdir(sourceDir, { recursive: true });

      // Create some test files
      await Deno.writeTextFile(`${sourceDir}/file1.txt`, "test content 1\n");
      await Deno.writeTextFile(`${sourceDir}/file2.txt`, "test content 2\n");
      await Deno.writeTextFile(
        `${sourceDir}/file3.json`,
        JSON.stringify({ test: "data" }) + "\n",
      );

      // Create backup directory
      const backupDir = `${context.tempDir}/backups`;

      // Run backup
      const result = await createBackup(sourceDir, backupDir);

      // Assert success
      assertEquals(result.success, true);
      assertEquals(typeof result.filename, "string");
      assertEquals(typeof result.sizeBytes, "number");

      // Assert size is greater than 0
      if (result.sizeBytes) {
        assertEquals(result.sizeBytes > 0, true);
      }

      // Assert no error
      assertEquals(result.error, undefined);
    });
  }

  /**
   * Test 2: Backup filename format
   */
  testBackupFilenameFormat(): void {
    this.test("backup filename format", async (context) => {
      // Setup: Create source directory with test files
      const sourceDir = `${context.tempDir}/source`;
      await Deno.mkdir(sourceDir, { recursive: true });
      await Deno.writeTextFile(`${sourceDir}/test.txt`, "content\n");

      const backupDir = `${context.tempDir}/backups`;

      // Use a custom timestamp for predictable filename
      const customTimestamp = new Date("2025-01-15T14:30:45.000Z");

      // Run backup with custom timestamp
      const result = await createBackup(sourceDir, backupDir, customTimestamp);

      // Assert success
      assertEquals(result.success, true);

      // Assert filename matches expected format: backup-2025-01-15-143045.tar.gz
      assertEquals(result.filename, "backup-2025-01-15-143045.tar.gz");
    });
  }

  /**
   * Test 3: Backup file exists and is valid
   */
  testBackupFileExistsAndValid(): void {
    this.test("backup file exists and is valid", async (context) => {
      // Setup: Create source directory with test files
      const sourceDir = `${context.tempDir}/source`;
      await Deno.mkdir(sourceDir, { recursive: true });
      await Deno.writeTextFile(`${sourceDir}/file1.txt`, "test content\n");
      await Deno.writeTextFile(`${sourceDir}/file2.txt`, "more content\n");

      const backupDir = `${context.tempDir}/backups`;

      // Run backup
      const result = await createBackup(sourceDir, backupDir);

      // Assert success
      assertEquals(result.success, true);

      // Construct backup file path
      const backupFilePath = `${backupDir}/${result.filename}`;

      // Assert backup file exists
      await this.assertFileExists(backupFilePath);

      // Get file stats and verify size
      const stat = await Deno.stat(backupFilePath);
      assertEquals(stat.size > 0, true);

      // Verify returned sizeBytes matches actual file size
      assertEquals(result.sizeBytes, stat.size);
    });
  }

  /**
   * Test 4: Backup contains expected files
   */
  testBackupContainsExpectedFiles(): void {
    this.test("backup contains expected files", async (context) => {
      // Setup: Create source directory with specific test files
      const sourceDir = `${context.tempDir}/source`;
      await Deno.mkdir(sourceDir, { recursive: true });

      const testContent1 = "This is test file 1\n";
      const testContent2 = "This is test file 2\n";
      const testContent3 = JSON.stringify({ key: "value", data: [1, 2, 3] });

      await Deno.writeTextFile(`${sourceDir}/file1.txt`, testContent1);
      await Deno.writeTextFile(`${sourceDir}/file2.txt`, testContent2);
      await Deno.writeTextFile(`${sourceDir}/data.json`, testContent3);

      const backupDir = `${context.tempDir}/backups`;

      // Run backup
      const result = await createBackup(sourceDir, backupDir);

      // Assert success
      assertEquals(result.success, true);

      // Extract backup to verify contents
      const extractDir = `${context.tempDir}/extract`;
      await Deno.mkdir(extractDir, { recursive: true });

      const backupFilePath = `${backupDir}/${result.filename}`;

      // Extract tar.gz
      const extractCommand = new Deno.Command("tar", {
        args: ["-xzf", backupFilePath, "-C", extractDir],
        stdout: "piped",
        stderr: "piped",
      });

      const { code } = await extractCommand.output();
      assertEquals(code, 0);

      // Verify extracted files exist and have correct content
      const extractedSourceDir = `${extractDir}/source`;
      await this.assertFileExists(`${extractedSourceDir}/file1.txt`);
      await this.assertFileExists(`${extractedSourceDir}/file2.txt`);
      await this.assertFileExists(`${extractedSourceDir}/data.json`);

      // Verify file contents
      const extractedContent1 = await Deno.readTextFile(
        `${extractedSourceDir}/file1.txt`,
      );
      const extractedContent2 = await Deno.readTextFile(
        `${extractedSourceDir}/file2.txt`,
      );
      const extractedContent3 = await Deno.readTextFile(
        `${extractedSourceDir}/data.json`,
      );

      assertEquals(extractedContent1, testContent1);
      assertEquals(extractedContent2, testContent2);
      assertEquals(extractedContent3, testContent3);
    });
  }

  /**
   * Test 5: Non-existent source directory
   */
  testNonExistentSourceDirectory(): void {
    this.test("non-existent source directory", async (context) => {
      // Setup: Create path to non-existent directory
      const sourceDir = `${context.tempDir}/does-not-exist`;
      const backupDir = `${context.tempDir}/backups`;

      // Run backup on non-existent directory
      const result = await createBackup(sourceDir, backupDir);

      // Assert failure
      assertEquals(result.success, false);

      // Assert error message mentions source directory
      assertEquals(typeof result.error, "string");
      if (result.error) {
        assertEquals(
          result.error.includes("Source directory does not exist"),
          true,
        );
      }

      // Assert no filename or size returned
      assertEquals(result.filename, undefined);
      assertEquals(result.sizeBytes, undefined);
    });
  }

  /**
   * Test 6: Source path is a file, not directory
   */
  testSourcePathIsFile(): void {
    this.test("source path is a file, not directory", async (context) => {
      // Setup: Create a file instead of a directory
      const sourcePath = `${context.tempDir}/somefile.txt`;
      await Deno.writeTextFile(sourcePath, "this is a file\n");

      const backupDir = `${context.tempDir}/backups`;

      // Run backup on file path
      const result = await createBackup(sourcePath, backupDir);

      // Assert failure
      assertEquals(result.success, false);

      // Assert error message mentions not a directory
      assertEquals(typeof result.error, "string");
      if (result.error) {
        assertEquals(
          result.error.includes("Source path is not a directory"),
          true,
        );
      }

      // Assert no filename or size returned
      assertEquals(result.filename, undefined);
      assertEquals(result.sizeBytes, undefined);
    });
  }

  /**
   * Test 7: Empty source directory
   */
  testEmptySourceDirectory(): void {
    this.test("empty source directory", async (context) => {
      // Setup: Create an empty source directory
      const sourceDir = `${context.tempDir}/empty-source`;
      await Deno.mkdir(sourceDir, { recursive: true });

      const backupDir = `${context.tempDir}/backups`;

      // Run backup on empty directory
      const result = await createBackup(sourceDir, backupDir);

      // Assert success
      assertEquals(result.success, true);
      assertEquals(typeof result.filename, "string");
      assertEquals(typeof result.sizeBytes, "number");

      // Assert backup file exists
      const backupFilePath = `${backupDir}/${result.filename}`;
      await this.assertFileExists(backupFilePath);

      // Assert file has size > 0 (even empty tar.gz has some size for headers)
      if (result.sizeBytes) {
        assertEquals(result.sizeBytes > 0, true);
      }

      // Extract and verify empty directory structure is preserved
      const extractDir = `${context.tempDir}/extract`;
      await Deno.mkdir(extractDir, { recursive: true });

      const extractCommand = new Deno.Command("tar", {
        args: ["-xzf", backupFilePath, "-C", extractDir],
        stdout: "piped",
        stderr: "piped",
      });

      const { code } = await extractCommand.output();
      assertEquals(code, 0);

      // Verify extracted directory exists and is empty
      const extractedSourceDir = `${extractDir}/empty-source`;
      await this.assertFileExists(extractedSourceDir);

      // Read directory to verify it's empty
      const entries = [];
      for await (const entry of Deno.readDir(extractedSourceDir)) {
        entries.push(entry);
      }
      assertEquals(entries.length, 0);
    });
  }

  runTests(): void {
    this.testBasicSuccessfulBackup();
    this.testBackupFilenameFormat();
    this.testBackupFileExistsAndValid();
    this.testBackupContainsExpectedFiles();
    this.testNonExistentSourceDirectory();
    this.testSourcePathIsFile();
    this.testEmptySourceDirectory();
  }
}

// Run tests
const test = new CreateBackupTest();
await test.runTests();
