import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { appendMessage, broadcastMessage, readInbox, sendPlainMessage } from "./messaging";
import * as paths from "./paths";

// Mock the paths to use a temporary directory
const testDir = path.join(os.tmpdir(), `pi-teams-test-${Date.now()}`);

describe("Messaging Utilities", () => {
	beforeEach(() => {
		if (fs.existsSync(testDir)) fs.rmSync(testDir, { recursive: true });
		fs.mkdirSync(testDir, { recursive: true });

		// Override paths to use testDir
		vi.spyOn(paths, "inboxPath").mockImplementation((_teamName, agentName) => {
			return path.join(testDir, "inboxes", `${agentName}.json`);
		});
		vi.spyOn(paths, "teamDir").mockReturnValue(testDir);
		vi.spyOn(paths, "configPath").mockImplementation((_teamName) => {
			return path.join(testDir, "config.json");
		});
	});

	afterEach(() => {
		vi.restoreAllMocks();
		if (fs.existsSync(testDir)) fs.rmSync(testDir, { recursive: true });
	});

	it("should append a message successfully", async () => {
		const msg = { from: "sender", text: "hello", timestamp: "now", read: false };
		await appendMessage("test-team", "receiver", msg);

		const inbox = await readInbox("test-team", "receiver", false, false);
		expect(inbox.length).toBe(1);
		expect(inbox[0].text).toBe("hello");
	});

	it("should handle concurrent appends (Stress Test)", async () => {
		const numMessages = 100;
		const promises = [];
		for (let i = 0; i < numMessages; i++) {
			promises.push(sendPlainMessage("test-team", `sender-${i}`, "receiver", `msg-${i}`, `summary-${i}`));
		}

		await Promise.all(promises);

		const inbox = await readInbox("test-team", "receiver", false, false);
		expect(inbox.length).toBe(numMessages);

		// Verify all messages are present
		const texts = inbox.map((m) => m.text).sort();
		for (let i = 0; i < numMessages; i++) {
			expect(texts).toContain(`msg-${i}`);
		}
	});

	it("should mark messages as read", async () => {
		await sendPlainMessage("test-team", "sender", "receiver", "msg1", "summary1");
		await sendPlainMessage("test-team", "sender", "receiver", "msg2", "summary2");

		// Read only unread messages
		const unread = await readInbox("test-team", "receiver", true, true);
		expect(unread.length).toBe(2);

		// Now all should be read
		const all = await readInbox("test-team", "receiver", false, false);
		expect(all.length).toBe(2);
		expect(all.every((m) => m.read)).toBe(true);
	});

	it("should broadcast message to all members except the sender", async () => {
		// Setup team config
		const config = {
			name: "test-team",
			members: [{ name: "sender" }, { name: "member1" }, { name: "member2" }],
		};
		const configFilePath = path.join(testDir, "config.json");
		fs.writeFileSync(configFilePath, JSON.stringify(config));

		await broadcastMessage("test-team", "sender", "broadcast text", "summary");

		// Check member1's inbox
		const inbox1 = await readInbox("test-team", "member1", false, false);
		expect(inbox1.length).toBe(1);
		expect(inbox1[0].text).toBe("broadcast text");
		expect(inbox1[0].from).toBe("sender");

		// Check member2's inbox
		const inbox2 = await readInbox("test-team", "member2", false, false);
		expect(inbox2.length).toBe(1);
		expect(inbox2[0].text).toBe("broadcast text");
		expect(inbox2[0].from).toBe("sender");

		// Check sender's inbox (should be empty)
		const inboxSender = await readInbox("test-team", "sender", false, false);
		expect(inboxSender.length).toBe(0);
	});
});
