// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
//
// SPDX-License-Identifier: GPL-3.0-or-later

import { describe, test, expect, beforeEach, afterEach } from "bun:test";
import { readdirSync, mkdtempSync } from "node:fs";
import { rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { execSync } from "node:child_process";
import { ConfigError } from "../src/util/errors.js";

/**
 * Snapshot rumilo-* dirs in tmpdir so we can detect leaks.
 */
function rumiloTmpDirs(): Set<string> {
  return new Set(readdirSync(tmpdir()).filter((n) => n.startsWith("rumilo-")));
}

function leakedDirs(before: Set<string>, after: Set<string>): string[] {
  return [...after].filter((d) => !before.has(d));
}

async function cleanupLeaked(leaked: string[]): Promise<void> {
  for (const d of leaked) {
    await rm(join(tmpdir(), d), { recursive: true, force: true });
  }
}

// ─── web command: workspace leaked on missing credentials ───────────

describe("web command – workspace cleanup on early failure", () => {
  const origEnv = { ...process.env };

  beforeEach(() => {
    // Ensure credential env vars are absent so validation throws.
    delete process.env["KAGI_SESSION_TOKEN"];
    delete process.env["TABSTACK_API_KEY"];
  });

  afterEach(() => {
    process.env = { ...origEnv };
  });

  test("workspace dir is removed when credential validation throws", async () => {
    const before = rumiloTmpDirs();

    const { runWebCommand } = await import("../src/cli/commands/web.js");

    try {
      await runWebCommand({
        query: "test",
        verbose: false,
        cleanup: true,
      });
    } catch (e: any) {
      expect(e).toBeInstanceOf(ConfigError);
    }

    const after = rumiloTmpDirs();
    const leaked = leakedDirs(before, after);

    // Safety: clean up any leaked dirs so the test doesn't pollute.
    await cleanupLeaked(leaked);

    // If this fails, the workspace was created but not cleaned up – a leak.
    expect(leaked).toEqual([]);
  });
});

// ─── repo command: workspace leaked on checkout failure ─────────────

describe("repo command – workspace cleanup on early failure", () => {
  const origEnv = { ...process.env };
  let localRepo: string;

  beforeEach(() => {
    // Create a small local bare git repo so clone succeeds without network.
    localRepo = mkdtempSync(join(tmpdir(), "rumilo-test-bare-"));
    execSync("git init --bare", { cwd: localRepo, stdio: "ignore" });
    // Create a temporary work clone to add a commit (bare repos need content)
    const workClone = mkdtempSync(join(tmpdir(), "rumilo-test-work-"));
    execSync(`git clone ${localRepo} work`, { cwd: workClone, stdio: "ignore" });
    const workDir = join(workClone, "work");
    execSync("git config user.email test@test.com && git config user.name Test && git config commit.gpgsign false", { cwd: workDir, stdio: "ignore" });
    execSync("echo hello > README.md && git add . && git commit -m init", { cwd: workDir, stdio: "ignore" });
    execSync("git push", { cwd: workDir, stdio: "ignore" });
    // Clean up work clone
    execSync(`rm -rf ${workClone}`, { stdio: "ignore" });
  });

  afterEach(async () => {
    process.env = { ...origEnv };
    await rm(localRepo, { recursive: true, force: true });
  });

  test("workspace dir is removed when clone fails", async () => {
    const before = rumiloTmpDirs();

    const { runRepoCommand } = await import("../src/cli/commands/repo.js");

    try {
      await runRepoCommand({
        query: "test",
        uri: "file:///nonexistent-path/repo.git",
        full: false,
        verbose: false,
        cleanup: true,
      });
    } catch {
      // expected – clone will fail
    }

    const after = rumiloTmpDirs();
    const leaked = leakedDirs(before, after);
    await cleanupLeaked(leaked);
    expect(leaked).toEqual([]);
  });

  test("workspace dir is removed when ref checkout fails after clone", async () => {
    const before = rumiloTmpDirs();

    const { runRepoCommand } = await import("../src/cli/commands/repo.js");

    try {
      await runRepoCommand({
        query: "test",
        uri: localRepo,
        ref: "nonexistent-ref-abc123",
        full: true,  // full clone for local bare repo compatibility
        verbose: false,
        cleanup: true,
      });
    } catch {
      // expected – checkout of bad ref will fail
    }

    const after = rumiloTmpDirs();
    const leaked = leakedDirs(before, after);
    await cleanupLeaked(leaked);
    expect(leaked).toEqual([]);
  });
});
