import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { render, screen, fireEvent, cleanup } from "@testing-library/react";
import { NotificationCenter } from "./notification-center";
import { NotificationProvider } from "../../../state/notifications";

vi.mock("../../../api/notifications", () => ({
	getNotifications: vi.fn(),
	getUnreadCount: vi.fn(),
	markNotificationRead: vi.fn(),
	markAllNotificationsRead: vi.fn(),
	dismissNotification: vi.fn(),
	clearAllNotifications: vi.fn(),
}));

import { getNotifications, getUnreadCount } from "../../../api/notifications";

const mockedGetNotifications = vi.mocked(getNotifications);
const mockedGetUnreadCount = vi.mocked(getUnreadCount);

const makeNotification = (id: string, overrides?: Record<string, unknown>) => ({
	id,
	user_id: "user-1",
	category: "instance",
	severity: "info" as const,
	title: `Notification ${id}`,
	message: null,
	source_type: null,
	source_id: null,
	metadata: {},
	read_at: null,
	dismissed_at: null,
	created_at: "2026-05-29T10:00:00Z",
	...overrides,
});

function wrapper({ children }: { children: React.ReactNode }) {
	return <NotificationProvider>{children}</NotificationProvider>;
}

describe("NotificationCenter", () => {
	beforeEach(() => {
		vi.useFakeTimers({ shouldAdvanceTime: true });
		mockedGetNotifications.mockResolvedValue({
			items: [],
			total: 0,
			limit: 20,
			offset: 0,
		});
		mockedGetUnreadCount.mockResolvedValue(0);
	});

	afterEach(() => {
		vi.useRealTimers();
		vi.clearAllMocks();
		cleanup();
	});

	it("renders bell icon", () => {
		render(<NotificationCenter />, { wrapper });
		expect(
			screen.getByRole("button", { name: /notifications/i }),
		).toBeInTheDocument();
	});

	it("shows badge when unread count > 0", async () => {
		mockedGetUnreadCount.mockResolvedValue(3);
		render(<NotificationCenter />, { wrapper });

		await vi.advanceTimersByTimeAsync(100);
		expect(screen.getByText("3")).toBeInTheDocument();
	});

	it("hides badge when unread count is 0", () => {
		render(<NotificationCenter />, { wrapper });
		expect(screen.queryByText("0")).not.toBeInTheDocument();
	});

	it("opens dropdown on bell click", () => {
		render(<NotificationCenter />, { wrapper });
		fireEvent.click(screen.getByRole("button", { name: /notifications/i }));
		expect(screen.getByRole("dialog")).toBeInTheDocument();
	});

	it("closes dropdown on outside click", () => {
		render(
			<div>
				<div data-testid="outside">Outside</div>
				<NotificationCenter />
			</div>,
			{ wrapper },
		);
		fireEvent.click(screen.getByRole("button", { name: /notifications/i }));
		expect(screen.getByRole("dialog")).toBeInTheDocument();

		fireEvent.mouseDown(screen.getByTestId("outside"));
		expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
	});

	it("closes dropdown on escape", () => {
		render(<NotificationCenter />, { wrapper });
		fireEvent.click(screen.getByRole("button", { name: /notifications/i }));
		expect(screen.getByRole("dialog")).toBeInTheDocument();

		fireEvent.keyDown(document, { key: "Escape" });
		expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
	});

	it("renders empty state when no notifications", () => {
		render(<NotificationCenter />, { wrapper });
		fireEvent.click(screen.getByRole("button", { name: /notifications/i }));
		expect(screen.getByText("No notifications")).toBeInTheDocument();
	});

	it("renders notification items", async () => {
		mockedGetNotifications.mockResolvedValue({
			items: [makeNotification("1"), makeNotification("2")],
			total: 2,
			limit: 20,
			offset: 0,
		});

		render(<NotificationCenter />, { wrapper });
		fireEvent.click(screen.getByRole("button", { name: /notifications/i }));

		await vi.advanceTimersByTimeAsync(100);
		expect(screen.getByText("Notification 1")).toBeInTheDocument();
		expect(screen.getByText("Notification 2")).toBeInTheDocument();
	});

	it("calls markAllRead on footer button click", async () => {
		mockedGetNotifications.mockResolvedValue({
			items: [makeNotification("1")],
			total: 1,
			limit: 20,
			offset: 0,
		});

		render(<NotificationCenter />, { wrapper });
		fireEvent.click(screen.getByRole("button", { name: /notifications/i }));

		await vi.advanceTimersByTimeAsync(100);
		fireEvent.click(screen.getByRole("button", { name: /mark all as read/i }));

		const { markAllNotificationsRead: mockMarkAll } = await import(
			"../../../api/notifications"
		);
		expect(vi.mocked(mockMarkAll)).toHaveBeenCalled();
	});

	it("calls clearAll on clear-all button click", async () => {
		mockedGetNotifications.mockResolvedValue({
			items: [makeNotification("1")],
			total: 1,
			limit: 20,
			offset: 0,
		});

		render(<NotificationCenter />, { wrapper });
		fireEvent.click(screen.getByRole("button", { name: /notifications/i }));

		await vi.advanceTimersByTimeAsync(100);
		fireEvent.click(screen.getByRole("button", { name: /clear all/i }));

		const { clearAllNotifications: mockClearAll } = await import(
			"../../../api/notifications"
		);
		expect(vi.mocked(mockClearAll)).toHaveBeenCalled();
	});

	it("refreshes list immediately on open", async () => {
		render(<NotificationCenter />, { wrapper });
		fireEvent.click(screen.getByRole("button", { name: /notifications/i }));

		await vi.advanceTimersByTimeAsync(100);
		expect(mockedGetNotifications).toHaveBeenCalled();
	});
});
