import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
import { render, screen, fireEvent, cleanup, waitFor, within } from "@testing-library/react";
import EditorPage from "@/app/editor/page";
import type { Block, ProjectConfig } from "@/features/editor/state/editorStore";

// EditorPage 프로젝트 저장/불러오기 UX TDD
// - 프로젝트 저장 버튼 클릭 시 현재 blocks 를 로컬스토리지(pb:project:<slug>)와 서버(/api/projects) 모두에 저장해야 한다.
// - 불러오기 버튼 클릭 시 로컬스토리지에 프로젝트가 있으면 서버를 호출하지 않고 로컬 데이터로 replaceBlocks 해야 한다.
// - 로컬스토리지에 없으면 /api/projects/[slug] 를 호출해 서버에서 프로젝트를 불러와 replaceBlocks 해야 한다.

let mockState: any;

beforeEach(() => {
  const baseProjectConfig: ProjectConfig = {
    title: "프로젝트 저장/불러오기 테스트",
    slug: "editor-project-test",
    canvasPreset: "full",
  } as ProjectConfig;

  mockState = {
    blocks: [
      {
        id: "blk_1",
        type: "text",
        props: {
          text: "프로젝트 저장/불러오기 테스트 블록",
          align: "left",
          size: "base",
        },
      },
    ] as Block[],
    projectConfig: baseProjectConfig,
    selectedBlockId: null as string | null,
    selectedListItemId: null as string | null,
    // EditorPage 가 참조하는 액션들: 기본적으로 no-op mock 으로 채운다.
    undo: vi.fn(),
    redo: vi.fn(),
    removeBlock: vi.fn(),
    duplicateBlock: vi.fn(),
    selectBlock: vi.fn(),
    selectListItem: vi.fn(),
    replaceBlocks: vi.fn(),
    reorderBlocks: vi.fn(),
    moveBlock: vi.fn(),
    addTextBlock: vi.fn(),
    addButtonBlock: vi.fn(),
    addImageBlock: vi.fn(),
    addDividerBlock: vi.fn(),
    addListBlock: vi.fn(),
    addSectionBlock: vi.fn(),
    addFormBlock: vi.fn(),
    addFormInputBlock: vi.fn(),
    addFormSelectBlock: vi.fn(),
    addFormCheckboxBlock: vi.fn(),
    addFormRadioBlock: vi.fn(),
    addHeroTemplateSection: vi.fn(),
    addFeaturesTemplateSection: vi.fn(),
    addCtaTemplateSection: vi.fn(),
    addFaqTemplateSection: vi.fn(),
    addPricingTemplateSection: vi.fn(),
    addTestimonialsTemplateSection: vi.fn(),
    addBlogTemplateSection: vi.fn(),
    addTeamTemplateSection: vi.fn(),
    addFooterTemplateSection: vi.fn(),
    updateBlock: vi.fn(),
    updateProjectConfig: vi.fn((partial: Partial<ProjectConfig>) => {
      mockState.projectConfig = {
        ...(mockState.projectConfig as ProjectConfig),
        ...partial,
      } as ProjectConfig;
    }),
  };

  window.localStorage.clear();
});

afterEach(() => {
  cleanup();
  vi.unstubAllGlobals();
});

vi.mock("@/features/editor/state/editorStore", () => {
  const useEditorStore = (selector: (state: any) => any) => selector(mockState);
  (useEditorStore as any).getState = () => mockState;

  return {
    __esModule: true,
    useEditorStore,
  };
});

vi.mock("next/link", () => {
  return {
    __esModule: true,
    default: ({ href, children }: any) => <a href={href}>{children}</a>,
  };
});

