import { afterEach, beforeEach, describe, expect, test } from "bun:test";
import { mkdirSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import {
  appendPrMemory,
  buildPrMemoryKey,
  clearClosedPrMemories,
  clearPrMemory,
  loadPrMemory,
} from "@/pr-memory/store.js";
import type { PrMemoryEntry } from "@/pr-memory/store.js";

function makeEntry(overrides: Partial<PrMemoryEntry> = {}): PrMemoryEntry {
  return {
    commentId: 1,
    commentAuthor: "alice",
    commentBody: "Please fix the bug",
    agentReply: "Done, fixed in abc123",
    filesChanged: ["src/index.ts"],
    timestamp: "2026-01-01T00:00:00Z",
    ...overrides,
  };
}

describe("pr-memory/store", () => {
  let tmp: string;
  let originalCwd: string;

  beforeEach(() => {
    originalCwd = process.cwd();
    tmp = join(tmpdir(), `pr-memory-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
    mkdirSync(tmp, { recursive: true });
    process.chdir(tmp);
  });

  afterEach(() => {
    process.chdir(originalCwd);
    rmSync(tmp, { recursive: true, force: true });
  });

  describe("buildPrMemoryKey", () => {
    test("formats key as repoName#prNumber", () => {
      expect(buildPrMemoryKey("my-api", 123)).toBe("my-api#123");
    });
  });

  describe("loadPrMemory", () => {
    test("returns empty array when file does not exist", () => {
      expect(loadPrMemory("nonexistent#99")).toEqual([]);
    });

    test("returns empty array for corrupt JSON file", () => {
      const memDir = join(tmp, "memory");
      mkdirSync(memDir, { recursive: true });
      writeFileSync(join(memDir, "broken#1.json"), "NOT VALID JSON{{{");

      expect(loadPrMemory("broken#1")).toEqual([]);
    });

    test("returns empty array when file contains a non-array JSON value", () => {
      const memDir = join(tmp, "memory");
      mkdirSync(memDir, { recursive: true });
      writeFileSync(join(memDir, "obj#1.json"), JSON.stringify({ not: "an array" }));

      expect(loadPrMemory("obj#1")).toEqual([]);
    });

    test("filters out invalid entries from the array", () => {
      const memDir = join(tmp, "memory");
      mkdirSync(memDir, { recursive: true });
      const validEntry = makeEntry();
      const invalidEntries = [
        {
          commentId: "not-a-number",
          commentAuthor: "a",
          commentBody: "b",
          agentReply: "c",
          filesChanged: [],
          timestamp: "t",
        },
        { commentId: 1 },
        null,
        42,
        "string",
      ];
      writeFileSync(join(memDir, "mixed#1.json"), JSON.stringify([validEntry, ...invalidEntries]));

      const result = loadPrMemory("mixed#1");
      expect(result).toEqual([validEntry]);
    });
  });

  describe("appendPrMemory", () => {
    test("creates memory dir and file, then loadPrMemory round-trips", () => {
      const key = buildPrMemoryKey("repo", 1);
      const entry = makeEntry();

      appendPrMemory(key, entry);

      const loaded = loadPrMemory(key);
      expect(loaded).toEqual([entry]);
    });

    test("multiple appends accumulate in insertion order", () => {
      const key = buildPrMemoryKey("repo", 2);
      const e1 = makeEntry({ commentId: 10, timestamp: "2026-01-01T00:00:00Z" });
      const e2 = makeEntry({ commentId: 20, timestamp: "2026-01-02T00:00:00Z" });
      const e3 = makeEntry({ commentId: 30, timestamp: "2026-01-03T00:00:00Z" });

      appendPrMemory(key, e1);
      appendPrMemory(key, e2);
      appendPrMemory(key, e3);

      const loaded = loadPrMemory(key);
      expect(loaded).toHaveLength(3);
      expect(loaded[0]!.commentId).toBe(10);
      expect(loaded[1]!.commentId).toBe(20);
      expect(loaded[2]!.commentId).toBe(30);
    });
  });

  describe("clearPrMemory", () => {
    test("deletes the file; subsequent load returns empty array", () => {
      const key = buildPrMemoryKey("repo", 3);
      appendPrMemory(key, makeEntry());
      expect(loadPrMemory(key)).toHaveLength(1);

      clearPrMemory(key);

      expect(loadPrMemory(key)).toEqual([]);
    });

    test("does not throw when file does not exist", () => {
      expect(() => clearPrMemory("nonexistent#999")).not.toThrow();
    });
  });

  describe("clearClosedPrMemories", () => {
    test("deletes only closed PRs and returns count", () => {
      const repo = "my-service";
      const memDir = join(tmp, "memory");
      mkdirSync(memDir, { recursive: true });

      appendPrMemory(buildPrMemoryKey(repo, 1), makeEntry({ commentId: 1 }));
      appendPrMemory(buildPrMemoryKey(repo, 2), makeEntry({ commentId: 2 }));
      appendPrMemory(buildPrMemoryKey(repo, 3), makeEntry({ commentId: 3 }));

      const cleared = clearClosedPrMemories(repo, [2]);

      expect(cleared).toBe(2);
      expect(loadPrMemory(buildPrMemoryKey(repo, 1))).toEqual([]);
      expect(loadPrMemory(buildPrMemoryKey(repo, 2))).toHaveLength(1);
      expect(loadPrMemory(buildPrMemoryKey(repo, 3))).toEqual([]);
    });

    test("returns 0 when memory dir does not exist", () => {
      expect(clearClosedPrMemories("no-repo", [1, 2])).toBe(0);
    });

    test("ignores files from other repos", () => {
      appendPrMemory(buildPrMemoryKey("repo-a", 1), makeEntry());
      appendPrMemory(buildPrMemoryKey("repo-b", 1), makeEntry());

      const cleared = clearClosedPrMemories("repo-a", []);

      expect(cleared).toBe(1);
      expect(loadPrMemory(buildPrMemoryKey("repo-b", 1))).toHaveLength(1);
    });
  });
});
