import "dotenv/config";
import { describe, it, expect, vi } from "vitest";

// PrismaClient를 실제 DB 대신 메모리 기반 저장소를 사용하는 목으로 대체한다.
// 이렇게 하면 CI/로컬 어디에서도 DATABASE_URL 이나 실제 DB 없이 API 테스트를 실행할 수 있다.
const inMemoryProjects: any[] = [];

vi.mock("@/generated/prisma/client", () => {
  class PrismaClientMock {
    project = {
      create: async ({ data }: any) => {
        const now = new Date();
        const project = {
          id: String(inMemoryProjects.length + 1),
          status: "DRAFT",
          createdAt: now,
          updatedAt: now,
          ...data,
        };
        inMemoryProjects.push(project);
        return project;
      },
      findUnique: async ({ where: { slug } }: any) => {
        return inMemoryProjects.find((p) => p.slug === slug) ?? null;
      },
      upsert: async ({ where: { slug }, update, create }: any) => {
        const index = inMemoryProjects.findIndex((p) => p.slug === slug);
        if (index >= 0) {
          const existing = inMemoryProjects[index];
          const updated = {
            ...existing,
            ...update,
            updatedAt: new Date(),
          };
          inMemoryProjects[index] = updated;
          return updated;
        }

        const now = new Date();
        const project = {
          id: String(inMemoryProjects.length + 1),
          status: "DRAFT",
          createdAt: now,
          updatedAt: now,
          ...create,
        };
        inMemoryProjects.push(project);
        return project;
      },
      delete: async ({ where: { slug } }: any) => {
        const index = inMemoryProjects.findIndex((p) => p.slug === slug);
        if (index === -1) {
          const error: any = new Error("Project not found");
          error.code = "P2025";
          throw error;
        }

        const [removed] = inMemoryProjects.splice(index, 1);
        return removed;
      },
      findMany: async ({ orderBy, take, select }: any = {}) => {
        let items = [...inMemoryProjects];

        if (orderBy?.createdAt === "desc") {
          items.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
        }

        if (typeof take === "number") {
          items = items.slice(0, take);
        }

        if (select) {
          return items.map((p) => {
            const picked: any = {};
            for (const key of Object.keys(select)) {
              if (select[key]) {
                picked[key] = p[key];
              }
            }
            return picked;
          });
        }

        return items;
      },
    };
  }

  return { PrismaClient: PrismaClientMock };
});

const BASE_URL = "http://localhost";

describe("/api/projects", () => {
  it("POST /api/projects 로 프로젝트를 생성하고 GET /api/projects/[slug] 로 조회할 수 있어야 한다", async () => {
    const payload = {
      title: "테스트 프로젝트",
      slug: `test-slug-${Date.now()}`,
      contentJson: [
        {
          id: "blk_test",
          type: "text",
          props: { text: "JSON에서 온 텍스트", align: "left", size: "base" },
        },
      ],
    };

    const { POST: createProject } = await import("@/app/api/projects/route");

    const createResponse = await createProject(
      new Request(`${BASE_URL}/api/projects`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(payload),
      }),
    );

    if (createResponse.status !== 201) {
      // 디버깅을 위해 에러 응답을 한번 출력해 둔다.
      // eslint-disable-next-line no-console
      console.error("createResponse status", createResponse.status, await createResponse.text());
    }
    expect(createResponse.status).toBe(201);
    const created = (await createResponse.json()) as any;
    expect(created.title).toBe(payload.title);
    expect(created.slug).toBe(payload.slug);

    const { GET: getProjectBySlug } = await import("@/app/api/projects/[slug]/route");

    const getResponse = await getProjectBySlug(
      new Request(`${BASE_URL}/api/projects/${payload.slug}`),
      { params: { slug: payload.slug } } as any,
    );

    expect(getResponse.status).toBe(200);
    const fetched = (await getResponse.json()) as any;
    expect(fetched.slug).toBe(payload.slug);
    expect(fetched.contentJson).toEqual(payload.contentJson);
  });

  it("GET /api/projects 는 최근 생성 순으로 프로젝트 목록을 반환해야 한다", async () => {
    const { POST: createProject } = await import("@/app/api/projects/route");

    const firstPayload = {
      title: "첫 번째 프로젝트",
      slug: "first-project",
      contentJson: [],
    };

    const secondPayload = {
      title: "두 번째 프로젝트",
      slug: "second-project",
      contentJson: [],
    };

    await createProject(
      new Request(`${BASE_URL}/api/projects`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(firstPayload),
      }),
    );

    await new Promise((resolve) => setTimeout(resolve, 5));

    await createProject(
      new Request(`${BASE_URL}/api/projects`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(secondPayload),
      }),
    );

    const { GET: listProjects } = await import("@/app/api/projects/route");

    const res = await listProjects(new Request(`${BASE_URL}/api/projects`));

    expect(res.status).toBe(200);
    const list = (await res.json()) as any[];

    expect(list.length).toBeGreaterThanOrEqual(2);
    expect(list[0].slug).toBe("second-project");
    expect(list[1].slug).toBe("first-project");
  });

  it("DELETE /api/projects/[slug] 로 프로젝트를 삭제할 수 있어야 한다", async () => {
    const { POST: createProject } = await import("@/app/api/projects/route");

    const slug = `delete-slug-${Date.now()}`;
    const payload = {
      title: "삭제 테스트 프로젝트",
      slug,
      contentJson: [],
    };

    await createProject(
      new Request(`${BASE_URL}/api/projects`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(payload),
      }),
    );

    const { DELETE: deleteProject, GET: getProjectBySlug } = await import("@/app/api/projects/[slug]/route");

    const deleteResponse = await deleteProject(
      new Request(`${BASE_URL}/api/projects/${slug}`, { method: "DELETE" }),
      { params: { slug } } as any,
    );

    expect(deleteResponse.status).toBe(200);

    const getAfterDelete = await getProjectBySlug(
      new Request(`${BASE_URL}/api/projects/${slug}`),
      { params: { slug } } as any,
    );

    expect(getAfterDelete.status).toBe(404);
  });

  it("동일 slug 로 두 번 POST 하면 두 번째 요청은 기존 프로젝트를 업데이트해야 한다", async () => {
    const { POST: handleProject } = await import("@/app/api/projects/route");

    const slug = "duplicate-slug";

    const firstPayload = {
      title: "첫 번째 제목",
      slug,
      contentJson: [
        {
          id: "blk_first",
          type: "text",
          props: { text: "첫 번째 내용", align: "left", size: "base" },
        },
      ],
    };

    const secondPayload = {
      title: "두 번째 제목",
      slug,
      contentJson: [
        {
          id: "blk_second",
          type: "text",
          props: { text: "두 번째 내용", align: "left", size: "base" },
        },
      ],
    };

    const firstRes = await handleProject(
      new Request(`${BASE_URL}/api/projects`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(firstPayload),
      }),
    );

    expect(firstRes.status).toBe(201);

    const secondRes = await handleProject(
      new Request(`${BASE_URL}/api/projects`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(secondPayload),
      }),
    );

    expect([200, 201]).toContain(secondRes.status);
    const updated = (await secondRes.json()) as any;

    expect(updated.slug).toBe(slug);
    expect(updated.title).toBe("두 번째 제목");
    expect(updated.contentJson).toEqual(secondPayload.contentJson);
  });
});
