// @vitest-environment jsdom
/**
 * Tests for OrgInfoTab — surfaces current org name/slug/UUID with copy
 * buttons, plus a list of the user's other orgs when applicable.
 *
 * Covers (≥3 cases per the closing-the-UX-gap brief):
 *   - Loading state (spinner + aria-live)
 *   - Renders current org matched by session.org_id, with UUID + slug + name
 *   - Copy button writes the UUID to navigator.clipboard
 *   - Falls back to host-slug match when session lookup fails
 *   - Lists other orgs when user belongs to multiple
 *   - Error banner when /cp/orgs throws
 *   - Empty/no-match state renders the recovery hint, not a crash
 */
import React from "react";
import { render, screen, fireEvent, cleanup, act, waitFor } from "@testing-library/react";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { OrgInfoTab } from "../OrgInfoTab";

const mockGet = vi.fn();
const mockFetchSession = vi.fn();
const mockGetTenantSlug = vi.fn();

vi.mock("@/lib/api", () => ({
  api: { get: (...args: unknown[]) => mockGet(...args) },
}));
vi.mock("@/lib/auth", () => ({
  fetchSession: (...args: unknown[]) => mockFetchSession(...args),
}));
vi.mock("@/lib/tenant", () => ({
  getTenantSlug: (...args: unknown[]) => mockGetTenantSlug(...args),
}));

// Stub clipboard
vi.stubGlobal("navigator", {
  clipboard: { writeText: vi.fn().mockResolvedValue(undefined) },
});

beforeEach(() => {
  vi.useRealTimers();
  mockGet.mockReset();
  mockFetchSession.mockReset();
  mockGetTenantSlug.mockReset();
  mockGetTenantSlug.mockReturnValue("");
  vi.mocked(navigator.clipboard.writeText).mockReset();
});

afterEach(() => {
  cleanup();
});

async function flush() {
  await act(async () => {
    await Promise.resolve();
    await Promise.resolve();
  });
}

const AGENTS_TEAM = {
  id: "2355b568-0799-4cc7-9e7f-806747f9958c",
  slug: "agents-team",
  name: "Agents Team",
  status: "running",
};
const OTHER_ORG = {
  id: "11111111-1111-4111-8111-111111111111",
  slug: "skunkworks",
  name: "Skunkworks",
  status: "running",
};

// ─── Loading ─────────────────────────────────────────────────────────────────

describe("OrgInfoTab — loading", () => {
  it("shows spinner while fetching", () => {
    mockGet.mockImplementation(() => new Promise(() => {}));
    mockFetchSession.mockImplementation(() => new Promise(() => {}));
    render(<OrgInfoTab />);
    const status = screen.getByRole("status");
    expect(status).toBeTruthy();
    expect(status.getAttribute("aria-live")).toBe("polite");
    expect(status.textContent).toContain("Loading organization");
  });
});

// ─── Current org renders + copy ──────────────────────────────────────────────

describe("OrgInfoTab — current org", () => {
  it("renders the org matched by session.org_id with name, slug, UUID", async () => {
    mockFetchSession.mockResolvedValue({
      user_id: "u-1",
      org_id: AGENTS_TEAM.id,
      email: "hongming@moleculesai.app",
    });
    mockGet.mockResolvedValue([AGENTS_TEAM, OTHER_ORG]);

    render(<OrgInfoTab />);
    await flush();
    await waitFor(() => screen.getByText("Current Organization"));

    // Name shown
    expect(screen.getByText("Agents Team")).toBeTruthy();
    // Slug shown
    expect(screen.getByText("agents-team")).toBeTruthy();
    // UUID shown
    expect(screen.getByText(AGENTS_TEAM.id)).toBeTruthy();
  });

  it("copy-UUID button writes the UUID to navigator.clipboard", async () => {
    mockFetchSession.mockResolvedValue({
      user_id: "u-1",
      org_id: AGENTS_TEAM.id,
      email: "hongming@moleculesai.app",
    });
    mockGet.mockResolvedValue([AGENTS_TEAM]);

    render(<OrgInfoTab />);
    await flush();
    await waitFor(() => screen.getByText(AGENTS_TEAM.id));

    const copyUuid = screen.getByRole("button", { name: /Copy UUID/i });
    fireEvent.click(copyUuid);

    expect(navigator.clipboard.writeText).toHaveBeenCalledWith(AGENTS_TEAM.id);
    // Optimistic "Copied" label flip
    await waitFor(() =>
      expect(
        screen.getByRole("button", { name: /Copy UUID/i }).textContent,
      ).toContain("Copied"),
    );
  });

  it("copy-Slug button writes the slug to navigator.clipboard", async () => {
    mockFetchSession.mockResolvedValue({
      user_id: "u-1",
      org_id: AGENTS_TEAM.id,
      email: "hongming@moleculesai.app",
    });
    mockGet.mockResolvedValue([AGENTS_TEAM]);

    render(<OrgInfoTab />);
    await flush();
    await waitFor(() => screen.getByText(AGENTS_TEAM.slug));

    fireEvent.click(screen.getByRole("button", { name: /Copy Slug/i }));
    expect(navigator.clipboard.writeText).toHaveBeenCalledWith(AGENTS_TEAM.slug);
  });
});

// ─── Fallback: host-slug match when session fails ────────────────────────────

describe("OrgInfoTab — fallbacks", () => {
  it("falls back to host-slug match when fetchSession rejects", async () => {
    mockFetchSession.mockRejectedValue(new Error("session probe failed"));
    mockGetTenantSlug.mockReturnValue("agents-team");
    mockGet.mockResolvedValue({ orgs: [AGENTS_TEAM, OTHER_ORG] }); // wrapped shape

    render(<OrgInfoTab />);
    await flush();
    await waitFor(() => screen.getByText("Current Organization"));

    expect(screen.getByText("Agents Team")).toBeTruthy();
    expect(screen.getByText(AGENTS_TEAM.id)).toBeTruthy();
  });

  it("lists other orgs the user belongs to under a separate header", async () => {
    mockFetchSession.mockResolvedValue({
      user_id: "u-1",
      org_id: AGENTS_TEAM.id,
      email: "hongming@moleculesai.app",
    });
    mockGet.mockResolvedValue([AGENTS_TEAM, OTHER_ORG]);

    render(<OrgInfoTab />);
    await flush();
    await waitFor(() => screen.getByText(/Your other organizations/));

    expect(screen.getByText("Skunkworks")).toBeTruthy();
    expect(screen.getByText(OTHER_ORG.id)).toBeTruthy();
  });
});

// ─── Error + empty handling ──────────────────────────────────────────────────

describe("OrgInfoTab — error + empty", () => {
  it("renders an error banner when /cp/orgs throws", async () => {
    mockFetchSession.mockResolvedValue(null);
    mockGet.mockRejectedValue(new Error("API GET /cp/orgs: 500 boom"));

    render(<OrgInfoTab />);
    await flush();
    await waitFor(() => screen.getByText(/500 boom/));
    expect(screen.queryByText("Current Organization")).toBeNull();
  });

  it("renders the recovery hint when no org matches (no crash)", async () => {
    mockFetchSession.mockResolvedValue(null);
    mockGetTenantSlug.mockReturnValue("");
    mockGet.mockResolvedValue([]);

    render(<OrgInfoTab />);
    await flush();
    await waitFor(() =>
      screen.getByText(/No organization found for this session/),
    );
  });
});
