import { describe, it, expect, vi, afterEach } from "vitest";
import { render, screen, fireEvent, cleanup } from "@testing-library/react";
import { ButtonPropertiesPanel } from "@/app/editor/panels/ButtonPropertiesPanel";
import type { ButtonBlockProps } from "@/features/editor/state/editorStore";

// ButtonPropertiesPanel 컨트롤 TDD
// - 색상 HEX 인풋 및 너비 모드 셀렉트가 updateBlock 을 올바르게 호출하는지 검증한다.

describe("ButtonPropertiesPanel", () => {
  const baseProps: ButtonBlockProps = {
    label: "버튼",
    href: "#",
    align: "left",
    size: "md",
    variant: "solid",
    colorPalette: "primary",
  };

  afterEach(() => {
    cleanup();
  });

  it("텍스트 색상 HEX 인풋 변경 시 updateBlock 이 textColorCustom 으로 호출되어야 한다", () => {
    const updateBlock = vi.fn();

    render(
      <ButtonPropertiesPanel
        buttonProps={{ ...baseProps, textColorCustom: "#ffffff" }}
        selectedBlockId="btn-1"
        updateBlock={updateBlock}
      />,
    );

    const hexInput = screen.getByLabelText("Button text color HEX");
    fireEvent.change(hexInput, { target: { value: "#112233" } });

    expect(updateBlock).toHaveBeenCalledWith(
      "btn-1",
      expect.objectContaining({ textColorCustom: "#112233" }),
    );
  });

  it("채움 색상 HEX 인풋 변경 시 updateBlock 이 fillColorCustom 으로 호출되어야 한다", () => {
    const updateBlock = vi.fn();

    render(
      <ButtonPropertiesPanel
        buttonProps={{ ...baseProps, fillColorCustom: "#000000" }}
        selectedBlockId="btn-2"
        updateBlock={updateBlock}
      />,
    );

    const hexInput = screen.getByLabelText("Button fill color HEX");
    fireEvent.change(hexInput, { target: { value: "#445566" } });

    expect(updateBlock).toHaveBeenCalledWith(
      "btn-2",
      expect.objectContaining({ fillColorCustom: "#445566" }),
    );
  });

  it("외곽선 색상 HEX 인풋 변경 시 updateBlock 이 strokeColorCustom 으로 호출되어야 한다", () => {
    const updateBlock = vi.fn();

    render(
      <ButtonPropertiesPanel
        buttonProps={{ ...baseProps, strokeColorCustom: "#000000" }}
        selectedBlockId="btn-3"
        updateBlock={updateBlock}
      />,
    );

    const hexInput = screen.getByLabelText("Button border color HEX");
    fireEvent.change(hexInput, { target: { value: "#778899" } });

    expect(updateBlock).toHaveBeenCalledWith(
      "btn-3",
      expect.objectContaining({ strokeColorCustom: "#778899" }),
    );
  });

	it("텍스트 색상 팔레트에서 \"None\" 을 선택하면 textColorCustom 이 빈 문자열로 업데이트되어야 한다", () => {
		const updateBlock = vi.fn();

		render(
			<ButtonPropertiesPanel
				buttonProps={{ ...baseProps, textColorCustom: "#ff0000" }}
				selectedBlockId="btn-text-none"
				updateBlock={updateBlock}
			/>,
		);

		// 첫 번째 ColorPickerField(텍스트 색상)의 팔레트 버튼을 연다.
		const paletteButtons = screen.getAllByText("Color palette");
		fireEvent.click(paletteButtons[0].closest("button") as HTMLButtonElement);

		// 팔레트에서 "None" 항목을 버튼 role 기준으로 선택한다.
		const noneButtons = screen.getAllByRole("button", { name: "None" });
		fireEvent.click(noneButtons[0]);

		expect(updateBlock).toHaveBeenCalledWith(
			"btn-text-none",
			expect.objectContaining({ textColorCustom: "" }),
		);
	});

	it("textColorCustom 이 비어 있으면 버튼 텍스트 색상 HEX 인풋 값은 빈 문자열이어야 한다", () => {
		const updateBlock = vi.fn();

		render(
			<ButtonPropertiesPanel
				buttonProps={{ ...baseProps, textColorCustom: "" }}
				selectedBlockId="btn-text-empty"
				updateBlock={updateBlock}
			/>,
		);

		const hexInput = screen.getByLabelText("Button text color HEX") as HTMLInputElement;
		expect(hexInput.value).toBe("");
	});

  it("버튼 너비 모드 셀렉트 변경 시 updateBlock 이 widthMode 로 호출되어야 한다", () => {
    const updateBlock = vi.fn();

    render(
      <ButtonPropertiesPanel
        buttonProps={{ ...baseProps, widthMode: "auto", fullWidth: false }}
        selectedBlockId="btn-4"
        updateBlock={updateBlock}
      />,
    );

    const select = screen.getByLabelText("Button width mode");
    fireEvent.change(select, { target: { value: "fixed" } });

    expect(updateBlock).toHaveBeenCalledWith(
      "btn-4",
      expect.objectContaining({ widthMode: "fixed" }),
    );
  });

  it("버튼 이미지 URL 인풋 변경 시 updateBlock 이 imageSrc 와 imageSourceType(externalUrl) 으로 호출되어야 한다", () => {
    const updateBlock = vi.fn();

    render(
      <ButtonPropertiesPanel
        buttonProps={{
          ...baseProps,
          imageSrc: "https://example.com/old.png",
          imageSourceType: "externalUrl",
        } as any}
        selectedBlockId="btn-img-url"
        updateBlock={updateBlock}
      />,
    );

    const urlInput = screen.getByLabelText("Button image URL");
    fireEvent.change(urlInput, { target: { value: "https://example.com/new.png" } });

    expect(updateBlock).toHaveBeenCalledWith(
      "btn-img-url",
      expect.objectContaining({
        imageSrc: "https://example.com/new.png",
        imageSourceType: "externalUrl",
      }),
    );
  });

  it("버튼 이미지 위치 셀렉트 변경 시 updateBlock 이 imagePlacement 로 호출되어야 한다", () => {
    const updateBlock = vi.fn();

    render(
      <ButtonPropertiesPanel
        buttonProps={{
          ...baseProps,
          imagePlacement: "left",
          imageSrc: "https://example.com/icon.png",
          imageSourceType: "externalUrl",
        } as any}
        selectedBlockId="btn-img-pos"
        updateBlock={updateBlock}
      />,
    );

    const select = screen.getByLabelText("Button image placement");
    fireEvent.change(select, { target: { value: "right" } });

    expect(updateBlock).toHaveBeenCalledWith(
      "btn-img-pos",
      expect.objectContaining({ imagePlacement: "right" }),
    );
  });

  it("버튼 이미지 소스 셀렉트에서 URL/업로드를 전환할 수 있어야 한다", () => {
    const updateBlock = vi.fn();

    render(
      <ButtonPropertiesPanel
        buttonProps={{
          ...baseProps,
          imageSrc: "/api/image/test-id",
          imageSourceType: "asset",
        } as any}
        selectedBlockId="btn-img-source"
        updateBlock={updateBlock}
      />,
    );

    const select = screen.getByLabelText("Button image source");
    // 업로드 → URL 로 전환
    fireEvent.change(select, { target: { value: "url" } });

    expect(updateBlock).toHaveBeenCalledWith(
      "btn-img-source",
      expect.objectContaining({
        imageSourceType: "externalUrl",
        imageAssetId: null,
      }),
    );
  });

  it("버튼 텍스트 변경 시 updateBlock 이 label 로 호출되어야 한다", () => {
    const updateBlock = vi.fn();

    render(
      <ButtonPropertiesPanel
        buttonProps={baseProps}
        selectedBlockId="btn-label"
        updateBlock={updateBlock}
      />,
    );

    const textarea = screen.getByLabelText("Button text");
    fireEvent.change(textarea, { target: { value: "새 버튼" } });

    expect(updateBlock).toHaveBeenCalledWith(
      "btn-label",
      expect.objectContaining({ label: "새 버튼" }),
    );
  });

  it("버튼 텍스트 textarea 는 라이트/다크 테마에 맞는 배경/텍스트 클래스를 사용해야 한다", () => {
    const updateBlock = vi.fn();

    render(
      <ButtonPropertiesPanel
        buttonProps={baseProps}
        selectedBlockId="btn-theme"
        updateBlock={updateBlock}
      />,
    );

    const textarea = screen.getByLabelText("Button text") as HTMLTextAreaElement;
    const className = textarea.getAttribute("class") ?? "";

    // 라이트 모드: 흰 배경 + 어두운 텍스트
    expect(className).toContain("bg-white");
    expect(className).toContain("text-slate-900");
    // 다크 모드: 다크 배경 + 밝은 텍스트
    expect(className).toContain("dark:bg-slate-900");
    expect(className).toContain("dark:text-slate-100");
  });

  it("가로 패딩 슬라이더 변경 시 updateBlock 이 paddingX 로 호출되어야 한다", () => {
    const updateBlock = vi.fn();

    render(
      <ButtonPropertiesPanel
        buttonProps={{ ...baseProps, paddingX: 16 }}
        selectedBlockId="btn-padding-x"
        updateBlock={updateBlock}
      />,
    );

    const slider = screen.getByLabelText("Horizontal padding (px) 슬라이더");
    fireEvent.change(slider, { target: { value: "24" } });

    expect(updateBlock).toHaveBeenCalledWith(
      "btn-padding-x",
      expect.objectContaining({ paddingX: 24 }),
    );
  });

  it("세로 패딩 슬라이더 변경 시 updateBlock 이 paddingY 로 호출되어야 한다", () => {
    const updateBlock = vi.fn();

    render(
      <ButtonPropertiesPanel
        buttonProps={{ ...baseProps, paddingY: 10 }}
        selectedBlockId="btn-padding-y"
        updateBlock={updateBlock}
      />,
    );

    const slider = screen.getByLabelText("Vertical padding (px) 슬라이더");
    fireEvent.change(slider, { target: { value: "18" } });

    expect(updateBlock).toHaveBeenCalledWith(
      "btn-padding-y",
      expect.objectContaining({ paddingY: 18 }),
    );
  });

  it("버튼 링크 인풋 변경 시 updateBlock 이 href 로 호출되어야 한다", () => {
    const updateBlock = vi.fn();

    render(
      <ButtonPropertiesPanel
        buttonProps={{ ...baseProps, href: "#" }}
        selectedBlockId="btn-href"
        updateBlock={updateBlock}
      />,
    );

    const input = screen.getByLabelText("Button link");
    fireEvent.change(input, { target: { value: "/signup" } });

    expect(updateBlock).toHaveBeenCalledWith(
      "btn-href",
      expect.objectContaining({ href: "/signup" }),
    );
  });

  it("버튼 정렬 셀렉트 변경 시 updateBlock 이 align 으로 호출되어야 한다", () => {
    const updateBlock = vi.fn();

    render(
      <ButtonPropertiesPanel
        buttonProps={{ ...baseProps, align: "left" }}
        selectedBlockId="btn-align"
        updateBlock={updateBlock}
      />,
    );

    const select = screen.getByLabelText("Button alignment");
    fireEvent.change(select, { target: { value: "center" } });

    expect(updateBlock).toHaveBeenCalledWith(
      "btn-align",
      expect.objectContaining({ align: "center" }),
    );
  });

  it("버튼 스타일 셀렉트 변경 시 updateBlock 이 variant 로 호출되어야 한다", () => {
    const updateBlock = vi.fn();

    render(
      <ButtonPropertiesPanel
        buttonProps={{ ...baseProps, variant: "solid" }}
        selectedBlockId="btn-variant"
        updateBlock={updateBlock}
      />,
    );

    const select = screen.getByLabelText("Button style");
    fireEvent.change(select, { target: { value: "outline" } });

    expect(updateBlock).toHaveBeenCalledWith(
      "btn-variant",
      expect.objectContaining({ variant: "outline" }),
    );
  });

  it("모서리 둥글기 슬라이더 변경 시 updateBlock 이 borderRadius 토큰으로 호출되어야 한다", () => {
    const updateBlock = vi.fn();

    render(
      <ButtonPropertiesPanel
        buttonProps={{ ...baseProps, borderRadius: "md" }}
        selectedBlockId="btn-radius"
        updateBlock={updateBlock}
      />,
    );

    const slider = screen.getByLabelText("Border radius 슬라이더");
    fireEvent.change(slider, { target: { value: "4" } });

    expect(updateBlock).toHaveBeenCalledWith(
      "btn-radius",
      expect.objectContaining({ borderRadius: "full" }),
    );
  });

  it("widthMode 가 fixed 일 때 고정 너비 슬라이더 변경 시 updateBlock 이 widthPx 로 호출되어야 한다", () => {
    const updateBlock = vi.fn();

    render(
      <ButtonPropertiesPanel
        buttonProps={{ ...baseProps, widthMode: "fixed", widthPx: 240 }}
        selectedBlockId="btn-width-fixed"
        updateBlock={updateBlock}
      />,
    );

    const slider = screen.getByLabelText("Button fixed width 슬라이더");
    fireEvent.change(slider, { target: { value: "300" } });

    expect(updateBlock).toHaveBeenCalledWith(
      "btn-width-fixed",
      expect.objectContaining({ widthPx: 300 }),
    );
  });

  it("버튼 크기 슬라이더 변경 시 updateBlock 이 fontSizeCustom 을 px 문자열로 호출해야 한다", () => {
    const updateBlock = vi.fn();

    render(
      <ButtonPropertiesPanel
        buttonProps={{ ...baseProps, fontSizeCustom: "16px" }}
        selectedBlockId="btn-font-size"
        updateBlock={updateBlock}
      />,
    );

    const slider = screen.getByLabelText("Button font size 슬라이더");
    fireEvent.change(slider, { target: { value: "20" } });

    expect(updateBlock).toHaveBeenCalledWith(
      "btn-font-size",
      expect.objectContaining({ fontSizeCustom: "20px" }),
    );
  });

  it("줄 간격 슬라이더 변경 시 updateBlock 이 lineHeightCustom 으로 호출되어야 한다", () => {
    const updateBlock = vi.fn();

    render(
      <ButtonPropertiesPanel
        buttonProps={{ ...baseProps, lineHeightCustom: "1.4" }}
        selectedBlockId="btn-line-height"
        updateBlock={updateBlock}
      />,
    );

    const slider = screen.getByLabelText("Line height 슬라이더");
    fireEvent.change(slider, { target: { value: "1.8" } });

    expect(updateBlock).toHaveBeenCalledWith(
      "btn-line-height",
      expect.objectContaining({ lineHeightCustom: "1.8" }),
    );
  });

  it("글자 간격 슬라이더 변경 시 updateBlock 이 letterSpacingCustom 을 em 단위로 호출해야 한다", () => {
    const updateBlock = vi.fn();

    render(
      <ButtonPropertiesPanel
        buttonProps={{ ...baseProps, letterSpacingCustom: "0em" }}
        selectedBlockId="btn-letter-spacing"
        updateBlock={updateBlock}
      />,
    );

    const slider = screen.getByLabelText("Letter spacing 슬라이더");
    fireEvent.change(slider, { target: { value: "16" } });

    expect(updateBlock).toHaveBeenCalledWith(
      "btn-letter-spacing",
      expect.objectContaining({ letterSpacingCustom: "1em" }),
    );
  });

  it("버튼 주요 인풋/셀렉트 컨트롤은 라이트/다크 듀얼 테마 크롬을 사용해야 한다", () => {
    const updateBlock = vi.fn();

    render(
      <ButtonPropertiesPanel
        buttonProps={baseProps as any}
        selectedBlockId="btn-theme"
        updateBlock={updateBlock}
      />,
    );

    const labelTextarea = screen.getByLabelText("Button text") as HTMLTextAreaElement;
    const labelClass = labelTextarea.getAttribute("class") ?? "";
    expect(labelClass).toContain("bg-white");
    expect(labelClass).toContain("text-slate-900");
    expect(labelClass).toContain("border-slate-300");
    expect(labelClass).toContain("dark:bg-slate-900");
    expect(labelClass).toContain("dark:text-slate-100");
    expect(labelClass).toContain("dark:border-slate-700");

    const imageSourceSelect = screen.getByLabelText("Button image source") as HTMLSelectElement;
    const imageSourceClass = imageSourceSelect.getAttribute("class") ?? "";
    expect(imageSourceClass).toContain("bg-white");
    expect(imageSourceClass).toContain("text-slate-900");
    expect(imageSourceClass).toContain("border-slate-300");
    expect(imageSourceClass).toContain("dark:bg-slate-900");
    expect(imageSourceClass).toContain("dark:text-slate-100");
    expect(imageSourceClass).toContain("dark:border-slate-700");

    const alignSelect = screen.getByLabelText("Button alignment") as HTMLSelectElement;
    const alignClass = alignSelect.getAttribute("class") ?? "";
    expect(alignClass).toContain("bg-white");
    expect(alignClass).toContain("text-slate-900");
    expect(alignClass).toContain("border-slate-300");
    expect(alignClass).toContain("dark:bg-slate-900");
    expect(alignClass).toContain("dark:text-slate-100");
    expect(alignClass).toContain("dark:border-slate-700");
  });
});
