import { describe, it, expect, afterEach } from "vitest";
import { render, cleanup } from "@testing-library/react";
import { PublicPageRenderer } from "@/features/editor/components/PublicPageRenderer";
import type { Block } from "@/features/editor/state/editorStore";

// 폼 필드 스타일 TDD
// - formInput: 색상/패딩/너비/둥글기 스타일
// - formSelect: 색상/패딩/너비/둥글기 스타일
// - formCheckbox/formRadio: 옵션 컨테이너 색상/패딩/둥글기 스타일

function getFirstLabel(root: HTMLElement): HTMLLabelElement {
  const label = root.querySelector("label");
  if (!label) {
    throw new Error("label element not found");
  }
  return label as HTMLLabelElement;
}

describe("PublicPageRenderer - 폼 필드 스타일", () => {
  afterEach(() => {
    cleanup();
  });

  it("formInput 블록은 색상/패딩/너비/둥글기 스타일을 반영해야 한다", () => {
    const blocks: Block[] = [
      {
        id: "form_input_styles",
        type: "formInput",
        props: {
          label: "이메일",
          formFieldName: "email",
          align: "center",
          widthMode: "fixed",
          widthPx: 320,
          paddingX: 16,
          paddingY: 8,
          textColorCustom: "#112233",
          fillColorCustom: "#123456",
          strokeColorCustom: "#ff0000",
          borderRadius: "lg",
        } as any,
      },
    ];

    const { getByTestId } = render(<PublicPageRenderer blocks={blocks} />);

    const wrapper = getByTestId("preview-form-input-wrapper") as HTMLElement;
    const input = wrapper.querySelector('[data-testid="preview-form-input"]') as HTMLElement | null;
    expect(input).not.toBeNull();

    // 정렬
    expect((input as HTMLInputElement).style.textAlign).toBe("center");
    // width: 320px / 16 = 20em
    expect((input as HTMLInputElement).style.width).toBe("20em");
    // padding-inline/block: 16px/8px -> 1em / 0.5em
    expect((input as HTMLInputElement).style.paddingInline).toBe("1em");
    expect((input as HTMLInputElement).style.paddingBlock).toBe("0.5em");
    // 색상: hex -> rgb 변환
    expect((input as HTMLInputElement).style.color).toBe("rgb(17, 34, 51)");
    expect((input as HTMLInputElement).style.backgroundColor).toBe("rgb(18, 52, 86)");
    expect((input as HTMLInputElement).style.borderColor).toBe("rgb(255, 0, 0)");
    // borderRadius: lg -> 6px
    expect((input as HTMLInputElement).style.borderRadius).toBe("6px");
  });

  it("formInput 블록에서 inputType/email 과 formFieldName 은 type/name/placeholder 로 반영되어야 한다", () => {
    const blocks: Block[] = [
      {
        id: "form_input_email_attrs",
        type: "formInput",
        props: {
          label: "이메일",
          formFieldName: "email_field",
          inputType: "email",
          placeholder: "이메일 주소",
        } as any,
      },
    ];

    const { getByLabelText } = render(<PublicPageRenderer blocks={blocks} />);

    const input = getByLabelText("이메일") as HTMLInputElement;
    expect(input.type).toBe("email");
    expect(input.name).toBe("email_field");
    expect(input.placeholder).toBe("이메일 주소");
  });

  it("formInput 블록의 타이포그래피 커스텀 값은 em 단위 스타일로 반영되어야 한다", () => {
    const blocks: Block[] = [
      {
        id: "form_input_typography",
        type: "formInput",
        props: {
          label: "커스텀 타이포",
          formFieldName: "typo",
          fontSizeCustom: "24px",
          lineHeightCustom: "30px",
          letterSpacingCustom: "2px",
        } as any,
      },
    ];

    const { getByTestId } = render(<PublicPageRenderer blocks={blocks} />);

    const wrapper = getByTestId("preview-form-input-wrapper") as HTMLElement;
    const input = wrapper.querySelector('[data-testid="preview-form-input"]') as HTMLInputElement | null;
    expect(input).not.toBeNull();

    expect(input!.style.fontSize).toBe("1.5em");
    expect(input!.style.lineHeight).toBe("1.875em");
    expect(input!.style.letterSpacing).toBe("0.125em");
  });

  it("formInput 블록에서 라벨 표시 방식이 hidden 이면 sr-only 클래스로 시각적으로 숨겨야 한다", () => {
    const blocks: Block[] = [
      {
        id: "form_input_hidden_label",
        type: "formInput",
        props: {
          label: "숨김 라벨",
          formFieldName: "hidden_label",
          labelDisplay: "hidden",
        } as any,
      },
    ];

    const { getByTestId, getByLabelText } = render(<PublicPageRenderer blocks={blocks} />);

    const wrapper = getByTestId("preview-form-input-wrapper") as HTMLElement;
    const span = wrapper.querySelector("span");
    expect(span).not.toBeNull();
    expect((span as HTMLSpanElement).className).toContain("sr-only");

    // label 이 숨김이더라도 접근성 측면에서 getByLabelText 로는 접근 가능해야 한다.
    const input = getByLabelText("숨김 라벨") as HTMLInputElement;
    expect(input).toBeTruthy();
  });

  it("formInput 블록에서 라벨 표시 방식이 floating 이면 pb-form-field/pb-form-label 기반 플로팅 라벨 마크업을 사용해야 한다", () => {
    const blocks: Block[] = [
      {
        id: "form_input_floating_label",
        type: "formInput",
        props: {
          label: "플로팅 라벨",
          formFieldName: "floating_label",
          labelDisplay: "floating",
        } as any,
      },
    ];

    const { container, getByLabelText } = render(<PublicPageRenderer blocks={blocks} />);

    const field = container.querySelector(
      '[data-testid="preview-form-input-wrapper"]',
    ) as HTMLDivElement | null;
    expect(field).not.toBeNull();
    expect(field!.className).toContain("pb-form-field");
    expect(field!.className).toContain("pb-form-field--floating");

    const firstChild = field!.firstElementChild as HTMLElement | null;
    const lastChild = field!.lastElementChild as HTMLElement | null;
    expect(firstChild).not.toBeNull();
    expect(lastChild).not.toBeNull();
    // 플로팅 라벨 CSS 는 input/textarea 다음 형제 label 을 기준으로 동작하므로,
    // DOM 상에서 input 이 먼저, label 이 나중에 와야 한다.
    expect(firstChild!.getAttribute("data-testid")).toBe("preview-form-input");
    expect(lastChild!.tagName).toBe("LABEL");

    const label = lastChild as HTMLLabelElement;
    expect(label.className).toContain("pb-form-label");

    const input = getByLabelText("플로팅 라벨") as HTMLInputElement;
    expect(input.className).toContain("pb-input");
    // 플로팅 라벨 모드에서는 placeholder 를 공백으로 두고, 라벨만 인풋 안/위에서 이동시킨다.
    expect(input.placeholder).toBe(" ");
    // formFieldName 은 name/id/htmlFor 로 연결되어야 한다.
    expect(input.name).toBe("floating_label");
    expect(input.id).toBe("floating_label");
    expect(label.htmlFor).toBe("floating_label");
  });

  it("formInput 플로팅 라벨에서 paddingY 값은 pb-form-field--floating 의 --pb-input-padding-y CSS 변수로 전달되어야 한다", () => {
    const blocks: Block[] = [
      {
        id: "form_input_floating_padding",
        type: "formInput",
        props: {
          label: "플로팅 패딩",
          formFieldName: "floating_padding",
          labelDisplay: "floating",
          paddingY: 16,
        } as any,
      },
    ];

    const { getByTestId } = render(<PublicPageRenderer blocks={blocks} />);

    const field = getByTestId("preview-form-input-wrapper") as HTMLDivElement;
    // paddingY: 16px -> 1rem 으로 변환되어 CSS 변수에 설정되어야 한다.
    expect(field.style.getPropertyValue("--pb-input-padding-y")).toBe("1rem");
  });

  it("formInput 플로팅 라벨에서 label 과 placeholder 가 같으면 placeholder 텍스트를 인풋 안에 표시하지 않아야 한다", () => {
    const blocks: Block[] = [
      {
        id: "form_input_floating_same_placeholder",
        type: "formInput",
        props: {
          label: "이름",
          formFieldName: "name",
          labelDisplay: "floating",
          placeholder: "이름",
        } as any,
      },
    ];

    const { getByLabelText } = render(<PublicPageRenderer blocks={blocks} />);

    const input = getByLabelText("이름") as HTMLInputElement;
    // 플로팅 라벨 모드에서는 placeholder 를 공백으로 두고, 라벨만 인풋 안/위에서 이동시킨다.
    expect(input.placeholder).toBe(" ");
  });

  it("formSelect 블록은 색상/패딩/너비/둥글기 스타일을 반영해야 한다", () => {
    const blocks: Block[] = [
      {
        id: "form_select_styles",
        type: "formSelect",
        props: {
          label: "카테고리",
          formFieldName: "category",
          options: [
            { label: "A", value: "a" },
            { label: "B", value: "b" },
          ],
          widthMode: "fixed",
          widthPx: 240,
          paddingX: 16,
          paddingY: 8,
          textColorCustom: "#00ff00",
          fillColorCustom: "#123456",
          strokeColorCustom: "#ff0000",
          borderRadius: "lg",
        } as any,
      },
    ];

    const { getByTestId } = render(<PublicPageRenderer blocks={blocks} />);

    const wrapper = getByTestId("preview-form-select") as HTMLElement;
    const select = wrapper.querySelector("select") as HTMLSelectElement | null;
    expect(select).not.toBeNull();

    // width: 240px / 16 = 15em
    expect(wrapper.style.width).toBe("15em");
    // padding-inline/block: 16px/8px -> 1em / 0.5em
    expect(select!.style.paddingInline).toBe("1em");
    expect(select!.style.paddingBlock).toBe("0.5em");
    // 색상
    expect(select!.style.color).toBe("rgb(0, 255, 0)");
    expect(select!.style.backgroundColor).toBe("rgb(18, 52, 86)");
    expect(select!.style.borderColor).toBe("rgb(255, 0, 0)");
    // 둥글기
    expect(select!.style.borderRadius).toBe("6px");
  });

  it("formSelect 블록에서 라벨 표시 방식이 hidden 이면 sr-only 클래스로 시각적으로 숨겨야 한다", () => {
    const blocks: Block[] = [
      {
        id: "form_select_hidden_label",
        type: "formSelect",
        props: {
          label: "숨김 셀렉트 라벨",
          formFieldName: "select_hidden",
          options: [
            { label: "A", value: "a" },
          ],
          labelDisplay: "hidden",
        } as any,
      },
    ];

    const { container } = render(<PublicPageRenderer blocks={blocks} />);

    const label = container.querySelector("label") as HTMLLabelElement | null;
    expect(label).not.toBeNull();
    const span = label!.querySelector("span");
    expect(span).not.toBeNull();
    expect((span as HTMLSpanElement).className).toContain("sr-only");
  });

  it("formSelect 블록의 select 요소는 formFieldName 을 name 속성으로 사용해야 한다", () => {
    const blocks: Block[] = [
      {
        id: "form_select_name_attr",
        type: "formSelect",
        props: {
          label: "카테고리",
          formFieldName: "category_field",
          options: [
            { label: "A", value: "a" },
            { label: "B", value: "b" },
          ],
        } as any,
      },
    ];

    const { getByTestId } = render(<PublicPageRenderer blocks={blocks} />);

    const wrapper = getByTestId("preview-form-select") as HTMLElement;
    const select = wrapper.querySelector("select") as HTMLSelectElement | null;
    expect(select).not.toBeNull();
    expect(select!.name).toBe("category_field");
  });

  it("formCheckbox 블록은 옵션 컨테이너에 색상/패딩/둥글기 스타일을 반영해야 한다", () => {
    const blocks: Block[] = [
      {
        id: "form_checkbox_styles",
        type: "formCheckbox",
        props: {
          groupLabel: "옵션들",
          formFieldName: "features",
          options: [{ label: "옵션 1", value: "opt1" }],
          widthMode: "fixed",
          widthPx: 320,
          paddingX: 8,
          paddingY: 4,
          optionGapPx: 16,
          textColorCustom: "#ffffff",
          fillColorCustom: "#123456",
          strokeColorCustom: "#ff0000",
          borderRadius: "lg",
        } as any,
      },
    ];

    const { getByTestId } = render(<PublicPageRenderer blocks={blocks} />);

    const group = getByTestId("preview-form-checkbox-group") as HTMLElement;
    const firstLabel = getFirstLabel(group);

    // width: 320px / 16 = 20em
    expect(group.style.width).toBe("20em");
    // 옵션 간 간격: 16px -> 1em
    const optionsContainer = group.querySelector('[data-testid="preview-form-checkbox-options"]') as HTMLElement | null;
    expect(optionsContainer).not.toBeNull();
    expect(optionsContainer!.style.rowGap).toBe("1em");

    // 옵션 컨테이너 스타일: padding, 배경, 보더, 둥글기
    expect(firstLabel.style.paddingInline).toBe("0.5em");
    expect(firstLabel.style.paddingBlock).toBe("0.25em");
    expect(firstLabel.style.backgroundColor).toBe("rgb(18, 52, 86)");
    expect(firstLabel.style.borderColor).toBe("rgb(255, 0, 0)");
    expect(firstLabel.style.borderRadius).toBe("6px");
  });

  it("formRadio 블록의 옵션 input 은 type/name/value 및 FormBlock.requiredFieldIds 기반 required 를 가져야 한다", () => {
    const blocks: Block[] = [
      {
        id: "form_ctrl_radio",
        type: "form",
        props: {
          kind: "contact",
          submitTarget: "internal",
          requiredFieldIds: ["radio_required"],
        } as any,
      },
      {
        id: "radio_required",
        type: "formRadio",
        props: {
          groupLabel: "플랜",
          formFieldName: "plan",
          options: [
            { label: "A", value: "a" },
            { label: "B", value: "b" },
          ],
        } as any,
      },
    ];

    const { getByTestId } = render(<PublicPageRenderer blocks={blocks} />);

    const group = getByTestId("preview-form-radio-group") as HTMLElement;
    const inputs = group.querySelectorAll("input[type='radio']");
    expect(inputs.length).toBe(2);

    const first = inputs[0] as HTMLInputElement;
    const second = inputs[1] as HTMLInputElement;

    expect(first.type).toBe("radio");
    expect(first.name).toBe("plan");
    expect(first.value).toBe("a");
    expect(first.required).toBe(true);

    expect(second.type).toBe("radio");
    expect(second.name).toBe("plan");
    expect(second.value).toBe("b");
    expect(second.required).toBe(false);
  });

  it("formCheckbox 블록의 각 옵션 라벨은 builder.css 의 pb-form-option 클래스를 사용해야 한다", () => {
    const blocks: Block[] = [
      {
        id: "form_checkbox_pb_option_class",
        type: "formCheckbox",
        props: {
          groupLabel: "옵션들",
          formFieldName: "features",
          options: [
            { label: "옵션 1", value: "opt1" },
            { label: "옵션 2", value: "opt2" },
          ],
        } as any,
      },
    ];

    const { getByTestId } = render(<PublicPageRenderer blocks={blocks} />);

    const group = getByTestId("preview-form-checkbox-group") as HTMLElement;
    const optionLabels = group.querySelectorAll("label");
    expect(optionLabels.length).toBeGreaterThan(0);
    optionLabels.forEach((label) => {
      expect((label as HTMLLabelElement).className).toContain("pb-form-option");
    });
  });

  it("formCheckbox 블록에서 optionLayout 이 stacked 이면 옵션 컨테이너가 pb-form-options--stacked 클래스를 사용해야 한다", () => {
    const blocks: Block[] = [
      {
        id: "form_checkbox_layout_stacked",
        type: "formCheckbox",
        props: {
          groupLabel: "옵션들",
          formFieldName: "features",
          options: [
            { label: "옵션 1", value: "opt1" },
            { label: "옵션 2", value: "opt2" },
          ],
          optionLayout: "stacked",
        } as any,
      },
    ];

    const { getByTestId } = render(<PublicPageRenderer blocks={blocks} />);

    const optionsContainer = getByTestId(
      "preview-form-checkbox-options",
    ) as HTMLElement;
    expect(optionsContainer.className).toContain("pb-form-options");
    expect(optionsContainer.className).toContain("pb-form-options--stacked");
  });

  it("formCheckbox 블록에서 optionLayout 이 inline 이면 옵션 컨테이너가 pb-form-options--inline 클래스를 사용해야 한다", () => {
    const blocks: Block[] = [
      {
        id: "form_checkbox_layout_inline",
        type: "formCheckbox",
        props: {
          groupLabel: "옵션들",
          formFieldName: "features",
          options: [
            { label: "옵션 1", value: "opt1" },
            { label: "옵션 2", value: "opt2" },
          ],
          optionLayout: "inline",
        } as any,
      },
    ];

    const { getByTestId } = render(<PublicPageRenderer blocks={blocks} />);

    const optionsContainer = getByTestId(
      "preview-form-checkbox-options",
    ) as HTMLElement;
    expect(optionsContainer.className).toContain("pb-form-options");
    expect(optionsContainer.className).toContain("pb-form-options--inline");
  });

  it("formCheckbox 블록에서 그룹 타이틀 표시 방식이 hidden 이면 sr-only 클래스로 시각적으로 숨겨야 한다", () => {
    const blocks: Block[] = [
      {
        id: "form_checkbox_hidden_group_label",
        type: "formCheckbox",
        props: {
          groupLabel: "숨김 체크박스 그룹",
          formFieldName: "features_hidden",
          options: [{ label: "옵션 1", value: "opt1" }],
          groupLabelDisplay: "hidden",
        } as any,
      },
    ];

    const { getByTestId } = render(<PublicPageRenderer blocks={blocks} />);
    const group = getByTestId("preview-form-checkbox-group") as HTMLElement;
    const spans = group.querySelectorAll("span");
    const spanArray = Array.from(spans) as HTMLSpanElement[];
    const groupLabelSpan = spanArray.find((s) => s.textContent === "숨김 체크박스 그룹");
    expect(groupLabelSpan).toBeTruthy();
    expect(groupLabelSpan!.className).toContain("sr-only");
  });

  it("formCheckbox 블록의 옵션 input 은 type/ name/value 및 FormBlock.requiredFieldIds 기반 required 를 가져야 한다", () => {
    const blocks: Block[] = [
      {
        id: "form_ctrl_checkbox",
        type: "form",
        props: {
          kind: "contact",
          submitTarget: "internal",
          requiredFieldIds: ["checkbox_required"],
        } as any,
      },
      {
        id: "checkbox_required",
        type: "formCheckbox",
        props: {
          groupLabel: "기능",
          formFieldName: "feature",
          options: [
            { label: "A", value: "a" },
            { label: "B", value: "b" },
          ],
        } as any,
      },
    ];

    const { getByTestId } = render(<PublicPageRenderer blocks={blocks} />);

    const group = getByTestId("preview-form-checkbox-group") as HTMLElement;
    const inputs = group.querySelectorAll("input[type='checkbox']");
    expect(inputs.length).toBe(2);

    const first = inputs[0] as HTMLInputElement;
    const second = inputs[1] as HTMLInputElement;

    expect(first.type).toBe("checkbox");
    expect(first.name).toBe("feature");
    expect(first.value).toBe("a");
    expect(first.required).toBe(true);

    expect(second.type).toBe("checkbox");
    expect(second.name).toBe("feature");
    expect(second.value).toBe("b");
    expect(second.required).toBe(false);
  });

  it("formRadio 블록에서 그룹 타이틀 레이아웃이 inline 이면 그룹 컨테이너가 가로 정렬되고 세로 중앙 정렬 및 columnGap 이 labelGapPx 로 반영되어야 한다", () => {
    const blocks: Block[] = [
      {
        id: "form_radio_label_inline_preview",
        type: "formRadio",
        props: {
          groupLabel: "플랜 인라인",
          formFieldName: "plan-inline",
          groupLabelDisplay: "visible",
          labelLayout: "inline",
          labelGapPx: 16,
          options: [
            { label: "플랜 A", value: "a" },
            { label: "플랜 B", value: "b" },
          ],
        } as any,
      },
    ];

    const { getByTestId } = render(<PublicPageRenderer blocks={blocks} />);
    const group = getByTestId("preview-form-radio-group") as HTMLElement;

    expect(group.className).toContain("flex");
    expect(group.className).toContain("flex-row");
    expect(group.className).toContain("items-center");
    // labelGapPx: 16px → 1em
    expect(group.style.columnGap).toBe("1em");
  });

  it("formCheckbox 블록에서 그룹 타이틀 레이아웃이 inline 이면 그룹 컨테이너가 가로 정렬되고 세로 중앙 정렬 및 columnGap 이 labelGapPx 로 반영되어야 한다", () => {
    const blocks: Block[] = [
      {
        id: "form_checkbox_label_inline_preview",
        type: "formCheckbox",
        props: {
          groupLabel: "체크 인라인",
          formFieldName: "features-inline",
          groupLabelDisplay: "visible",
          labelLayout: "inline",
          labelGapPx: 12,
          options: [
            { label: "옵션 1", value: "opt1" },
            { label: "옵션 2", value: "opt2" },
          ],
        } as any,
      },
    ];

    const { getByTestId } = render(<PublicPageRenderer blocks={blocks} />);
    const group = getByTestId("preview-form-checkbox-group") as HTMLElement;

    expect(group.className).toContain("flex");
    expect(group.className).toContain("flex-row");
    expect(group.className).toContain("items-center");
    // labelGapPx: 12px → 0.75em
    expect(group.style.columnGap).toBe("0.75em");
  });

  it("formRadio 블록은 옵션 컨테이너에 색상/패딩/둥글기 스타일을 반영해야 한다", () => {
    const blocks: Block[] = [
      {
        id: "form_radio_styles",
        type: "formRadio",
        props: {
          groupLabel: "플랜",
          formFieldName: "plan",
          options: [{ label: "플랜 A", value: "a" }],
          widthMode: "fixed",
          widthPx: 320,
          paddingX: 8,
          paddingY: 4,
          optionGapPx: 16,
          textColorCustom: "#ffffff",
          fillColorCustom: "#123456",
          strokeColorCustom: "#ff0000",
          borderRadius: "lg",
        } as any,
      },
    ];

    const { getByTestId } = render(<PublicPageRenderer blocks={blocks} />);

    const group = getByTestId("preview-form-radio-group") as HTMLElement;
    const firstLabel = getFirstLabel(group);

    // width: 320px / 16 = 20em
    expect(group.style.width).toBe("20em");
    // 옵션 간 간격: 16px -> 1em
    const optionsContainer = group.querySelector('[data-testid="preview-form-radio-options"]') as HTMLElement | null;
    expect(optionsContainer).not.toBeNull();
    expect(optionsContainer!.style.rowGap).toBe("1em");

    // 옵션 컨테이너 스타일: padding, 배경, 보더, 둥글기
    expect(firstLabel.style.paddingInline).toBe("0.5em");
    expect(firstLabel.style.paddingBlock).toBe("0.25em");
    expect(firstLabel.style.backgroundColor).toBe("rgb(18, 52, 86)");
    expect(firstLabel.style.borderColor).toBe("rgb(255, 0, 0)");
    expect(firstLabel.style.borderRadius).toBe("6px");
  });

  it("formRadio 블록의 각 옵션 라벨은 builder.css 의 pb-form-option 클래스를 사용해야 한다", () => {
    const blocks: Block[] = [
      {
        id: "form_radio_pb_option_class",
        type: "formRadio",
        props: {
          groupLabel: "플랜",
          formFieldName: "plan",
          options: [
            { label: "플랜 A", value: "a" },
            { label: "플랜 B", value: "b" },
          ],
        } as any,
      },
    ];

    const { getByTestId } = render(<PublicPageRenderer blocks={blocks} />);

    const group = getByTestId("preview-form-radio-group") as HTMLElement;
    const optionLabels = group.querySelectorAll("label");
    expect(optionLabels.length).toBeGreaterThan(0);
    optionLabels.forEach((label) => {
      expect((label as HTMLLabelElement).className).toContain("pb-form-option");
    });
  });
});
