import assert from "node:assert/strict";
import { test } from "bun:test";

import { runTaskTreeCli } from "./tasktree-cli.ts";

test("--overapi preserves the command DTO and only changes transport", async () => {
  const originalFetch = globalThis.fetch;
  let received: unknown;
  globalThis.fetch = (async (input, init) => {
    assert.equal(String(input), "http://tasktree.test/v1/tasktree/commands");
    received = JSON.parse(String(init?.body));
    return Response.json({ ok: true, operation: "timeline.get", data: { group: { id: "tg_1" }, tasks: [], milestones: [], reports: [] } });
  }) as typeof fetch;
  try {
    const result = await runTaskTreeCli(["timeline", "--group", "tg_1", "--overapi"], {
      HWLAB_TASKTREE_API_URL: "http://tasktree.test"
    });
    assert.deepEqual(received, { operation: "timeline.get", groupId: "tg_1" });
    assert.equal(result.transport, "api");
    assert.equal(result.httpStatus, 200);
    assert.equal(result.operation, "timeline.get");
  } finally {
    globalThis.fetch = originalFetch;
  }
});

test("TaskTree command surface covers hierarchy, lifecycle, batch, stats, and reports", async () => {
  const originalFetch = globalThis.fetch;
  const received: unknown[] = [];
  globalThis.fetch = (async (_input, init) => {
    const command = JSON.parse(String(init?.body));
    received.push(command);
    return Response.json({ ok: true, operation: command.operation, data: {} });
  }) as typeof fetch;
  const env = { HWLAB_TASKTREE_API_URL: "http://tasktree.test" };
  try {
    const cases: Array<[string[], unknown, string?]> = [
      [["group", "stats", "--group", "tg_1", "--overapi"], { operation: "group.stats", groupId: "tg_1" }],
      [["task", "list", "--group", "tg_1", "--overapi"], { operation: "task.list", groupId: "tg_1" }],
      [["task", "get", "--task", "tt_1", "--overapi"], { operation: "task.get", taskId: "tt_1" }],
      [["task", "create", "--group", "tg_1", "--parent", "tt_1", "--stdin", "--overapi"], {
        operation: "task.create", groupId: "tg_1", parentId: "tt_1", title: "Child"
      }, "Child\n"],
      [["task", "create-batch", "--group", "tg_1", "--parent", "tt_1", "One", "Two", "--overapi"], {
        operation: "task.create-batch", groupId: "tg_1", parentId: "tt_1", titles: ["One", "Two"]
      }],
      [["task", "update", "--task", "tt_1", "--stdin", "--overapi"], {
        operation: "task.update", taskId: "tt_1", title: "Renamed"
      }, "Renamed\n"],
      [["task", "start", "--task", "tt_1", "--overapi"], { operation: "task.start", taskId: "tt_1" }],
      [["task", "done", "--task", "tt_1", "--overapi"], { operation: "task.complete", taskId: "tt_1" }],
      [["task", "remove", "--task", "tt_1", "--overapi"], { operation: "task.delete", taskId: "tt_1" }],
      [["report", "list", "--task", "tt_1", "--overapi"], { operation: "report.list", taskId: "tt_1" }],
      [["report", "get", "--report", "tr_1", "--overapi"], { operation: "report.get", reportId: "tr_1" }],
      [["report", "write", "--task", "tt_1", "--title", "Execution", "--stdin", "--overapi"], {
        operation: "report.write", taskId: "tt_1", title: "Execution", body: "# Result\n\nPassed.\n"
      }, "# Result\n\nPassed.\n"],
      [["backup", "create", "--dry-run", "--reason", "smoke", "--overapi"], {
        operation: "backup.create", dryRun: true, reason: "smoke"
      }],
      [["backup", "status", "--overapi"], { operation: "backup.status" }],
      [["backup", "verify", "--overapi"], { operation: "backup.verify" }],
      [["backup", "restore", "--overapi"], {
        operation: "backup.restore", confirm: false
      }],
      [["backup", "restore", "--confirm", "--expected-sha", "a".repeat(64), "--overapi"], {
        operation: "backup.restore", confirm: true, expectedSha256: "a".repeat(64)
      }]
    ];
    for (const [argv, expected, stdin] of cases) {
      await runTaskTreeCli(argv, env, stdin);
      assert.deepEqual(received.at(-1), expected);
    }
  } finally {
    globalThis.fetch = originalFetch;
  }
});

