import { expect, test } from "vitest";
import { screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { http, HttpResponse } from "msw";
import { Routes, Route } from "react-router-dom";
import { server } from "../test/server";
import { renderApp } from "../test/render";
import { VocabulariesPage } from "./vocabularies-page";
import { VocabularyTerms } from "./vocabulary-terms";
import { SelectVocabularyPrompt } from "./select-vocabulary-prompt";

function tree() {
  return (
    <Routes>
      <Route path="/vocabularies" element={<VocabulariesPage />}>
        <Route index element={<SelectVocabularyPrompt />} />
        <Route path=":id" element={<VocabularyTerms />} />
      </Route>
    </Routes>
  );
}

test("lists vocabularies and creates one", async () => {
  let body: unknown;
  server.use(
    http.post("/api/admin/vocabularies", async ({ request }) => {
      body = await request.json();
      return HttpResponse.json({ id: "v-c", key: "colour" }, { status: 201 });
    }),
  );
  renderApp(tree(), { route: "/vocabularies" });
  expect(await screen.findByText("material")).toBeInTheDocument();
  await userEvent.type(screen.getByLabelText(/key/i), "colour");
  await userEvent.click(screen.getByRole("button", { name: /create/i }));
  await waitFor(() => expect((body as { key: string })?.key).toBe("colour"));
});

test("selecting a vocabulary shows its terms and adds one", async () => {
  let termBody: unknown;
  server.use(
    http.post("/api/admin/vocabularies/:id/terms", async ({ request }) => {
      termBody = await request.json();
      return HttpResponse.json({ id: "t-c" }, { status: 201 });
    }),
  );
  renderApp(tree(), { route: "/vocabularies/v-material" });
  expect(await screen.findByText("Bronze")).toBeInTheDocument();
  await userEvent.type(screen.getByLabelText(/^label$/i), "Stone");
  await userEvent.click(screen.getByRole("button", { name: /add term/i }));
  await waitFor(() =>
    expect((termBody as { labels: { label: string }[] })?.labels[0].label).toBe("Stone"),
  );
});

test("terms endpoint error shows vocab loadError", async () => {
  server.use(
    http.get("/api/admin/vocabularies/:id/terms", () => new HttpResponse(null, { status: 500 })),
  );
  renderApp(tree(), { route: "/vocabularies/v-material" });
  expect(await screen.findByText(/could not load/i)).toBeInTheDocument();
});

test("add term without EN label shows required alert and does not POST", async () => {
  let posted = false;
  server.use(
    http.post("/api/admin/vocabularies/:id/terms", () => {
      posted = true;
      return HttpResponse.json({ id: "t-x" }, { status: 201 });
    }),
  );
  renderApp(tree(), { route: "/vocabularies/v-material" });
  expect(await screen.findByText("Bronze")).toBeInTheDocument();
  await userEvent.click(screen.getByRole("button", { name: /add term/i }));
  expect(screen.getByRole("alert")).toBeInTheDocument();
  expect(posted).toBe(false);
});

test("vocabularies render sorted by key", async () => {
  server.use(
    http.get("/api/admin/vocabularies", () =>
      HttpResponse.json([
        { id: "v-zeta", key: "zeta" },
        { id: "v-alpha", key: "alpha" },
      ]),
    ),
  );
  renderApp(tree(), { route: "/vocabularies" });
  expect(await screen.findByText("alpha")).toBeInTheDocument();
  const links = screen.getAllByRole("link");
  const keys = links.map((link) => link.textContent);
  expect(keys.indexOf("alpha")).toBeLessThan(keys.indexOf("zeta"));
});

test("filter narrows the vocabulary list", async () => {
  renderApp(tree(), { route: "/vocabularies" });
  expect(await screen.findByText("material")).toBeInTheDocument();
  expect(screen.getByText("technique")).toBeInTheDocument();
  await userEvent.type(screen.getByRole("textbox", { name: /filter/i }), "mat");
  expect(screen.getByText("material")).toBeInTheDocument();
  expect(screen.queryByText("technique")).not.toBeInTheDocument();
});

test("renaming a vocabulary to an empty key does not call the rename endpoint", async () => {
  let renamed = false;
  server.use(
    http.patch("/api/admin/vocabularies/:id", () => {
      renamed = true;
      return new HttpResponse(null, { status: 204 });
    }),
  );
  renderApp(tree(), { route: "/vocabularies" });
  expect(await screen.findByText("material")).toBeInTheDocument();
  await userEvent.click(screen.getAllByRole("button", { name: /rename/i })[0]);
  const keyInputs = screen.getAllByRole("textbox", { name: /key/i });
  const input = keyInputs[keyInputs.length - 1];
  await userEvent.clear(input);
  await userEvent.click(screen.getByRole("button", { name: /save/i }));
  expect(renamed).toBe(false);
});

test("term read row shows its external_uri as a link", async () => {
  server.use(
    http.get("/api/admin/vocabularies/:id/terms", () =>
      HttpResponse.json([
        { id: "t-bronze", external_uri: "https://example.org/bronze", labels: [{ lang: "en", label: "Bronze" }] },
      ]),
    ),
  );
  renderApp(tree(), { route: "/vocabularies/v-material" });
  expect(await screen.findByText("Bronze")).toBeInTheDocument();
  expect(screen.getByRole("link", { name: /example\.org/ })).toBeInTheDocument();
});
