import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { act } from "react";
import { createRoot, type Root } from "react-dom/client";
import { MemoryRouter, Route, Routes } from "react-router-dom";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import AppLayout from "./AppLayout";

const authState = {
  isAuthenticated: true,
  isLoading: false,
  activeNavigator: undefined as string | undefined,
  error: null as Error | null,
  user: {
    access_token: "access-token",
    expires_at: Math.floor(Date.now() / 1000) + 120,
    profile: {
      sub: "user-1",
      name: "Dev Admin",
      email: "dev@example.com",
      role: "super_admin",
    },
  },
  signinSilent: vi.fn(),
  removeUser: vi.fn(),
};

vi.mock("react-oidc-context", () => ({
  useAuth: () => authState,
}));

vi.mock("../../features/auth/authApi", () => ({
  fetchMe: vi.fn(async () => ({
    id: "user-1",
    name: "Fetched Dev Admin",
    email: "fetched@example.com",
    role: "super_admin",
  })),
}));

vi.mock("../../lib/i18n", () => ({
  t: (key: string, fallback?: string, vars?: Record<string, unknown>) => {
    let text = fallback ?? key;
    for (const [name, value] of Object.entries(vars ?? {})) {
      text = text.replaceAll(`{{${name}}}`, String(value));
    }
    return text;
  },
}));

const roots: Root[] = [];

beforeEach(() => {
  authState.isAuthenticated = true;
  authState.isLoading = false;
  authState.activeNavigator = undefined;
  authState.error = null;
  authState.user.expires_at = Math.floor(Date.now() / 1000) + 120;
  authState.signinSilent.mockReset();
  authState.signinSilent.mockResolvedValue(undefined);
  authState.removeUser.mockReset();
  window.localStorage.clear();
  vi.spyOn(window, "confirm").mockReturnValue(true);
});

afterEach(() => {
  for (const root of roots.splice(0)) {
    act(() => {
      root.unmount();
    });
  }
  vi.restoreAllMocks();
});

async function renderLayout(initialEntry = "/clients") {
  const container = document.createElement("div");
  document.body.appendChild(container);
  const root = createRoot(container);
  roots.push(root);
  const queryClient = new QueryClient({
    defaultOptions: {
      queries: { retry: false },
      mutations: { retry: false },
    },
  });

  await act(async () => {
    root.render(
      <QueryClientProvider client={queryClient}>
        <MemoryRouter initialEntries={[initialEntry]}>
          <Routes>
            <Route path="/" element={<AppLayout />}>
              <Route path="clients" element={<div>Client outlet</div>} />
              <Route path="profile" element={<div>Profile outlet</div>} />
            </Route>
          </Routes>
        </MemoryRouter>
      </QueryClientProvider>,
    );
  });

  await act(async () => {
    await new Promise((resolve) => setTimeout(resolve, 0));
  });

  return container;
}

describe("devfront AppLayout", () => {
  it("renders shell navigation, profile summary, and outlet content", async () => {
    const container = await renderLayout();

    expect(container.textContent).toContain("Developer Console");
    expect(container.textContent).toContain("Clients");
    expect(container.textContent).toContain("Client outlet");
    expect(container.textContent).toContain("Fetched Dev Admin");
    expect(document.documentElement.classList.contains("light")).toBe(true);
  });

  it("toggles the sidebar and persists the collapsed state", async () => {
    const container = await renderLayout();

    const collapseButton = container.querySelector(
      'button[aria-label="사이드바 접기"]',
    ) as HTMLButtonElement;
    await act(async () => {
      collapseButton.click();
    });

    expect(window.localStorage.getItem("baron_shell_sidebar_collapsed")).toBe(
      "true",
    );
    expect(
      container.querySelector('button[aria-label="사이드바 펼치기"]'),
    ).not.toBeNull();
  });

  it("toggles profile menu, navigates to profile, toggles theme, and logs out", async () => {
    const container = await renderLayout();

    const themeButton = container.querySelector(
      'button[aria-label="Toggle theme"]',
    ) as HTMLButtonElement;
    await act(async () => {
      themeButton.click();
    });
    expect(document.documentElement.classList.contains("dark")).toBe(true);

    const profileButton = container.querySelector(
      'button[aria-label="Open account menu"]',
    ) as HTMLButtonElement;
    await act(async () => {
      profileButton.click();
    });
    expect(container.textContent).toContain("My Profile");

    const profileMenuItem = Array.from(
      container.querySelectorAll('button[role="menuitem"]'),
    ).find((button) => button.textContent?.includes("My Profile"));
    await act(async () => {
      (profileMenuItem as HTMLButtonElement).click();
    });
    expect(container.textContent).toContain("Profile outlet");

    const logoutButton = Array.from(container.querySelectorAll("button")).find(
      (button) => button.textContent?.includes("Logout"),
    );
    await act(async () => {
      (logoutButton as HTMLButtonElement).click();
    });
    expect(window.confirm).toHaveBeenCalled();
    expect(authState.removeUser).toHaveBeenCalled();
  });

  it("attempts silent renewal after user action when the session is expiring", async () => {
    authState.user.expires_at = Math.floor(Date.now() / 1000) + 60;
    await renderLayout();

    await act(async () => {
      window.dispatchEvent(new KeyboardEvent("keydown", { key: "Tab" }));
    });

    expect(authState.signinSilent).toHaveBeenCalled();
  });
});
