import { act, renderHook } from "@testing-library/react";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import type { Message } from "../types";
import { useChatHistory } from "./useChatHistory";

const PROJECT = "/tmp/test-project";
const STORAGE_KEY = `storykit-chat-history:${PROJECT}`;
const LIMIT_KEY = `storykit-chat-history-limit:${PROJECT}`;

const sampleMessages: Message[] = [
	{ role: "user", content: "Hello" },
	{ role: "assistant", content: "Hi there!" },
];

function makeMessages(count: number): Message[] {
	return Array.from({ length: count }, (_, i) => ({
		role: "user" as const,
		content: `Message ${i + 1}`,
	}));
}

describe("useChatHistory", async () => {
	beforeEach(() => {
		localStorage.clear();
	});

	afterEach(() => {
		localStorage.clear();
	});

	it("AC1: restores messages from localStorage on mount", async () => {
		localStorage.setItem(STORAGE_KEY, JSON.stringify(sampleMessages));

		const { result } = renderHook(() => useChatHistory(PROJECT));

		expect(result.current.messages).toEqual(sampleMessages);
	});

	it("AC1: returns empty array when localStorage has no data", async () => {
		const { result } = renderHook(() => useChatHistory(PROJECT));

		expect(result.current.messages).toEqual([]);
	});

	it("AC1: returns empty array when localStorage contains invalid JSON", async () => {
		localStorage.setItem(STORAGE_KEY, "not-json{{{");

		const { result } = renderHook(() => useChatHistory(PROJECT));

		expect(result.current.messages).toEqual([]);
	});

	it("AC1: returns empty array when localStorage contains a non-array", async () => {
		localStorage.setItem(STORAGE_KEY, JSON.stringify({ not: "array" }));

		const { result } = renderHook(() => useChatHistory(PROJECT));

		expect(result.current.messages).toEqual([]);
	});

	it("AC2: saves messages to localStorage when setMessages is called with an array", async () => {
		const { result } = renderHook(() => useChatHistory(PROJECT));

		await act(async () => {
			result.current.setMessages(sampleMessages);
		});

		const stored = JSON.parse(localStorage.getItem(STORAGE_KEY) ?? "[]");
		expect(stored).toEqual(sampleMessages);
	});

	it("AC2: saves messages to localStorage when setMessages is called with updater function", async () => {
		const { result } = renderHook(() => useChatHistory(PROJECT));

		await act(async () => {
			result.current.setMessages(() => sampleMessages);
		});

		const stored = JSON.parse(localStorage.getItem(STORAGE_KEY) ?? "[]");
		expect(stored).toEqual(sampleMessages);
	});

	it("AC3: clearMessages removes messages from state and localStorage", async () => {
		localStorage.setItem(STORAGE_KEY, JSON.stringify(sampleMessages));

		const { result } = renderHook(() => useChatHistory(PROJECT));

		expect(result.current.messages).toEqual(sampleMessages);

		await act(async () => {
			result.current.clearMessages();
		});

		expect(result.current.messages).toEqual([]);
		expect(localStorage.getItem(STORAGE_KEY)).toBeNull();
	});

	it("AC4: handles localStorage quota errors gracefully", async () => {
		const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
		const setItemSpy = vi
			.spyOn(Storage.prototype, "setItem")
			.mockImplementation(() => {
				throw new DOMException("QuotaExceededError");
			});

		const { result } = renderHook(() => useChatHistory(PROJECT));

		// Should not throw
		await act(async () => {
			result.current.setMessages(sampleMessages);
		});

		// State should still update even though storage failed
		expect(result.current.messages).toEqual(sampleMessages);
		expect(warnSpy).toHaveBeenCalledWith(
			"Failed to persist chat history to localStorage:",
			expect.any(DOMException),
		);

		warnSpy.mockRestore();
		setItemSpy.mockRestore();
	});

	it("AC5: scopes storage key to project path", async () => {
		const projectA = "/projects/a";
		const projectB = "/projects/b";
		const keyA = `storykit-chat-history:${projectA}`;
		const keyB = `storykit-chat-history:${projectB}`;

		const messagesA: Message[] = [{ role: "user", content: "From project A" }];
		const messagesB: Message[] = [{ role: "user", content: "From project B" }];

		localStorage.setItem(keyA, JSON.stringify(messagesA));
		localStorage.setItem(keyB, JSON.stringify(messagesB));

		const { result: resultA } = renderHook(() => useChatHistory(projectA));
		const { result: resultB } = renderHook(() => useChatHistory(projectB));

		expect(resultA.current.messages).toEqual(messagesA);
		expect(resultB.current.messages).toEqual(messagesB);
	});

	it("AC2: removes localStorage key when messages are set to empty array", async () => {
		localStorage.setItem(STORAGE_KEY, JSON.stringify(sampleMessages));

		const { result } = renderHook(() => useChatHistory(PROJECT));

		await act(async () => {
			result.current.setMessages([]);
		});

		expect(localStorage.getItem(STORAGE_KEY)).toBeNull();
	});

	// --- Story 179: Chat history pruning tests ---

	it("S179: default limit of 200 is applied when saving to localStorage", async () => {
		const { result } = renderHook(() => useChatHistory(PROJECT));

		expect(result.current.maxMessages).toBe(200);
	});

	it("S179: messages are pruned from the front when exceeding the limit", async () => {
		// Set a small limit to make testing practical
		localStorage.setItem(LIMIT_KEY, "3");

		const { result } = renderHook(() => useChatHistory(PROJECT));
		const fiveMessages = makeMessages(5);

		await act(async () => {
			result.current.setMessages(fiveMessages);
		});

		// localStorage should contain only the last 3 messages
		const stored: Message[] = JSON.parse(
			localStorage.getItem(STORAGE_KEY) ?? "[]",
		);
		expect(stored).toEqual(fiveMessages.slice(-3));
		expect(stored).toHaveLength(3);
		expect(stored[0].content).toBe("Message 3");
	});

	it("S179: messages under the limit are not pruned", async () => {
		localStorage.setItem(LIMIT_KEY, "10");

		const { result } = renderHook(() => useChatHistory(PROJECT));
		const threeMessages = makeMessages(3);

		await act(async () => {
			result.current.setMessages(threeMessages);
		});

		const stored: Message[] = JSON.parse(
			localStorage.getItem(STORAGE_KEY) ?? "[]",
		);
		expect(stored).toEqual(threeMessages);
		expect(stored).toHaveLength(3);
	});

	it("S179: limit is configurable via localStorage key", async () => {
		localStorage.setItem(LIMIT_KEY, "5");

		const { result } = renderHook(() => useChatHistory(PROJECT));

		expect(result.current.maxMessages).toBe(5);
	});

	it("S179: setMaxMessages updates the limit and persists it", async () => {
		const { result } = renderHook(() => useChatHistory(PROJECT));

		await act(async () => {
			result.current.setMaxMessages(50);
		});

		expect(result.current.maxMessages).toBe(50);
		expect(localStorage.getItem(LIMIT_KEY)).toBe("50");
	});

	it("S179: a limit of 0 means unlimited (no pruning)", async () => {
		localStorage.setItem(LIMIT_KEY, "0");

		const { result } = renderHook(() => useChatHistory(PROJECT));
		const manyMessages = makeMessages(500);

		await act(async () => {
			result.current.setMessages(manyMessages);
		});

		const stored: Message[] = JSON.parse(
			localStorage.getItem(STORAGE_KEY) ?? "[]",
		);
		expect(stored).toHaveLength(500);
		expect(stored).toEqual(manyMessages);
	});

	it("S179: changing the limit re-prunes messages on next save", async () => {
		const { result } = renderHook(() => useChatHistory(PROJECT));
		const tenMessages = makeMessages(10);

		await act(async () => {
			result.current.setMessages(tenMessages);
		});

		// All 10 saved (default limit 200 > 10)
		let stored: Message[] = JSON.parse(
			localStorage.getItem(STORAGE_KEY) ?? "[]",
		);
		expect(stored).toHaveLength(10);

		// Now lower the limit — the effect re-runs and prunes
		await act(async () => {
			result.current.setMaxMessages(3);
		});

		stored = JSON.parse(localStorage.getItem(STORAGE_KEY) ?? "[]");
		expect(stored).toHaveLength(3);
		expect(stored[0].content).toBe("Message 8");
	});

	it("S179: invalid limit in localStorage falls back to default", async () => {
		localStorage.setItem(LIMIT_KEY, "not-a-number");

		const { result } = renderHook(() => useChatHistory(PROJECT));

		expect(result.current.maxMessages).toBe(200);
	});

	it("S179: negative limit in localStorage falls back to default", async () => {
		localStorage.setItem(LIMIT_KEY, "-5");

		const { result } = renderHook(() => useChatHistory(PROJECT));

		expect(result.current.maxMessages).toBe(200);
	});
});