test("TaskTree help and public operations do not expose the legacy domain term", async () => {
  const result = await runTaskTreeCli(["--help"]);
  assert.doesNotMatch(JSON.stringify(result), /mdtodo/iu);
  assert.ok(result.usage.includes("hwlab-cli tasktree group list|overview"));
  assert.ok(result.usage.includes("hwlab-cli tasktree group get|delete|stats --group ID"));
  assert.match(result.usage.join("\n"), /task update .*--start ISO\|null.*--due ISO\|null/u);
  assert.doesNotMatch(result.usage.join("\n"), /report create/u);
});

test("TaskTree rejects ambiguous title input and empty update patches before transport", async () => {
  await assert.rejects(
    runTaskTreeCli(["task", "create", "--group", "tg_1", "--title", "One", "--stdin", "--overapi"], { HWLAB_TASKTREE_API_URL: "http://tasktree.test" }, "Two"),
    (error: any) => error?.code === "title_source_conflict"
  );
  await assert.rejects(
    runTaskTreeCli(["task", "update", "--task", "tt_1", "--overapi"], { HWLAB_TASKTREE_API_URL: "http://tasktree.test" }),
    (error: any) => error?.code === "update_patch_required"
  );
  await assert.rejects(
    runTaskTreeCli(["backup", "restore", "--confirm", "--overapi"], { HWLAB_TASKTREE_API_URL: "http://tasktree.test" }),
    (error: any) => error?.code === "restore_confirmation_invalid"
  );
  await assert.rejects(
    runTaskTreeCli(["backup", "restore", "--expected-sha", "a".repeat(64), "--overapi"], { HWLAB_TASKTREE_API_URL: "http://tasktree.test" }),
    (error: any) => error?.code === "restore_confirmation_invalid"
  );
});

test("TaskTree rejects unknown, duplicate, hidden, and extra CLI arguments", async () => {
  const cases: Array<[string[], string]> = [
    [["group", "list", "unexpected"], "invalid_arguments"],
    [["group", "list", "--bogus", "value"], "invalid_option"],
    [["group", "create", "--name", "One", "--name", "Two"], "duplicate_option"],
    [["report", "create", "--task", "tt_1", "--title", "Hidden", "--body", "body"], "unsupported_command"],
    [["group", "list", "--api-url", "http://tasktree.test"], "invalid_option"],
    [["group", "list", "--stdin"], "invalid_option"]
  ];
  for (const [argv, code] of cases) {
    await assert.rejects(runTaskTreeCli(argv), (error: any) => error?.code === code);
  }
});

test("TaskTree accepts an explicit empty description value", async () => {
  const originalFetch = globalThis.fetch;
  let received: unknown;
  globalThis.fetch = (async (_input, init) => {
    received = JSON.parse(String(init?.body));
    return Response.json({ ok: true, operation: "group.create", data: {} });
  }) as typeof fetch;
  try {
    await runTaskTreeCli(
      ["group", "create", "--name", "Group", "--description", "", "--overapi"],
      { HWLAB_TASKTREE_API_URL: "http://tasktree.test" }
    );
    assert.deepEqual(received, { operation: "group.create", name: "Group", description: "" });
  } finally {
    globalThis.fetch = originalFetch;
  }
});

test("TaskTree returns stable transport and runtime configuration errors", async () => {
  await assert.rejects(
    runTaskTreeCli(["group", "list", "--overapi"]),
    (error: any) => error?.code === "missing_api_url"
  );
  await assert.rejects(
    runTaskTreeCli(["health"], {}),
    (error: any) => error?.code === "missing_database_url"
  );

  const originalFetch = globalThis.fetch;
  try {
    globalThis.fetch = (async () => { throw new Error("connection refused"); }) as typeof fetch;
    await assert.rejects(
      runTaskTreeCli(["group", "list", "--overapi"], { HWLAB_TASKTREE_API_URL: "http://tasktree.test" }),
      (error: any) => error?.code === "api_unreachable"
    );

    for (const response of [
      new Response("not json", { status: 502 }),
      Response.json({ ok: true, operation: "task.get", data: {} }),
      Response.json({ ok: true, operation: "group.list", data: {} }, { status: 500 })
    ]) {
      globalThis.fetch = (async () => response) as typeof fetch;
      await assert.rejects(
        runTaskTreeCli(["group", "list", "--overapi"], { HWLAB_TASKTREE_API_URL: "http://tasktree.test" }),
        (error: any) => error?.code === "invalid_api_response"
      );
    }
  } finally {
    globalThis.fetch = originalFetch;
  }
});
