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

import { describe, test, expect, beforeAll, afterAll } from "bun:test";
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import simpleGit from "simple-git";
import { createGitShowTool } from "../src/agent/tools/git/show.js";
import { createGitDiffTool } from "../src/agent/tools/git/diff.js";
import { createGitBlameTool } from "../src/agent/tools/git/blame.js";
import { createGitLogTool } from "../src/agent/tools/git/log.js";
import { DEFAULT_MAX_LINES, DEFAULT_MAX_BYTES } from "../src/util/truncate.js";

function textOf(result: any): string {
  return result.content[0].text;
}

let workDir: string;
let git: ReturnType<typeof simpleGit>;

beforeAll(async () => {
  workDir = mkdtempSync(join(tmpdir(), "rumilo-git-test-"));
  git = simpleGit(workDir);
  await git.init();
  await git.addConfig("user.name", "Test");
  await git.addConfig("user.email", "test@test.com");
  await git.addConfig("commit.gpgsign", "false");

  // Create a large file for truncation tests
  const largeLine = "x".repeat(100);
  const largeContent = Array.from({ length: 3000 }, (_, i) => `${i}: ${largeLine}`).join("\n");
  writeFileSync(join(workDir, "large.txt"), largeContent);
  await git.add("large.txt");
  await git.commit("add large file");

  // Create many commits for log default-limit test
  for (let i = 0; i < 30; i++) {
    writeFileSync(join(workDir, "counter.txt"), String(i));
    await git.add("counter.txt");
    await git.commit(`commit number ${i}`);
  }
});

afterAll(() => {
  try {
    rmSync(workDir, { recursive: true, force: true });
  } catch {}
});

describe("git_show truncation (issue #8)", () => {
  test("truncates large output and appends notice", async () => {
    const tool = createGitShowTool(workDir);
    // The first commit has the large file diff, which should exceed truncation limits
    const logs = await git.log();
    const firstCommitHash = logs.all[logs.all.length - 1]!.hash;
    const result = await tool.execute("call-1", { ref: firstCommitHash });
    const text = textOf(result);

    // Output should be bounded - not return all 3000+ lines raw
    const lines = text.split("\n");
    expect(lines.length).toBeLessThanOrEqual(DEFAULT_MAX_LINES + 5); // small margin for notice
    expect(Buffer.byteLength(text, "utf-8")).toBeLessThanOrEqual(DEFAULT_MAX_BYTES + 500); // margin for notice
    expect(text).toContain("[truncated");
  });

  test("small output is not truncated", async () => {
    const tool = createGitShowTool(workDir);
    const result = await tool.execute("call-2", { ref: "HEAD" });
    const text = textOf(result);
    // HEAD commit is small (counter.txt change), should NOT be truncated
    expect(text).not.toContain("[truncated");
  });
});

describe("git_diff truncation (issue #8)", () => {
  test("truncates large diff output", async () => {
    const tool = createGitDiffTool(workDir);
    const logs = await git.log();
    const firstCommitHash = logs.all[logs.all.length - 1]!.hash;
    const secondCommitHash = logs.all[logs.all.length - 2]!.hash;
    // Diff between first commit (large file add) and second commit
    const result = await tool.execute("call-3", { ref: firstCommitHash, ref2: secondCommitHash });
    const text = textOf(result);
    // The diff won't be huge (only counter.txt changes), so let's create a proper large diff scenario
    // Instead, diff from the first commit to HEAD which has many changes but also large.txt unchanged
    // Better: modify large.txt to create a big diff
    // Actually, let's just verify the mechanism works by checking the first commit via show already.
    // For diff specifically, create a modified version of large.txt
    const largeLine2 = "y".repeat(100);
    const largeContent2 = Array.from({ length: 3000 }, (_, i) => `${i}: ${largeLine2}`).join("\n");
    writeFileSync(join(workDir, "large.txt"), largeContent2);
    const result2 = await tool.execute("call-3b", { ref: "HEAD" });
    const text2 = textOf(result2);
    const lines2 = text2.split("\n");
    expect(lines2.length).toBeLessThanOrEqual(DEFAULT_MAX_LINES + 5);
    expect(text2).toContain("[truncated");
    // Restore the file
    await git.checkout(["--", "large.txt"]);
  });
});

describe("git_blame truncation (issue #8)", () => {
  test("truncates large blame output", async () => {
    const tool = createGitBlameTool(workDir);
    const result = await tool.execute("call-4", { path: "large.txt" });
    const text = textOf(result);
    const lines = text.split("\n");
    expect(lines.length).toBeLessThanOrEqual(DEFAULT_MAX_LINES + 5);
    expect(Buffer.byteLength(text, "utf-8")).toBeLessThanOrEqual(DEFAULT_MAX_BYTES + 500);
    expect(text).toContain("[truncated");
  });
});

describe("git_log default limit (issue #9)", () => {
  test("returns at most 20 commits when n is not specified", async () => {
    const tool = createGitLogTool(workDir);
    const result: any = await tool.execute("call-5", {});
    // We have 31 commits total (1 large file + 30 counter), default should limit to 20
    expect(result.details.count).toBeLessThanOrEqual(20);
    expect(result.details.count).toBe(20);
  });

  test("explicit n overrides default limit", async () => {
    const tool = createGitLogTool(workDir);
    const result: any = await tool.execute("call-6", { n: 5 });
    expect(result.details.count).toBe(5);
  });

  test("explicit n larger than 20 works", async () => {
    const tool = createGitLogTool(workDir);
    const result: any = await tool.execute("call-7", { n: 25 });
    expect(result.details.count).toBe(25);
  });
});
