/**
 * 文件库 HTTP 端点集成测试(真实 Postgres + Fastify inject)。
 * 覆盖:401 门禁、D8(404/403)、8.1 授权矩阵、文件冲突流(409+审计)、导出状态机。
 * 运行前提:本地 PG 且已 migrate(同 filelib-tree.test.ts)。
 */
import { afterAll, beforeEach, describe, expect, it } from "vitest";
import Fastify, { type FastifyInstance } from "fastify";
import fastifyCookie from "@fastify/cookie";
import { prisma, resetDb, DEFAULT_ORG_ID } from "./helpers.js";
import { signSession, SESSION_COOKIE_NAME } from "../../src/admin/auth/session.js";
import { registerFileLibRoutes } from "../../src/database/routes/filelibRoutes.js";
import { registerFileRoutes } from "../../src/database/routes/fileRoutes.js";
import { createStaticGroupResolver } from "../../src/database/filelib/groupResolver.js";
import { createInMemoryVersionStore } from "../../src/database/filelib/versionStore.js";
import { createManifestStubAdapter } from "../../src/database/filelib/exportService.js";
import type { FileLibRouteDeps } from "../../src/database/filelib/routeShared.js";

const SECRET = "filelib-test-secret";

let app: FastifyInstance;
let deps: FileLibRouteDeps;

function cookie(userId: string, openId: string): string {
  return `${SESSION_COOKIE_NAME}=${signSession({ userId, feishuOpenId: openId }, SECRET)}`;
}
const ADMIN_COOKIE = () => cookie("u_admin", "ou_admin");
const ALICE_COOKIE = () => cookie("u_alice", "ou_alice");
const BOB_COOKIE = () => cookie("u_bob", "ou_bob");

async function seedUsers(): Promise<void> {
  await prisma.user.create({ data: { id: "u_admin", feishuOpenId: "ou_admin", displayName: "Admin" } });
  await prisma.user.create({ data: { id: "u_alice", feishuOpenId: "ou_alice", displayName: "Alice" } });
  await prisma.user.create({ data: { id: "u_bob", feishuOpenId: "ou_bob", displayName: "Bob" } });
  await prisma.organizationMembership.create({
    data: { organizationId: DEFAULT_ORG_ID, userId: "u_admin", role: "OWNER" },
  });
  await prisma.organizationMembership.create({
    data: { organizationId: DEFAULT_ORG_ID, userId: "u_alice", role: "MEMBER" },
  });
  await prisma.organizationMembership.create({
    data: { organizationId: DEFAULT_ORG_ID, userId: "u_bob", role: "MEMBER" },
  });
}

beforeEach(async () => {
  await resetDb();
  await seedUsers();
  const versionStore = createInMemoryVersionStore();
  deps = {
    prisma,
    sessionSecret: SECRET,
    organizationId: DEFAULT_ORG_ID,
    storageRoot: "/tmp/filelib-it",
    groupResolver: createStaticGroupResolver({}),
    versionStore,
    exportAdapters: [createManifestStubAdapter(versionStore)],
  };
  app = Fastify({ logger: false });
  await app.register(fastifyCookie);
  await registerFileLibRoutes(app, deps);
  await registerFileRoutes(app, deps);
  await app.ready();
});

afterAll(async () => {
  await app?.close();
});

async function createRoot(cookieHeader: string, name = "物理"): Promise<string> {
  const res = await app.inject({
    method: "POST", url: "/database/api/nodes",
    headers: { cookie: cookieHeader },
    payload: { parentId: null, kind: "FOLDER", name },
  });
  expect(res.statusCode).toBe(201);
  return res.json().node.id as string;
}

describe("filelib http · 门禁与 D8", () => {
  it("无 session → 401", async () => {
    const res = await app.inject({ method: "GET", url: "/database/api/nodes" });
    expect(res.statusCode).toBe(401);
  });

  it("/me:OWNER → isWebsiteAdmin=true;MEMBER → false", async () => {
    const admin = await app.inject({ method: "GET", url: "/database/api/me", headers: { cookie: ADMIN_COOKIE() } });
    expect(admin.json().isWebsiteAdmin).toBe(true);
    const alice = await app.inject({ method: "GET", url: "/database/api/me", headers: { cookie: ALICE_COOKIE() } });
    expect(alice.json().isWebsiteAdmin).toBe(false);
  });

  it("root 创建:MEMBER 403;无权限节点 GET 404、VIEW 越权 DELETE 403", async () => {
    const forbidden = await app.inject({
      method: "POST", url: "/database/api/nodes",
      headers: { cookie: ALICE_COOKIE() },
      payload: { parentId: null, kind: "FOLDER", name: "越权" },
    });
    expect(forbidden.statusCode).toBe(403);

    const rootId = await createRoot(ADMIN_COOKIE());
    const notFound = await app.inject({ method: "GET", url: `/database/api/nodes/${rootId}`, headers: { cookie: BOB_COOKIE() } });
    expect(notFound.statusCode).toBe(404);

    await app.inject({
      method: "PUT", url: `/database/api/nodes/${rootId}/grants`,
      headers: { cookie: ADMIN_COOKIE() },
      payload: { grants: [{ principalType: "USER", principalId: "u_alice", role: "VIEW" }] },
    });
    const visible = await app.inject({ method: "GET", url: `/database/api/nodes/${rootId}`, headers: { cookie: ALICE_COOKIE() } });
    expect(visible.statusCode).toBe(200);
    expect(visible.json().node.role).toBe("VIEW");
    const denied = await app.inject({ method: "DELETE", url: `/database/api/nodes/${rootId}`, headers: { cookie: ALICE_COOKIE() } });
    expect(denied.statusCode).toBe(403);
  });
});

