import { describe, it, expect } from "vitest";
import {
  findNextPendingIndex,
  countByStatus,
  buildTaskPrompt,
  buildAutoCommitPrompt,
  normalizeToUnixPath,
  isTaskBlocked,
  getReadyTasks,
  computeWaves,
  type TaskLoopTask,
} from "./taskLoop";

const makeTask = (
  id: string,
  status: TaskLoopTask["status"] = "pending",
  dependsOn?: string[]
): TaskLoopTask => ({
  id,
  title: `Task ${id}`,
  prompt: `Do the thing for ${id}`,
  priority: "medium",
  status,
  dependsOn,
});

describe("findNextPendingIndex", () => {
  it("returns -1 for an empty list", () => {
    expect(findNextPendingIndex([])).toBe(-1);
  });

  it("returns 0 when the first task is pending", () => {
    const tasks = [makeTask("1", "pending"), makeTask("2", "pending")];
    expect(findNextPendingIndex(tasks)).toBe(0);
  });

  it("skips completed and failed tasks to find the next pending", () => {
    const tasks = [
      makeTask("1", "completed"),
      makeTask("2", "failed"),
      makeTask("3", "pending"),
      makeTask("4", "pending"),
    ];
    expect(findNextPendingIndex(tasks)).toBe(2);
  });

  it("returns -1 when all tasks are completed", () => {
    const tasks = [makeTask("1", "completed"), makeTask("2", "completed")];
    expect(findNextPendingIndex(tasks)).toBe(-1);
  });

  it("skips running tasks", () => {
    const tasks = [makeTask("1", "running"), makeTask("2", "pending")];
    expect(findNextPendingIndex(tasks)).toBe(1);
  });
});

describe("countByStatus", () => {
  it("returns 0 for an empty list", () => {
    expect(countByStatus([], "pending")).toBe(0);
  });

  it("counts only tasks with the specified status", () => {
    const tasks = [
      makeTask("1", "pending"),
      makeTask("2", "completed"),
      makeTask("3", "pending"),
      makeTask("4", "failed"),
    ];
    expect(countByStatus(tasks, "pending")).toBe(2);
    expect(countByStatus(tasks, "completed")).toBe(1);
    expect(countByStatus(tasks, "failed")).toBe(1);
    expect(countByStatus(tasks, "running")).toBe(0);
  });

  it("counts all tasks when all have the same status", () => {
    const tasks = [makeTask("1", "completed"), makeTask("2", "completed")];
    expect(countByStatus(tasks, "completed")).toBe(2);
  });
});

describe("buildTaskPrompt", () => {
  it("includes the task number and total", () => {
    const task = makeTask("1");
    const result = buildTaskPrompt(task, 1, 5);
    expect(result).toContain("1/5");
  });

  it("includes the task title", () => {
    const task = makeTask("abc");
    const result = buildTaskPrompt(task, 2, 3);
    expect(result).toContain("Task abc");
  });

  it("includes the task prompt", () => {
    const task = makeTask("x");
    const result = buildTaskPrompt(task, 1, 1);
    expect(result).toContain("Do the thing for x");
  });

  it("labels the output as an automated task loop entry", () => {
    const task = makeTask("1");
    const result = buildTaskPrompt(task, 1, 1);
    expect(result).toContain("Automated Task Loop");
  });
});

