import "dotenv/config";
import { describe, it, expect, vi } from "vitest";
import type { FormBlockProps } from "@/features/editor/state/editorStore";

const BASE_URL = "http://localhost";

// /api/forms/submit 라우트에 대한 기본 TDD:
// - internal 모드에서 임의 필드가 포함된 FormData 를 받아도 에러 없이 ok:true 를 반환해야 한다.
// - webhook 모드에서는 config.extraParams / headers 와 함께 x-www-form-urlencoded 로 전달해야 한다.

describe("/api/forms/submit", () => {
  it("internal 모드에서는 v2 필드 이름(email_address 등)을 포함한 FormData 를 받아도 ok:true 를 반환해야 한다", async () => {
    const config: FormBlockProps = {
      kind: "contact",
      submitTarget: "internal",
      successMessage: "성공적으로 전송되었습니다.",
      errorMessage: "전송 중 오류가 발생했습니다.",
      fieldIds: [],
      submitButtonId: null,
    };

    const formData = new FormData();
    formData.append("email_address", "test@example.com");
    formData.append("__config", JSON.stringify(config));

    const { POST: handleSubmit } = await import("@/app/api/forms/submit/route");

    const res = await handleSubmit(
      new Request(`${BASE_URL}/api/forms/submit`, {
        method: "POST",
        body: formData,
      }),
    );

    expect(res.status).toBe(200);
    const json = (await res.json()) as any;
    expect(json.ok).toBe(true);
  });

  it("webhook 모드에서는 extraParams 와 FormData 필드들이 x-www-form-urlencoded payload 로 전달되어야 한다", async () => {
    const destinationUrl = `${BASE_URL}/webhook-test`;

    const config: FormBlockProps = {
      kind: "contact",
      submitTarget: "webhook",
      destinationUrl,
      method: "POST",
      headers: { Authorization: "Bearer test-token" },
      extraParams: { source: "builder", formId: "contact-hero" },
      successMessage: "성공적으로 전송되었습니다.",
      errorMessage: "전송 중 오류가 발생했습니다.",
      fieldIds: [],
      submitButtonId: null,
    };

    // fetch 를 목킹하여 payload 와 헤더가 기대대로 전달되는지 검증한다.
    const fetchMock = vi.fn(async () => new Response("ok", { status: 200 }));
    // @ts-expect-error - 글로벌 fetch 재정의
    global.fetch = fetchMock;

    const formData = new FormData();
    formData.append("email_address", "test@example.com");
    formData.append("__config", JSON.stringify(config));

    const { POST: handleSubmit } = await import("@/app/api/forms/submit/route");

    const res = await handleSubmit(
      new Request(`${BASE_URL}/api/forms/submit`, {
        method: "POST",
        body: formData,
      }),
    );

    expect(res.status).toBe(200);
    const json = (await res.json()) as any;
    expect(json.ok).toBe(true);

    expect(fetchMock).toHaveBeenCalledTimes(1);
    const [url, options] = fetchMock.mock.calls[0] as [string, RequestInit];
    expect(url).toBe(destinationUrl);
    expect(options.method).toBe("POST");
    expect(options.headers).toMatchObject({
      "Content-Type": "application/x-www-form-urlencoded",
      Authorization: "Bearer test-token",
    });

    const body = options.body as string;
    // payload 안에 폼 필드와 extraParams 가 모두 포함되어야 한다.
    expect(body).toContain("email_address=test%40example.com");
    expect(body).toContain("source=builder");
    expect(body).toContain("formId=contact-hero");
  });

  it("webhook 모드에서 payloadFormat 이 json 인 경우 application/json 으로 JSON body 를 전송해야 한다", async () => {
    const destinationUrl = `${BASE_URL}/webhook-json-test`;

    const config: FormBlockProps = {
      kind: "contact",
      submitTarget: "webhook",
      destinationUrl,
      method: "POST",
      headers: { Authorization: "Bearer json-token" },
      extraParams: { source: "builder", formId: "contact-json" },
      successMessage: "성공적으로 전송되었습니다.",
      errorMessage: "전송 중 오류가 발생했습니다.",
      fieldIds: [],
      submitButtonId: null,
      payloadFormat: "json",
    };

    const fetchMock = vi.fn(async () => new Response("ok", { status: 200 }));
    // @ts-expect-error - 글로벌 fetch 재정의
    global.fetch = fetchMock;

    const formData = new FormData();
    formData.append("email_address", "json@example.com");
    formData.append("__config", JSON.stringify(config));

    const { POST: handleSubmit } = await import("@/app/api/forms/submit/route");

    const res = await handleSubmit(
      new Request(`${BASE_URL}/api/forms/submit`, {
        method: "POST",
        body: formData,
      }),
    );

    expect(res.status).toBe(200);
    const json = (await res.json()) as any;
    expect(json.ok).toBe(true);
    expect(json.message).toBe(config.successMessage);

    expect(fetchMock).toHaveBeenCalledTimes(1);
    const [url, options] = fetchMock.mock.calls[0] as unknown as [string, RequestInit];
    expect(url).toBe(destinationUrl);
    expect(options.method).toBe("POST");
    expect(options.headers).toMatchObject({
      "Content-Type": "application/json",
      Authorization: "Bearer json-token",
    });

    const body = options.body as string;
    const parsed = JSON.parse(body) as Record<string, string>;
    expect(parsed.email_address).toBe("json@example.com");
    expect(parsed.source).toBe("builder");
    expect(parsed.formId).toBe("contact-json");
  });

  it("webhook 모드 성공 시에도 successMessage 가 응답 message 필드에 포함되어야 한다", async () => {
    const destinationUrl = `${BASE_URL}/webhook-success-message`;

    const config: FormBlockProps = {
      kind: "contact",
      submitTarget: "webhook",
      destinationUrl,
      method: "POST",
      headers: { Authorization: "Bearer success-token" },
      extraParams: { source: "builder" },
      successMessage: "웹훅으로 전송되었습니다.",
      errorMessage: "웹훅 전송 실패",
      fieldIds: [],
      submitButtonId: null,
    };

    const fetchMock = vi.fn(async () => new Response("ok", { status: 200 }));
    // @ts-expect-error - 글로벌 fetch 재정의
    global.fetch = fetchMock;

    const formData = new FormData();
    formData.append("email_address", "success-webhook@example.com");
    formData.append("__config", JSON.stringify(config));

    const { POST: handleSubmit } = await import("@/app/api/forms/submit/route");

    const res = await handleSubmit(
      new Request(`${BASE_URL}/api/forms/submit`, {
        method: "POST",
        body: formData,
      }),
    );

    expect(res.status).toBe(200);
    const json = (await res.json()) as any;
    expect(json.ok).toBe(true);
    expect(json.message).toBe(config.successMessage);
  });

  it("both 모드에서는 internal 처리 후 webhook 으로도 전송되어야 한다", async () => {
    const destinationUrl = `${BASE_URL}/webhook-both`;

    const config: FormBlockProps = {
      kind: "contact",
      submitTarget: "both",
      destinationUrl,
      method: "POST",
      headers: { Authorization: "Bearer both-token" },
      extraParams: { source: "builder-both" },
      successMessage: "양쪽 모두 전송되었습니다.",
      errorMessage: "both 모드 전송 실패",
      fieldIds: [],
      submitButtonId: null,
    };

    const fetchMock = vi.fn(async () => new Response("ok", { status: 200 }));
    (globalThis as any).fetch = fetchMock;

    const formData = new FormData();
    formData.append("email_address", "both@example.com");
    formData.append("__config", JSON.stringify(config));

    const { POST: handleSubmit } = await import("@/app/api/forms/submit/route");

    const res = await handleSubmit(
      new Request(`${BASE_URL}/api/forms/submit`, {
        method: "POST",
        body: formData,
      }),
    );

    expect(res.status).toBe(200);
    const json = (await res.json()) as any;
    expect(json.ok).toBe(true);
    expect(json.message).toBe(config.successMessage);

    expect(fetchMock).toHaveBeenCalledTimes(1);
    const [url, options] = fetchMock.mock.calls[0] as [string, RequestInit];
    expect(url).toBe(destinationUrl);
    expect(options.method).toBe("POST");
  });

  it("webhook 모드에서 destinationUrl 이 없으면 400 과 destinationUrl_missing 및 errorMessage 가 포함되어야 한다", async () => {
    const config: FormBlockProps = {
      kind: "contact",
      submitTarget: "webhook",
      // destinationUrl 누락
      successMessage: "성공 메시지",
      errorMessage: "Webhook URL 이 설정되지 않았습니다.",
      fieldIds: [],
      submitButtonId: null,
    } as any;

    const formData = new FormData();
    formData.append("email_address", "missing-destination@example.com");
    formData.append("__config", JSON.stringify(config));

    const { POST: handleSubmit } = await import("@/app/api/forms/submit/route");

    const res = await handleSubmit(
      new Request(`${BASE_URL}/api/forms/submit`, {
        method: "POST",
        body: formData,
      }),
    );

    expect(res.status).toBe(400);
    const json = (await res.json()) as any;
    expect(json.ok).toBe(false);
    expect(json.error).toBe("destinationUrl_missing");
    expect(json.message).toBe(config.errorMessage);
  });

  it("webhook 모드에서 원격 응답이 실패하면 502 와 webhook_failed, errorMessage 가 포함되어야 한다", async () => {
    const destinationUrl = `${BASE_URL}/webhook-fail`;

    const config: FormBlockProps = {
      kind: "contact",
      submitTarget: "webhook",
      destinationUrl,
      method: "POST",
      headers: {},
      extraParams: {},
      successMessage: "성공 메시지",
      errorMessage: "웹훅 호출이 실패했습니다.",
      fieldIds: [],
      submitButtonId: null,
    };

    const fetchMock = vi.fn(async () => new Response("remote error", { status: 503 }));
    // @ts-expect-error - 글로벌 fetch 재정의
    global.fetch = fetchMock;

    const formData = new FormData();
    formData.append("email_address", "fail-remote@example.com");
    formData.append("__config", JSON.stringify(config));

    const { POST: handleSubmit } = await import("@/app/api/forms/submit/route");

    const res = await handleSubmit(
      new Request(`${BASE_URL}/api/forms/submit`, {
        method: "POST",
        body: formData,
      }),
    );

    expect(res.status).toBe(502);
    const json = (await res.json()) as any;
    expect(json.ok).toBe(false);
    expect(json.error).toBe("webhook_failed");
    expect(json.message).toBe(config.errorMessage);
  });

  it("webhook 모드에서 fetch 예외가 발생하면 500 과 webhook_exception, errorMessage 가 포함되어야 한다", async () => {
    const destinationUrl = `${BASE_URL}/webhook-exception`;

    const config: FormBlockProps = {
      kind: "contact",
      submitTarget: "webhook",
      destinationUrl,
      method: "POST",
      headers: {},
      extraParams: {},
      successMessage: "성공 메시지",
      errorMessage: "웹훅 호출 중 예외가 발생했습니다.",
      fieldIds: [],
      submitButtonId: null,
    };

    const fetchMock = vi.fn(async () => {
      throw new Error("network error");
    });
    // @ts-expect-error - 글로벌 fetch 재정의
    global.fetch = fetchMock;

    const formData = new FormData();
    formData.append("email_address", "exception@example.com");
    formData.append("__config", JSON.stringify(config));

    const { POST: handleSubmit } = await import("@/app/api/forms/submit/route");

    const res = await handleSubmit(
      new Request(`${BASE_URL}/api/forms/submit`, {
        method: "POST",
        body: formData,
      }),
    );

    expect(res.status).toBe(500);
    const json = (await res.json()) as any;
    expect(json.ok).toBe(false);
    expect(json.error).toBe("webhook_exception");
    expect(json.message).toBe(config.errorMessage);
  });
});
