import { describe, it, expect, vi, beforeEach } from "vitest";
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
import TokenWidget from "@/components/token/TokenWidget";

// Track the mock pathname so individual tests can override it
let mockPathname = "/dashboard";

vi.mock("next/navigation", () => ({
  usePathname: () => mockPathname,
}));

// Mock react-markdown to avoid ESM transform issues in test env
vi.mock("react-markdown", () => ({
  default: ({ children }: { children: string }) => <span>{children}</span>,
}));

vi.mock("remark-gfm", () => ({
  default: () => {},
}));

function mockFetchAlerts(alerts: Array<{ id: string; severity: string; acknowledged: boolean }>) {
  return vi.fn().mockImplementation((url: string) => {
    if (typeof url === "string" && url.includes("/api/token/alerts")) {
      return Promise.resolve({
        ok: true,
        json: () => Promise.resolve({ alerts }),
      });
    }
    return Promise.resolve({ ok: true, json: () => Promise.resolve({}) });
  });
}

describe("TokenWidget", () => {
  beforeEach(() => {
    vi.clearAllMocks();
    mockPathname = "/dashboard";
    vi.stubGlobal("fetch", mockFetchAlerts([]));
  });

  it("renders the floating button", () => {
    render(<TokenWidget />);
    const button = screen.getByTestId("token-widget-button");
    expect(button).toBeDefined();
    expect(button.getAttribute("aria-label")).toBe("Open Token assistant");
  });

  it("opens the chat panel when the button is clicked", async () => {
    render(<TokenWidget />);
    const button = screen.getByTestId("token-widget-button");
    fireEvent.click(button);

    await waitFor(() => {
      expect(screen.getByTestId("token-widget-panel")).toBeDefined();
    });

    // Should have input and send button
    expect(screen.getByTestId("token-widget-input")).toBeDefined();
    expect(screen.getByTestId("token-widget-send")).toBeDefined();
  });

  it("shows the input field and send button in the chat panel", async () => {
    render(<TokenWidget />);
    fireEvent.click(screen.getByTestId("token-widget-button"));

    await waitFor(() => {
      const input = screen.getByTestId("token-widget-input");
      expect(input).toBeDefined();
      expect(input.getAttribute("placeholder")).toBe("Ask Token...");
    });

    const sendBtn = screen.getByTestId("token-widget-send");
    expect(sendBtn).toBeDefined();
    expect(sendBtn.getAttribute("aria-label")).toBe("Send message");
  });

  it("minimizes the panel back to the floating button", async () => {
    render(<TokenWidget />);
    // Open
    fireEvent.click(screen.getByTestId("token-widget-button"));
    await waitFor(() => {
      expect(screen.getByTestId("token-widget-panel")).toBeDefined();
    });

    // Minimize
    const minimizeBtn = screen.getByTestId("token-widget-minimize");
    fireEvent.click(minimizeBtn);

    await waitFor(() => {
      expect(screen.getByTestId("token-widget-button")).toBeDefined();
    });
    expect(screen.queryByTestId("token-widget-panel")).toBeNull();
  });

  it("is hidden on the /dashboard/token path", () => {
    mockPathname = "/dashboard/token";
    const { container } = render(<TokenWidget />);
    expect(container.innerHTML).toBe("");
  });

  it("shows alert badge count from the API", async () => {
    vi.stubGlobal(
      "fetch",
      mockFetchAlerts([
        { id: "a1", severity: "critical", acknowledged: false },
        { id: "a2", severity: "warning", acknowledged: false },
        { id: "a3", severity: "info", acknowledged: false },
        { id: "a4", severity: "critical", acknowledged: true },
      ]),
    );

    render(<TokenWidget />);

    // Wait for alerts to load — 2 unacknowledged critical/warning alerts
    await waitFor(() => {
      const badge = screen.getByTestId("token-widget-badge");
      expect(badge).toBeDefined();
      expect(badge.textContent).toBe("2");
    });
  });

  it("does not show badge when there are no unread alerts", async () => {
    vi.stubGlobal(
      "fetch",
      mockFetchAlerts([
        { id: "a1", severity: "critical", acknowledged: true },
        { id: "a2", severity: "info", acknowledged: false },
      ]),
    );

    render(<TokenWidget />);

    // Wait for fetch to complete
    await waitFor(() => {
      expect(fetch).toHaveBeenCalled();
    });

    expect(screen.queryByTestId("token-widget-badge")).toBeNull();
  });

  it("sends a message and displays streaming response", async () => {
    // Build a fake SSE stream
    const ssePayload = [
      'data: {"conversationId":"conv-1","intent":"chat"}\n\n',
      'data: {"token":"Hello "}\n\n',
      'data: {"token":"world"}\n\n',
      'data: {"done":true,"model":"gemini-2.5-flash","costUsd":0.001,"latencyMs":800}\n\n',
    ].join("");

    const encoder = new TextEncoder();
    const stream = new ReadableStream({
      start(controller) {
        controller.enqueue(encoder.encode(ssePayload));
        controller.close();
      },
    });

    vi.stubGlobal(
      "fetch",
      vi.fn().mockImplementation((url: string) => {
        if (typeof url === "string" && url.includes("/api/token/alerts")) {
          return Promise.resolve({
            ok: true,
            json: () => Promise.resolve({ alerts: [] }),
          });
        }
        if (typeof url === "string" && url.includes("/api/token/chat")) {
          return Promise.resolve({
            ok: true,
            body: stream,
          });
        }
        return Promise.resolve({ ok: true, json: () => Promise.resolve({}) });
      }),
    );

    render(<TokenWidget />);
    fireEvent.click(screen.getByTestId("token-widget-button"));

    await waitFor(() => {
      expect(screen.getByTestId("token-widget-input")).toBeDefined();
    });

    const input = screen.getByTestId("token-widget-input");
    fireEvent.change(input, { target: { value: "Hello Token" } });
    fireEvent.click(screen.getByTestId("token-widget-send"));

    // User message appears
    await waitFor(() => {
      expect(screen.getByText("Hello Token")).toBeDefined();
    });

    // Assistant response streams in
    await waitFor(() => {
      expect(screen.getByText("Hello world")).toBeDefined();
    });
  });

  it("retains messages when minimized and re-opened", async () => {
    // Set up fetch to handle alerts only
    vi.stubGlobal("fetch", mockFetchAlerts([]));

    render(<TokenWidget />);
    fireEvent.click(screen.getByTestId("token-widget-button"));

    await waitFor(() => {
      expect(screen.getByTestId("token-widget-panel")).toBeDefined();
    });

    // The empty state message should show
    expect(screen.getByText(/Ask Token about models/)).toBeDefined();

    // Minimize
    fireEvent.click(screen.getByTestId("token-widget-minimize"));
    await waitFor(() => {
      expect(screen.getByTestId("token-widget-button")).toBeDefined();
    });

    // Re-open — same state should be preserved (empty state text still there)
    fireEvent.click(screen.getByTestId("token-widget-button"));
    await waitFor(() => {
      expect(screen.getByTestId("token-widget-panel")).toBeDefined();
      expect(screen.getByText(/Ask Token about models/)).toBeDefined();
    });
  });
});