describe("buildAutoCommitPrompt", () => {
  const task = makeTask("task-1");

  it("includes the git add and commit commands", () => {
    const result = buildAutoCommitPrompt(task, "feat", false, "2026-03-07T12:00:00");
    expect(result).toContain("git add -A");
    expect(result).toContain("git commit -m");
  });

  it("uses the provided prefix in the commit message", () => {
    const result = buildAutoCommitPrompt(task, "fix", false, "2026-03-07T12:00:00");
    expect(result).toContain("fix:");
  });

  it("includes the task title in the commit message", () => {
    const result = buildAutoCommitPrompt(task, "feat", false, "2026-03-07T12:00:00");
    expect(result).toContain("Task task-1");
  });

  it("includes the task id in the commit body", () => {
    const result = buildAutoCommitPrompt(task, "feat", false, "2026-03-07T12:00:00");
    expect(result).toContain("task-1");
  });

  it("includes the session timestamp in the commit body", () => {
    const result = buildAutoCommitPrompt(task, "feat", false, "2026-03-07T12:00:00");
    expect(result).toContain("2026-03-07T12:00:00");
  });

  it("does not include SUMMARY.md instructions when includeSummary is false", () => {
    const result = buildAutoCommitPrompt(task, "feat", false, "2026-03-07T12:00:00");
    expect(result).not.toContain("SUMMARY.md");
  });

  it("includes SUMMARY.md instructions when includeSummary is true", () => {
    const result = buildAutoCommitPrompt(task, "feat", true, "2026-03-07T12:00:00");
    expect(result).toContain("SUMMARY.md");
  });

  it("mentions non-blocking failure handling", () => {
    const result = buildAutoCommitPrompt(task, "feat", false, "2026-03-07T12:00:00");
    expect(result).toContain("do not retry");
  });

  it("escapes double quotes in the task title", () => {
    const quotedTask = makeTask("q1");
    quotedTask.title = 'Fix "quoted" title';
    const result = buildAutoCommitPrompt(quotedTask, "fix", false, "2026-03-07T12:00:00");
    expect(result).toContain('\\"quoted\\"');
  });
});

describe("normalizeToUnixPath", () => {
  it("converts a WSL UNC path with wsl.localhost to a Unix path", () => {
    expect(
      normalizeToUnixPath("\\\\wsl.localhost\\Ubuntu\\home\\naomi\\code\\temp\\file.json")
    ).toBe("/home/naomi/code/temp/file.json");
  });

  it("converts a WSL UNC path with wsl$ (legacy) to a Unix path", () => {
    expect(normalizeToUnixPath("\\\\wsl$\\Ubuntu\\home\\naomi\\file.json")).toBe(
      "/home/naomi/file.json"
    );
  });

  it("handles forward-slash UNC paths produced by some tools", () => {
    expect(normalizeToUnixPath("//wsl.localhost/Ubuntu/home/naomi/file.json")).toBe(
      "/home/naomi/file.json"
    );
  });

  it("leaves a plain Unix path unchanged", () => {
    expect(normalizeToUnixPath("/home/naomi/code/temp/file.json")).toBe(
      "/home/naomi/code/temp/file.json"
    );
  });

  it("leaves an empty string unchanged", () => {
    expect(normalizeToUnixPath("")).toBe("");
  });
});

describe("isTaskBlocked", () => {
  it("returns false when dependsOn is empty", () => {
    const task = makeTask("a", "pending", []);
    expect(isTaskBlocked(task, [task])).toBe(false);
  });

  it("returns false when dependsOn is undefined", () => {
    const task = makeTask("a", "pending");
    expect(isTaskBlocked(task, [task])).toBe(false);
  });

  it("returns false when all dependencies are completed", () => {
    const dep = makeTask("dep", "completed");
    const task = makeTask("a", "pending", ["dep"]);
    expect(isTaskBlocked(task, [dep, task])).toBe(false);
  });

  it("returns true when a dependency has failed", () => {
    const dep = makeTask("dep", "failed");
    const task = makeTask("a", "pending", ["dep"]);
    expect(isTaskBlocked(task, [dep, task])).toBe(true);
  });

  it("returns true when a dependency is blocked", () => {
    const dep = makeTask("dep", "blocked");
    const task = makeTask("a", "pending", ["dep"]);
    expect(isTaskBlocked(task, [dep, task])).toBe(true);
  });

  it("returns false when a dependency is still pending (not yet failed)", () => {
    const dep = makeTask("dep", "pending");
    const task = makeTask("a", "pending", ["dep"]);
    expect(isTaskBlocked(task, [dep, task])).toBe(false);
  });

  it("returns false when dependency ID does not exist in task list", () => {
    const task = makeTask("a", "pending", ["nonexistent"]);
    expect(isTaskBlocked(task, [task])).toBe(false);
  });
});

