import { expect, test } from "vitest";
import { screen, waitFor, within } from "@testing-library/react";
import { http, HttpResponse } from "msw";
import { Routes, Route } from "react-router-dom";
import { server } from "../test/server";
import { renderApp } from "../test/render";
import { amphora } from "../test/fixtures";
import { ObjectDetail } from "./object-detail";

function tree() {
  return (
    <Routes>
      <Route path="/objects/:id" element={<ObjectDetail />} />
    </Routes>
  );
}

// An object seeded with a term, a localized_text and a date flexible value.
// `inscription` belongs to the "Description" group; the rest are ungrouped
// ("Other"). recording_date is null to exercise the empty-core placeholder.
const detailed = {
  ...amphora,
  recording_date: null,
  fields: {
    inscription: "AVE",
    material: "t-bronze",
    title_ml: { sv: "Brons-amfora", en: "Bronze amphora" },
    made_on: "2024-01-05",
    // No matching field definition — its definition was removed after the value
    // was set. Must still render (muted), keyed by its raw key.
    legacy_note: "orphaned value",
  },
};

function renderDetail() {
  server.use(http.get("/api/admin/objects/:id", () => HttpResponse.json(detailed)));
  return renderApp(tree(), { route: `/objects/${detailed.id}` });
}

test("renders inventory-minimum fields and visibility", async () => {
  renderDetail();
  expect(await screen.findByText("Amphora")).toBeInTheDocument();
  expect(screen.getByText("Vault 3")).toBeInTheDocument();
  // "Public" appears in both the VisibilityBadge and the PublishControl stepper;
  // scope the assertion to the badge element to avoid ambiguity.
  expect(document.querySelector("[data-slot='badge']")).toHaveTextContent("Public");
});

test("resolves a term id to its label, not the UUID", async () => {
  renderDetail();
  expect(await screen.findByText("Bronze")).toBeInTheDocument();
  expect(screen.queryByText("t-bronze")).not.toBeInTheDocument();
});

test("renders a localized_text value as a string, not JSON", async () => {
  renderDetail();
  // i18n falls back to "en", so the en variant is picked, never the JSON object.
  expect(await screen.findByText("Bronze amphora")).toBeInTheDocument();
  expect(screen.queryByText(/\{.*"en".*\}/)).not.toBeInTheDocument();
});

test("groups flexible fields under subheadings in definition order", async () => {
  renderDetail();
  await screen.findByText("Bronze");
  // "Description" (the inscription def's group) precedes the trailing "Other" group.
  const description = screen.getByText("Description");
  const other = screen.getByText("Other");
  expect(description.compareDocumentPosition(other)).toBe(
    Node.DOCUMENT_POSITION_FOLLOWING,
  );
});

test("renders an orphaned field key (no definition) with its raw key and value", async () => {
  renderDetail();
  await screen.findByText("Bronze");
  // The key itself is the label, since there is no definition to localize it.
  expect(screen.getByText("legacy_note")).toBeInTheDocument();
  expect(screen.getByText("orphaned value")).toBeInTheDocument();
});

test("an empty core field shows a placeholder dash", async () => {
  renderDetail();
  const recordingDate = (await screen.findByText("Recording date")).parentElement;
  expect(within(recordingDate!).getByText("—")).toBeInTheDocument();
});

test("the Edit link points to the object's edit route", async () => {
  renderDetail();
  const edit = await screen.findByRole("link", { name: "Edit" });
  expect(edit).toHaveAttribute("href", `/objects/${detailed.id}/edit`);
});

test("shows a not-found state for a missing object", async () => {
  server.use(http.get("/api/admin/objects/:id", () => new HttpResponse(null, { status: 404 })));
  renderApp(tree(), { route: "/objects/does-not-exist" });
  expect(await screen.findByText(/object not found/i)).toBeInTheDocument();
});

test("sets the tab title to the object number and reverts on unmount", async () => {
  document.title = "Base";
  const { unmount } = renderDetail();
  await waitFor(() => expect(document.title).toMatch(/^LM-0042 \| /));
  unmount();
  expect(document.title).toBe("Base");
});

test("detail shows the publish control with the current visibility stepper", async () => {
  // default GET /api/admin/objects/:id handler returns amphora (visibility "public")
  renderApp(tree(), { route: "/objects/11111111-1111-1111-1111-111111111111" });
  expect(await screen.findByText(/visibility/i)).toBeInTheDocument();
  expect(await screen.findByRole("button", { name: /unpublish to internal/i })).toBeInTheDocument();
});