describe("filelib http · 8.1 授权矩阵", () => {
  it("非创建者的 MANAGE 授 MANAGE → 403;创建者可授;creator grant 不可收回", async () => {
    const rootId = await createRoot(ADMIN_COOKIE());
    // admin(创建者)授 alice MANAGE → 允许
    const put1 = await app.inject({
      method: "PUT", url: `/database/api/nodes/${rootId}/grants`,
      headers: { cookie: ADMIN_COOKIE() },
      payload: { grants: [{ principalType: "USER", principalId: "u_alice", role: "MANAGE" }] },
    });
    expect(put1.statusCode).toBe(200);
    // alice(MANAGE 但非创建者)授 bob MANAGE → 403
    const put2 = await app.inject({
      method: "PUT", url: `/database/api/nodes/${rootId}/grants`,
      headers: { cookie: ALICE_COOKIE() },
      payload: { grants: [{ principalType: "USER", principalId: "u_bob", role: "MANAGE" }] },
    });
    expect(put2.statusCode).toBe(403);
    expect(put2.json().error.code).toBe("only_creator_can_grant_manage");
    // alice 授 bob EDIT → 允许
    const put3 = await app.inject({
      method: "PUT", url: `/database/api/nodes/${rootId}/grants`,
      headers: { cookie: ALICE_COOKIE() },
      payload: { grants: [{ principalType: "USER", principalId: "u_bob", role: "EDIT" }] },
    });
    expect(put3.statusCode).toBe(200);
    // creator grant 不可收回
    const list = await app.inject({ method: "GET", url: `/database/api/nodes/${rootId}/grants`, headers: { cookie: ADMIN_COOKIE() } });
    const creatorGrant = list.json().grants.find((g: { isCreatorGrant: boolean }) => g.isCreatorGrant);
    const revoke = await app.inject({
      method: "DELETE", url: `/database/api/nodes/${rootId}/grants/${creatorGrant.id}`,
      headers: { cookie: ADMIN_COOKIE() },
    });
    expect(revoke.statusCode).toBe(403);
    expect(revoke.json().error.code).toBe("cannot_touch_creator");
  });
});

describe("filelib http · 文件冲突流", () => {
  it("上传→读→提交→409 冲突→conflict 审计落库", async () => {
    const res = await app.inject({
      method: "POST", url: "/database/api/nodes",
      headers: { cookie: ADMIN_COOKIE() },
      payload: { parentId: null, kind: "PROJECT", name: "TH-141" },
    });
    const projectId = res.json().node.id as string;

    const up = await app.inject({
      method: "PUT", url: `/database/api/projects/${projectId}/file`,
      headers: { cookie: ADMIN_COOKIE() },
      payload: { path: "docs/a.md", content: "第一版" },
    });
    expect(up.statusCode).toBe(201);

    const read = await app.inject({
      method: "GET", url: `/database/api/projects/${projectId}/file?path=docs/a.md`,
      headers: { cookie: ADMIN_COOKIE() },
    });
    expect(read.json().content).toBe("第一版");
    const v1 = read.json().version as string;

    const commit = await app.inject({
      method: "POST", url: `/database/api/projects/${projectId}/file/commits`,
      headers: { cookie: ADMIN_COOKIE() },
      payload: { path: "docs/a.md", baseVersion: v1, content: "第二版" },
    });
    expect(commit.statusCode).toBe(200);

    const conflict = await app.inject({
      method: "POST", url: `/database/api/projects/${projectId}/file/commits`,
      headers: { cookie: ADMIN_COOKIE() },
      payload: { path: "docs/a.md", baseVersion: v1, content: "幽灵版" },
    });
    expect(conflict.statusCode).toBe(409);
    expect(conflict.json().error.currentVersion).toBe(commit.json().version);

    const audit = await prisma.auditEntry.findFirst({
      where: { organizationId: DEFAULT_ORG_ID, action: "file.conflict_detected" },
    });
    expect(audit).not.toBeNull();
  });
});

describe("filelib http · 导出状态机", () => {
  it("提交 → 轮询 DONE → 下载 manifest", async () => {
    const res = await app.inject({
      method: "POST", url: "/database/api/nodes",
      headers: { cookie: ADMIN_COOKIE() },
      payload: { parentId: null, kind: "PROJECT", name: "导出源" },
    });
    const projectId = res.json().node.id as string;
    const submit = await app.inject({
      method: "POST", url: `/database/api/projects/${projectId}/exports`,
      headers: { cookie: ADMIN_COOKIE() },
      payload: { target: "manifest" },
    });
    expect(submit.statusCode).toBe(202);
    const jobId = submit.json().jobId as string;

    let job: { status: string } = { status: "QUEUED" };
    for (let i = 0; i < 50 && job.status !== "DONE" && job.status !== "FAILED"; i += 1) {
      await new Promise((r) => setTimeout(r, 50));
      const poll = await app.inject({ method: "GET", url: `/database/api/exports/${jobId}`, headers: { cookie: ADMIN_COOKIE() } });
      job = poll.json();
    }
    expect(job.status).toBe("DONE");

    const download = await app.inject({ method: "GET", url: `/database/api/exports/${jobId}/download`, headers: { cookie: ADMIN_COOKIE() } });
    expect(download.statusCode).toBe(200);
    expect(download.body).toContain("manifest");
  });
});