describe("getReadyTasks", () => {
  it("returns empty array when task list is empty", () => {
    expect(getReadyTasks([], 3)).toEqual([]);
  });

  it("returns all pending tasks with no deps when under limit", () => {
    const tasks = [makeTask("a", "pending"), makeTask("b", "pending"), makeTask("c", "pending")];
    expect(getReadyTasks(tasks, 5)).toEqual([0, 1, 2]);
  });

  it("respects the concurrency limit", () => {
    const tasks = [makeTask("a", "pending"), makeTask("b", "pending"), makeTask("c", "pending")];
    expect(getReadyTasks(tasks, 2)).toEqual([0, 1]);
  });

  it("skips tasks whose dependencies are not completed", () => {
    const tasks = [makeTask("a", "pending"), makeTask("b", "pending", ["a"])];
    // b depends on a which is pending, not completed — so only a is ready
    expect(getReadyTasks(tasks, 5)).toEqual([0]);
  });

  it("includes task when all its dependencies are completed", () => {
    const tasks = [makeTask("a", "completed"), makeTask("b", "pending", ["a"])];
    expect(getReadyTasks(tasks, 5)).toEqual([1]);
  });

  it("skips running, completed, failed, and blocked tasks", () => {
    const tasks = [
      makeTask("a", "running"),
      makeTask("b", "completed"),
      makeTask("c", "failed"),
      makeTask("d", "blocked"),
      makeTask("e", "pending"),
    ];
    expect(getReadyTasks(tasks, 5)).toEqual([4]);
  });

  it("returns empty when limit is 0", () => {
    const tasks = [makeTask("a", "pending")];
    expect(getReadyTasks(tasks, 0)).toEqual([]);
  });
});

describe("computeWaves", () => {
  it("returns empty array for empty task list", () => {
    expect(computeWaves([])).toEqual([]);
  });

  it("puts all independent tasks in a single wave", () => {
    const tasks = [makeTask("a", "pending"), makeTask("b", "pending"), makeTask("c", "pending")];
    expect(computeWaves(tasks)).toEqual([[0, 1, 2]]);
  });

  it("creates one wave per task for a linear chain", () => {
    const tasks = [
      makeTask("a", "pending"),
      makeTask("b", "pending", ["a"]),
      makeTask("c", "pending", ["b"]),
    ];
    expect(computeWaves(tasks)).toEqual([[0], [1], [2]]);
  });

  it("handles diamond dependency: A → B,C → D", () => {
    // A has no deps, B and C depend on A, D depends on B and C
    const tasks = [
      makeTask("a", "pending"),
      makeTask("b", "pending", ["a"]),
      makeTask("c", "pending", ["a"]),
      makeTask("d", "pending", ["b", "c"]),
    ];
    const waves = computeWaves(tasks);
    expect(waves).toHaveLength(3);
    expect(waves[0]).toEqual([0]);
    expect(waves[1]).toEqual([1, 2]);
    expect(waves[2]).toEqual([3]);
  });

  it("groups circular dependencies into a final overflow wave", () => {
    // a→b, b→a — circular; c has no deps so goes in wave 0
    const tasks = [
      makeTask("a", "pending", ["b"]),
      makeTask("b", "pending", ["a"]),
      makeTask("c", "pending"),
    ];
    const waves = computeWaves(tasks);
    // c goes in wave 0, then a+b get dumped in overflow
    expect(waves[0]).toEqual([2]);
    expect(waves[1]).toEqual([0, 1]);
  });

  it("ignores unknown dependency IDs (treats them as satisfied)", () => {
    const tasks = [makeTask("a", "pending", ["nonexistent"])];
    expect(computeWaves(tasks)).toEqual([[0]]);
  });
});
