import { beforeEach, describe, expect, it } from "bun:test";
import { GET as getMessages, POST as postMessage } from "../app/api/chat/messages/route";
import { GET as getStream } from "../app/api/chat/stream/route";
import {
  __resetChatStoreForTests,
  appendMessage,
  getMessages as getStoreMessages,
  MAX_HISTORY_MESSAGES,
} from "../src/server/application/chat-store";
import { getAIAdapter, setAIAdapter } from "../src/server/application/ai-adapter";
import { createTestRequest, parseJsonResponse } from "./helpers";

describe("realtime chat starter template", () => {
  const originalAdapter = getAIAdapter();

  beforeEach(() => {
    __resetChatStoreForTests();
    setAIAdapter(originalAdapter);
  });

  it("returns empty history by default", async () => {
    const request = createTestRequest("http://localhost:3000/api/chat/messages");
    const response = getMessages(request);
    expect(response.status).toBe(200);

    const json = await parseJsonResponse<{ messages: unknown[] }>(response);
    expect(Array.isArray(json.messages)).toBe(true);
    expect(json.messages.length).toBe(0);
  });

  it("rejects invalid message payload", async () => {
    const request = createTestRequest("http://localhost:3000/api/chat/messages", {
      method: "POST",
      body: { text: "   " },
    });

    const response = await postMessage(request);
    expect(response.status).toBe(400);
  });

  it("rejects malformed json payload", async () => {
    const request = new Request("http://localhost:3000/api/chat/messages", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: "{not-valid-json",
    });

    const response = await postMessage(request);
    expect(response.status).toBe(400);
  });

  it("accepts valid payload and appends assistant reply", async () => {
    const request = createTestRequest("http://localhost:3000/api/chat/messages", {
      method: "POST",
      body: { text: "hello" },
    });

    const response = await postMessage(request);
    expect(response.status).toBe(201);

    const historyRequest = createTestRequest("http://localhost:3000/api/chat/messages");
    const history = getMessages(historyRequest);
    const json = await parseJsonResponse<{ messages: Array<{ role: string }> }>(history);

    expect(json.messages.length).toBe(2);
    expect(json.messages.at(-1)?.role).toBe("assistant");
  });

  it("keeps user message when adapter completion fails", async () => {
    setAIAdapter({
      async complete() {
        throw new Error("adapter-failure");
      },
    });

    const request = createTestRequest("http://localhost:3000/api/chat/messages", {
      method: "POST",
      body: { text: "hello" },
    });

    const response = await postMessage(request);
    expect(response.status).toBe(201);

    const messages = getStoreMessages();
    expect(messages.length).toBe(1);
    expect(messages[0]?.role).toBe("user");
  });

  it("caps in-memory history size", () => {
    for (let i = 0; i < MAX_HISTORY_MESSAGES + 25; i++) {
      appendMessage("user", `msg-${i}`);
    }

    const messages = getStoreMessages();
    expect(messages.length).toBe(MAX_HISTORY_MESSAGES);
    expect(messages[0]?.text).toBe("msg-25");
  });

  it("exposes SSE stream endpoint", () => {
    const request = createTestRequest("http://localhost:3000/api/chat/stream");
    const response = getStream(request);
    expect(response.status).toBe(200);
    expect(response.headers.get("Content-Type")).toContain("text/event-stream");
  });

  it("SSE stream emits snapshot and live message events", async () => {
    const abortController = new AbortController();
    const request = new Request("http://localhost:3000/api/chat/stream", {
      signal: abortController.signal,
    });

    const response = getStream(request);
    const reader = response.body?.getReader();
    expect(reader).toBeDefined();

    const decoder = new TextDecoder();

    try {
      const firstChunk = await reader!.read();
      expect(firstChunk.done).toBe(false);
      const firstText = decoder.decode(firstChunk.value);
      expect(firstText).toContain('"type":"snapshot"');

      appendMessage("user", "live-event");

      const secondChunk = await reader!.read();
      expect(secondChunk.done).toBe(false);
      const secondText = decoder.decode(secondChunk.value);
      expect(secondText).toContain('"type":"message"');
      expect(secondText).toContain('"text":"live-event"');
    } finally {
      abortController.abort();
    }
  });

  it("supports catch-up resume from lastEventId without snapshot", async () => {
    appendMessage("user", "m1");
    appendMessage("assistant", "m2");

    const request = createTestRequest("http://localhost:3000/api/chat/stream?lastEventId=msg-1");
    const response = getStream(request);

    const reader = response.body?.getReader();
    expect(reader).toBeDefined();

    const firstChunk = await reader!.read();
    expect(firstChunk.done).toBe(false);

    const firstText = new TextDecoder().decode(firstChunk.value);
    expect(firstText).toContain("id: msg-2");
    expect(firstText).toContain('"type":"message"');
    expect(firstText).toContain('"text":"m2"');
    expect(firstText).not.toContain('"type":"snapshot"');

    appendMessage("user", "m3-live");

    const secondChunk = await reader!.read();
    expect(secondChunk.done).toBe(false);

    const secondText = new TextDecoder().decode(secondChunk.value);
    expect(secondText).toContain("id: msg-3");
    expect(secondText).toContain('"type":"message"');
    expect(secondText).toContain('"text":"m3-live"');

    await reader!.cancel();
  });

  it("falls back to snapshot when lastEventId is out of catch-up range", async () => {
    appendMessage("user", "hello");

    const request = createTestRequest("http://localhost:3000/api/chat/stream?lastEventId=msg-9999");
    const response = getStream(request);

    const reader = response.body?.getReader();
    expect(reader).toBeDefined();

    const chunk = await reader!.read();
    expect(chunk.done).toBe(false);

    const text = new TextDecoder().decode(chunk.value);
    expect(text).toContain('"type":"snapshot"');
    expect(text).toContain('"text":"hello"');

    await reader!.cancel();
  });

  it("includes essential ARIA attributes in chat UI", async () => {
    const source = await Bun.file(
      new URL("../src/client/features/chat/realtime-chat-starter.client.tsx", import.meta.url),
    ).text();

    expect(source).toContain('role="log"');
    expect(source).toContain('aria-live="polite"');
    expect(source).toContain('aria-label="Chat messages"');
    expect(source).toContain('aria-label="Chat message input"');
    expect(source).toContain('aria-describedby="chat-input-description"');
    expect(source).toContain('id="chat-input-description"');
    expect(source).toContain('aria-label="Send message"');
  });
});
