import { fireEvent, render, screen, waitFor, within } from "@testing-library/react";
import { expect, test } from "vitest";

import { CharacterDetailPage } from "@/features/characters/pages/CharacterDetailPage";
import type { CharacterDetail } from "@/features/characters/model/types";
import type { ApiClient, ApiRequestOptions } from "@/shared/api/client";

type CapturedRequest = {
  readonly body?: BodyInit | null;
  readonly method?: string;
  readonly path: string;
};

const activeCharacter: CharacterDetail = {
  id: 101,
  characterUUID: "character-101",
  name: "루나",
  imageUrl: null,
  description: "차분한 상담형 캐릭터",
  systemPrompt: "친절하게 답한다.",
  characterType: "Character",
  age: null,
  gender: null,
  mbti: null,
  speechPattern: null,
  speechStyle: null,
  appearance: null,
  region: "KR",
  isActive: true,
  tags: ["상담"],
  hobbies: [],
  values: [],
  goals: [],
  relationships: [],
  personalities: [],
  backgrounds: [],
  memories: [],
  originalWork: null,
};

function createDetailClient(requests: CapturedRequest[]): ApiClient {
  return {
    async request<Data>(options: ApiRequestOptions<Data>): Promise<Data> {
      requests.push({ body: options.body, method: options.method, path: options.path });
      if (options.path === "/api/v2/admin/ai-characters/101" && options.method === undefined) {
        return options.responseSchema.parse(activeCharacter);
      }

      return options.responseSchema.parse(null);
    },
  };
}

function requireFormData(body: BodyInit | null | undefined): FormData {
  if (body instanceof FormData) {
    return body;
  }

  throw new TypeError("Expected FormData body");
}

async function readJsonPart(part: FormDataEntryValue | null): Promise<unknown> {
  if (part instanceof Blob) {
    return JSON.parse(await part.text());
  }
  if (typeof part === "string") {
    return JSON.parse(part);
  }

  return null;
}

test("CharacterDetailPage soft deletes an active character after confirmation", async () => {
  // Given
  const requests: CapturedRequest[] = [];
  window.history.pushState({}, "", "/ai-characters/101");

  // When
  render(<CharacterDetailPage apiClient={createDetailClient(requests)} characterId="101" />);
  fireEvent.click(await screen.findByRole("button", { name: "비활성화" }));

  // Then
  const dialog = screen.getByRole("alertdialog", { name: "루나 비활성화 확인" });
  expect(dialog).toHaveTextContent("복원은 지원하지 않습니다.");
  expect(dialog).toHaveTextContent("완전 삭제는 아니며 목록에서 제외됩니다.");
  expect(dialog).not.toHaveTextContent("hard delete");

  // When
  fireEvent.click(screen.getByRole("button", { name: "비활성화 확인" }));

  // Then
  await waitFor(() => expect(window.location.pathname).toBe("/ai-characters"));
  expect(requests).toMatchObject([
    { path: "/api/v2/admin/ai-characters/101", method: undefined },
    { path: "/api/v2/admin/ai-characters/101", method: "PUT" },
  ]);
  expect(await readJsonPart(requireFormData(requests[1]?.body).get("request"))).toEqual({ isActive: false });
});

test("CharacterDetailPage sends one deactivate request while pending and shows retry guidance on failure", async () => {
  // Given
  const requests: CapturedRequest[] = [];
  let rejectDeactivate: () => void = () => { throw new Error("Deactivate request was not started"); };
  const client: ApiClient = {
    async request<Data>(options: ApiRequestOptions<Data>): Promise<Data> {
      requests.push({ body: options.body, method: options.method, path: options.path });
      if (options.method === undefined) {
        return options.responseSchema.parse(activeCharacter);
      }
      await new Promise<void>((_, reject) => { rejectDeactivate = () => reject(new Error("fail")); });
      return options.responseSchema.parse(null);
    },
  };
  window.history.pushState({}, "", "/ai-characters/101");
  render(<CharacterDetailPage apiClient={client} characterId="101" />);
  fireEvent.click(await screen.findByRole("button", { name: "비활성화" }));

  // When
  fireEvent.click(screen.getByRole("button", { name: "비활성화 확인" }));
  fireEvent.click(screen.getByRole("button", { name: "처리 중" }));
  rejectDeactivate();

  // Then
  expect(requests.filter((request) => request.method === "PUT")).toHaveLength(1);
  expect(await screen.findByRole("alert")).toHaveTextContent("비활성화하지 못했습니다. 다시 시도하세요.");
});

test("CharacterDetailPage routes active characters to edit", async () => {
  // Given
  const requests: CapturedRequest[] = [];
  window.history.pushState({}, "", "/ai-characters/101");

  // When
  render(<CharacterDetailPage apiClient={createDetailClient(requests)} characterId="101" />);
  fireEvent.click(await screen.findByRole("button", { name: "수정" }));

  // Then
  expect(window.location.pathname).toBe("/ai-characters/101/edit");
});

test("CharacterDetailPage shows characterUUID as read-only profile data", async () => {
  // Given
  const requests: CapturedRequest[] = [];
  window.history.pushState({}, "", "/ai-characters/101");

  // When
  render(<CharacterDetailPage apiClient={createDetailClient(requests)} characterId="101" />);

  // Then
  const profile = await screen.findByRole("region", { name: "프로필" });
  expect(within(profile).getByText("캐릭터 UUID")).toBeInTheDocument();
  expect(within(profile).getByText("character-101")).toBeInTheDocument();
  expect(screen.queryByText("externalCharacterId")).not.toBeInTheDocument();
});
