import {
  describe,
  it,
  expect,
  vi,
  beforeAll,
  afterEach,
} from "vitest";
import {
  render,
  screen,
  cleanup,
  fireEvent,
  waitFor,
} from "@testing-library/react";
import { MantineProvider } from "@mantine/core";

// --- Mocks for the heavy / networked module graph ---------------------------
// EditSpaceForm wires the "Enable Git sync" Switch to a TanStack-Query mutation
// (useUpdateSpaceMutation). We mock ONLY that hook so the test fully controls
// mutateAsync (resolve / reject) and isPending, and stub i18n. The real Mantine
// Switch is rendered so the checkbox role / disabled state is meaningful.

// i18n: identity translator — labels stay as their English keys for queries.
vi.mock("react-i18next", () => ({
  useTranslation: () => ({ t: (key: string) => key }),
}));

// Mutation hook: a controllable mutateAsync plus a togglable isPending.
const mutateAsync = vi.fn();
let isPending = false;
vi.mock("@/features/space/queries/space-query.ts", () => ({
  useUpdateSpaceMutation: () => ({
    mutateAsync,
    get isPending() {
      return isPending;
    },
  }),
}));

// jsdom lacks matchMedia, which MantineProvider's color-scheme hook needs.
beforeAll(() => {
  if (!window.matchMedia) {
    window.matchMedia = (query: string) =>
      ({
        matches: false,
        media: query,
        onchange: null,
        addListener: () => {},
        removeListener: () => {},
        addEventListener: () => {},
        removeEventListener: () => {},
        dispatchEvent: () => false,
      }) as unknown as MediaQueryList;
  }
});

import { EditSpaceForm } from "./edit-space-form";
import type { ISpace } from "@/features/space/types/space.types.ts";

function makeSpace(overrides: Partial<ISpace> = {}): ISpace {
  return {
    id: "space-1",
    name: "Engineering",
    description: "",
    slug: "eng",
    hostname: "host",
    creatorId: "u1",
    createdAt: new Date("2026-01-01"),
    updatedAt: new Date("2026-01-01"),
    ...overrides,
  } as ISpace;
}

function renderForm(props: { space: ISpace; readOnly?: boolean }) {
  return render(
    <MantineProvider>
      <EditSpaceForm space={props.space} readOnly={props.readOnly} />
    </MantineProvider>,
  );
}

// The git-sync toggle is the only switch on the form. Mantine renders it as an
// <input type="checkbox" role="switch">; its label text lives in a sibling
// wrapper, so query by role and assert the visible label is present alongside.
function getToggle(): HTMLInputElement {
  // Sanity: the human-readable label is rendered.
  screen.getByText("Enable Git sync");
  return screen.getByRole("switch") as HTMLInputElement;
}

afterEach(() => {
  cleanup();
  mutateAsync.mockReset();
  isPending = false;
});

describe("EditSpaceForm git-sync toggle", () => {
  // Test 3: initial checked state derives from settings.gitSync.enabled ?? false.
  it("derives initial checked state from space.settings.gitSync.enabled (true -> checked)", () => {
    renderForm({
      space: makeSpace({ settings: { gitSync: { enabled: true } } }),
    });
    expect(getToggle().checked).toBe(true);
  });

  it("defaults to unchecked when gitSync settings are missing", () => {
    renderForm({ space: makeSpace() });
    expect(getToggle().checked).toBe(false);
  });

  // Test 4: toggling fires the mutation with { spaceId, gitSyncEnabled } and
  // optimistically flips the switch.
  it("fires the mutation with the correct payload and optimistically flips on", async () => {
    mutateAsync.mockResolvedValue(undefined);
    renderForm({ space: makeSpace() });

    const toggle = getToggle();
    expect(toggle.checked).toBe(false);

    fireEvent.click(toggle);

    // Optimistic update: the switch reflects the new state immediately.
    expect(toggle.checked).toBe(true);

    expect(mutateAsync).toHaveBeenCalledTimes(1);
    expect(mutateAsync).toHaveBeenCalledWith({
      spaceId: "space-1",
      gitSyncEnabled: true,
    });

    // Resolution leaves the toggle on.
    await waitFor(() => expect(toggle.checked).toBe(true));
  });

  // Test 5: rollback on mutation error — the most valuable test.
  it("rolls back the toggle to its prior state when the mutation rejects", async () => {
    mutateAsync.mockRejectedValue(new Error("network"));
    renderForm({
      space: makeSpace({ settings: { gitSync: { enabled: false } } }),
    });

    const toggle = getToggle();
    expect(toggle.checked).toBe(false);

    fireEvent.click(toggle);

    // Optimistically flips on before the rejection lands.
    expect(toggle.checked).toBe(true);
    expect(mutateAsync).toHaveBeenCalledWith({
      spaceId: "space-1",
      gitSyncEnabled: true,
    });

    // After the rejected promise settles, the component reverts to OFF so the
    // user is not misled into believing sync is enabled.
    await waitFor(() => expect(toggle.checked).toBe(false));
  });

  // Test 6: disabled when readOnly and when the mutation is pending.
  it("disables the toggle when readOnly", () => {
    renderForm({ space: makeSpace(), readOnly: true });
    expect(getToggle().disabled).toBe(true);
  });

  it("disables the toggle while the mutation is pending", () => {
    isPending = true;
    renderForm({ space: makeSpace() });
    expect(getToggle().disabled).toBe(true);
  });
});