describe("EditorPage - 프로젝트 저장/불러오기", () => {
  it("프로젝트 저장 버튼 클릭 시 로컬스토리지와 서버에 상태를 저장해야 하고, projectConfig 와 제목/주소 입력이 동기화되어야 한다", async () => {
    const fetchMock = vi.fn().mockResolvedValue({
      ok: true,
      status: 201,
      json: async () => ({ slug: "save-test-slug" }),
    } as any);

    vi.stubGlobal("fetch", fetchMock);

    const { rerender } = render(<EditorPage />);

    const menuButton = screen.getByText("메뉴");
    fireEvent.click(menuButton);

    const projectMenuItem = screen.getByText("프로젝트 저장/불러오기");
    fireEvent.click(projectMenuItem);

    const modalTitle = screen.getByText("프로젝트 저장 / 불러오기");
    const projectModal = (modalTitle.parentElement?.parentElement ?? document.body) as HTMLElement;

    const titleInput = within(projectModal).getByLabelText("프로젝트 제목") as HTMLInputElement;
    const slugInput = within(projectModal).getByLabelText("프로젝트 주소 (예: my-landing)") as HTMLInputElement;

    expect(titleInput.value).toBe("프로젝트 저장/불러오기 테스트");
    expect(slugInput.value).toBe("editor-project-test");

    fireEvent.change(titleInput, { target: { value: "저장 테스트 프로젝트" } });
    fireEvent.change(slugInput, { target: { value: "save-test-slug" } });

    expect(mockState.projectConfig.title).toBe("저장 테스트 프로젝트");
    expect(mockState.projectConfig.slug).toBe("save-test-slug");

    rerender(<EditorPage />);

    const saveButton = screen.getByText("저장 (로컬 + 서버)");
    fireEvent.click(saveButton);

    await waitFor(() => {
      expect(fetchMock).toHaveBeenCalledTimes(1);
    });

    const localKey = "pb:project:save-test-slug";
    const raw = window.localStorage.getItem(localKey);
    expect(raw).not.toBeNull();

    const parsed = JSON.parse(raw as string);
    expect(parsed.title).toBe("저장 테스트 프로젝트");
    expect(Array.isArray(parsed.contentJson)).toBe(true);
    expect(parsed.contentJson[0].id).toBe("blk_1");

    const [url, options] = fetchMock.mock.calls[0] as any;
    expect(url).toBe("/api/projects");
    expect(options.method).toBe("POST");
    expect(options.headers["Content-Type"]).toBe("application/json");

    const body = JSON.parse(options.body);
    expect(body.slug).toBe("save-test-slug");
    expect(body.title).toBe("저장 테스트 프로젝트");
    expect(Array.isArray(body.contentJson)).toBe(true);
    expect(body.contentJson[0].id).toBe("blk_1");

    const message = await screen.findByText("프로젝트가 저장되었습니다: save-test-slug");
    expect(message).toBeTruthy();
  });

  it("로컬스토리지에 프로젝트가 있으면 서버를 호출하지 않고 로컬 데이터로 불러와야 한다", async () => {
    const slug = "local-test-slug";
    const localKey = `pb:project:${slug}`;

    const savedBlocks: Block[] = [
      {
        id: "saved_1",
        type: "text",
        props: {
          text: "로컬 저장된 블록",
          align: "left",
          size: "base",
        },
      } as any,
    ];

    window.localStorage.setItem(
      localKey,
      JSON.stringify({
        title: "로컬 프로젝트",
        contentJson: savedBlocks,
      }),
    );

    const fetchMock = vi.fn();
    vi.stubGlobal("fetch", fetchMock);

    mockState.projectConfig.slug = slug;

    render(<EditorPage />);

    const menuButton = screen.getByText("메뉴");
    fireEvent.click(menuButton);

    const projectMenuItem = screen.getByText("프로젝트 저장/불러오기");
    fireEvent.click(projectMenuItem);

    const modalTitle = screen.getByText("프로젝트 저장 / 불러오기");
    const projectModal = (modalTitle.parentElement?.parentElement ?? document.body) as HTMLElement;

    const loadButton = screen.getByText("불러오기");
    fireEvent.click(loadButton);

    await waitFor(() => {
      expect(mockState.replaceBlocks).toHaveBeenCalledTimes(1);
    });

    expect(mockState.replaceBlocks).toHaveBeenCalledWith(savedBlocks as any);
    expect(fetchMock).not.toHaveBeenCalled();

    const message = await screen.findByText(`로컬에서 프로젝트를 불러왔습니다: ${slug}`);
    expect(message).toBeTruthy();
  });

  it("로컬스토리지에 없으면 서버에서 프로젝트를 불러와야 한다", async () => {
    const slug = "server-test-slug";

    const serverBlocks: Block[] = [
      {
        id: "server_1",
        type: "text",
        props: {
          text: "서버에서 불러온 블록",
          align: "left",
          size: "base",
        },
      } as any,
    ];

    const fetchMock = vi.fn().mockResolvedValue({
      ok: true,
      status: 200,
      json: async () => ({ slug, contentJson: serverBlocks }),
    } as any);

    vi.stubGlobal("fetch", fetchMock);

    mockState.projectConfig.slug = slug;

    render(<EditorPage />);

    const menuButton = screen.getByText("메뉴");
    fireEvent.click(menuButton);

    const projectMenuItem = screen.getByText("프로젝트 저장/불러오기");
    fireEvent.click(projectMenuItem);

    const loadButton = screen.getByText("불러오기");
    fireEvent.click(loadButton);

    await waitFor(() => {
      expect(fetchMock).toHaveBeenCalledTimes(1);
    });

    const [url] = fetchMock.mock.calls[0] as any;
    expect(url).toBe(`/api/projects/${slug}`);

    await waitFor(() => {
      expect(mockState.replaceBlocks).toHaveBeenCalledWith(serverBlocks as any);
    });

    const message = await screen.findByText(`프로젝트를 불러왔습니다: ${slug}`);
    expect(message).toBeTruthy();
  });

  it("에디터 메뉴에서 프로젝트 삭제를 선택하면 서버에 DELETE 요청을 보내고 로컬 저장/자동저장 상태를 정리한 뒤 목록으로 이동해야 한다", async () => {
    const slug = "delete-test-slug";
    mockState.projectConfig.slug = slug;

    // 로컬 저장/자동저장 스냅샷이 존재하는 상황을 준비한다.
    window.localStorage.setItem(`pb:project:${slug}`, "stored-project");
    window.localStorage.setItem(`pb:autosave:${slug}`, "stored-autosave");

    const fetchMock = vi.fn().mockResolvedValue({
      ok: true,
      status: 200,
    } as any);

    vi.stubGlobal("fetch", fetchMock);

    const confirmSpy = vi.spyOn(window, "confirm").mockReturnValue(true as any);

    render(<EditorPage />);

    const menuButton = screen.getByText("메뉴");
    fireEvent.click(menuButton);

    const deleteMenuItem = screen.getByText("프로젝트 삭제");
    fireEvent.click(deleteMenuItem);

    await waitFor(() => {
      expect(fetchMock).toHaveBeenCalledTimes(1);
    });

    const [url, options] = fetchMock.mock.calls[0] as any;
    expect(url).toBe(`/api/projects/${slug}`);
    expect(options.method).toBe("DELETE");

    expect(window.localStorage.getItem(`pb:project:${slug}`)).toBeNull();
    expect(window.localStorage.getItem(`pb:autosave:${slug}`)).toBeNull();

    confirmSpy.mockRestore();
  });
});
