import { render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { vi, describe, it, expect, beforeEach } from "vitest";
import type { CheckinBrandingData } from "@alqove/api-client";

const submit = vi.fn();
vi.mock("@/lib/checkin/api", () => ({
  checkinApi: { submit: (...a: unknown[]) => submit(...a) },
  getOptionalAuthToken: () => null,
}));

import { CheckinForm } from "../CheckinForm";
import { DEV_TURNSTILE_TOKEN } from "@/components/checkin/Turnstile";
import { readPersistedStatus, persistStatus } from "@/lib/checkin/status-link";

const BRANDING: CheckinBrandingData = {
  name: "Wax & Wane Records",
  logo: null,
  description: null,
  hours: null,
  status_visibility: "live",
  terms_version: "2026-01",
  checkin_enabled: true,
  checkin_paused: false,
};

// Inner payload of CheckinAccepted (the submit endpoint wraps it in `{ data }`).
const ACCEPTED = {
  checkin_code: "A7X-4421",
  status_token: "st123",
  status_url: "/c/tok1/s/st123",
};

function renderForm() {
  const qc = new QueryClient({ defaultOptions: { queries: { retry: false } } });
  render(
    <QueryClientProvider client={qc}>
      <CheckinForm token="tok1" branding={BRANDING} />
    </QueryClientProvider>,
  );
}

async function driveToSubmit(user: ReturnType<typeof userEvent.setup>) {
  await user.type(screen.getByLabelText(/first name/i), "Ada");
  await user.type(screen.getByLabelText(/last name/i), "Lovelace");
  await user.type(screen.getByLabelText(/phone/i), "5125551234");
  await user.click(screen.getByRole("button", { name: /next/i }));
  await user.click(screen.getByRole("button", { name: /next/i }));
  await user.click(screen.getByRole("button", { name: /check in/i }));
}

describe("CheckinForm submit handling", () => {
  beforeEach(() => {
    submit.mockReset();
    localStorage.clear();
  });

  it("submits with a stable idempotency key + dev Turnstile token and shows the confirmation", async () => {
    const user = userEvent.setup();
    submit.mockResolvedValue({ data: ACCEPTED });
    renderForm();
    await driveToSubmit(user);

    await waitFor(() =>
      expect(screen.getByTestId("checkin-code")).toHaveTextContent("A7X-4421"),
    );

    const [token, payload, opts] = submit.mock.calls[0];
    expect(token).toBe("tok1");
    expect(payload).toMatchObject({ first_name: "Ada", phone: "5125551234" });
    expect(opts.turnstileToken).toBe(DEV_TURNSTILE_TOKEN);
    expect(typeof opts.idempotencyKey).toBe("string");
    expect(opts.authToken).toBeUndefined();

    // Status link is persisted on-device + the confirmation links to it.
    expect(readPersistedStatus("tok1")?.status_url).toBe("/c/tok1/s/st123");
    expect(
      screen.getByRole("link", { name: /track my check-in/i }),
    ).toHaveAttribute("href", "/c/tok1/s/st123");
  });

  it("shows the already-submitted state with a link on 409", async () => {
    const user = userEvent.setup();
    persistStatus("tok1", ACCEPTED); // a prior submit left a status link
    submit.mockRejectedValue({ status: 409, message: "duplicate" });
    renderForm();
    await driveToSubmit(user);

    await waitFor(() =>
      expect(screen.getByText(/already checked in/i)).toBeInTheDocument(),
    );
    expect(
      screen.getByRole("link", { name: /track my check-in/i }),
    ).toHaveAttribute("href", "/c/tok1/s/st123");
  });

  it("shows the dead-link state on 410", async () => {
    const user = userEvent.setup();
    submit.mockRejectedValue({ status: 410, message: "gone" });
    renderForm();
    await driveToSubmit(user);

    await waitFor(() =>
      expect(screen.getByText(/no longer active/i)).toBeInTheDocument(),
    );
  });

  it("shows the paused state on 423", async () => {
    const user = userEvent.setup();
    submit.mockRejectedValue({ status: 423, message: "locked" });
    renderForm();
    await driveToSubmit(user);

    await waitFor(() =>
      expect(screen.getByText(/not accepting check-ins/i)).toBeInTheDocument(),
    );
  });

  it("retries with the SAME idempotency key after a network error (safe replay)", async () => {
    const user = userEvent.setup();
    submit
      .mockRejectedValueOnce(new TypeError("network down"))
      .mockResolvedValueOnce({ data: ACCEPTED });
    renderForm();
    await driveToSubmit(user);

    const retry = await screen.findByRole("button", { name: /try again/i });
    await user.click(retry);

    await waitFor(() =>
      expect(screen.getByTestId("checkin-code")).toBeInTheDocument(),
    );

    expect(submit).toHaveBeenCalledTimes(2);
    const firstKey = submit.mock.calls[0][2].idempotencyKey;
    const secondKey = submit.mock.calls[1][2].idempotencyKey;
    expect(firstKey).toBe(secondKey);
  });
});
