import { expect, test } from "vitest";
import { screen, waitFor, within } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { http, HttpResponse } from "msw";
import { server } from "../test/server";
import { renderApp } from "../test/render";
import { PublishControl } from "./publish-control";
import type { components } from "../api/schema";

type AdminObjectView = components["schemas"]["AdminObjectView"];

function objectWith(visibility: string): AdminObjectView {
  return {
    id: "o-1", object_number: "A-1", object_name: "Amphora", number_of_objects: 1,
    brief_description: null, current_location: null, current_owner: null,
    recorder: null, recording_date: null, visibility, fields: {},
  } as AdminObjectView;
}

test("internal: shows publish (forward) and back-to-draft buttons", async () => {
  renderApp(<PublishControl object={objectWith("internal")} />);
  expect(screen.getByRole("button", { name: /publish/i })).toBeInTheDocument();
  expect(screen.getByRole("button", { name: /back to draft/i })).toBeInTheDocument();
});

test("draft: forward to internal posts immediately (no confirm)", async () => {
  let body: unknown;
  server.use(
    http.post("/api/admin/objects/:id/visibility", async ({ request }) => {
      body = await request.json();
      return new HttpResponse(null, { status: 204 });
    }),
  );
  renderApp(<PublishControl object={objectWith("draft")} />);
  await userEvent.click(screen.getByRole("button", { name: /advance to internal/i }));
  await waitFor(() => expect((body as { visibility: string })?.visibility).toBe("internal"));
});

test("public: back to internal posts immediately", async () => {
  let body: unknown;
  server.use(
    http.post("/api/admin/objects/:id/visibility", async ({ request }) => {
      body = await request.json();
      return new HttpResponse(null, { status: 204 });
    }),
  );
  renderApp(<PublishControl object={objectWith("public")} />);
  await userEvent.click(screen.getByRole("button", { name: /unpublish to internal/i }));
  await waitFor(() => expect((body as { visibility: string })?.visibility).toBe("internal"));
});

test("internal -> public requires confirmation, then posts public", async () => {
  let body: unknown;
  server.use(
    http.post("/api/admin/objects/:id/visibility", async ({ request }) => {
      body = await request.json();
      return new HttpResponse(null, { status: 204 });
    }),
  );
  renderApp(<PublishControl object={objectWith("internal")} />);
  await userEvent.click(screen.getByRole("button", { name: /publish/i }));
  const dialog = await screen.findByRole("alertdialog");
  await userEvent.click(within(dialog).getByRole("button", { name: /publish/i }));
  await waitFor(() => expect((body as { visibility: string })?.visibility).toBe("public"));
});

test("publish gate (422) shows an inline error with an edit link", async () => {
  server.use(
    http.post("/api/admin/objects/:id/visibility", () => new HttpResponse(null, { status: 422 })),
  );
  renderApp(<PublishControl object={objectWith("internal")} />);
  await userEvent.click(screen.getByRole("button", { name: /publish/i }));
  const dialog = await screen.findByRole("alertdialog");
  await userEvent.click(within(dialog).getByRole("button", { name: /publish/i }));
  await waitFor(() =>
    expect(screen.getByText(/required fields are missing/i)).toBeInTheDocument(),
  );
  expect(screen.getByRole("link", { name: /edit the record/i })).toBeInTheDocument();
});
