/**
 * 文件库树服务集成测试(真实 Postgres)。
 * 覆盖:root 创建规则、父级 EDIT+ 创建、D8(404/403)、D11 toggle 冻结、
 * D12 move(环拒绝/目标授权/并发对移)、D14 兄弟名冲突、D15 祖先删除整支隐藏、
 * D17 breadcrumb 占位、provisioning 状态机、审计落库。
 * 运行前提:本地 PG(paradigm:paradigm@127.0.0.1:5432/cph_hub_test)且已 migrate。
 */
import { beforeEach, describe, expect, it } from "vitest";
import { prisma, resetDb, DEFAULT_ORG_ID } from "./helpers.js";
import {
  createNode,
  renameNode,
  moveNode,
  softDeleteNode,
  breadcrumb,
  getEffectiveRole,
  listChildren,
  type FileLibActor,
  type TreeServiceDeps,
} from "../../src/database/filelib/treeService.js";
import { createStaticGroupResolver } from "../../src/database/filelib/groupResolver.js";
import { createInMemoryVersionStore } from "../../src/database/filelib/versionStore.js";
import { FileLibError } from "../../src/database/filelib/model.js";

const ADMIN: FileLibActor = { userId: "u_admin", isWebsiteAdmin: true };
const ALICE: FileLibActor = { userId: "u_alice", isWebsiteAdmin: false };
const BOB: FileLibActor = { userId: "u_bob", isWebsiteAdmin: false };

function deps(): TreeServiceDeps {
  return {
    prisma,
    groupResolver: createStaticGroupResolver({ u_bob: ["g_physics"] }),
    versionStore: createInMemoryVersionStore(),
    organizationId: DEFAULT_ORG_ID,
    storageRoot: "/tmp/filelib-test",
  };
}

async function seedUsers(): Promise<void> {
  for (const [id, openId] of [["u_admin", "ou_admin"], ["u_alice", "ou_alice"], ["u_bob", "ou_bob"]] as const) {
    await prisma.user.create({ data: { id, feishuOpenId: openId, displayName: id } });
  }
}

beforeEach(async () => {
  await resetDb();
  await seedUsers();
});

describe("treeService · 创建规则", () => {
  it("root 仅网站管理员可建;创建者自动 MANAGE", async () => {
    await expect(createNode(deps(), ALICE, { parentId: null, kind: "FOLDER", name: "根" }))
      .rejects.toMatchObject({ statusCode: 403 });
    const root = await createNode(deps(), ADMIN, { parentId: null, kind: "FOLDER", name: "物理" });
    expect(await getEffectiveRole(deps(), ADMIN, root.id)).toBe("MANAGE");
  });

  it("非 root:父级无权限 404、仅 VIEW 403、EDIT+ 可建;项目下禁止子节点", async () => {
    const root = await createNode(deps(), ADMIN, { parentId: null, kind: "FOLDER", name: "物理" });
    await expect(createNode(deps(), BOB, { parentId: root.id, kind: "FOLDER", name: "必修一" }))
      .rejects.toMatchObject({ statusCode: 404 });
    await createNode(deps(), ADMIN, {
      parentId: null, kind: "FOLDER", name: "化学",
      grants: [{ principalType: "USER", principalId: "u_alice", role: "VIEW" }],
    });
    await expect(createNode(deps(), ALICE, { parentId: (await listChildren(deps(), ALICE, null))[0]!.id, kind: "FOLDER", name: "x" }))
      .rejects.toMatchObject({ statusCode: 403 });
    const child = await createNode(deps(), ADMIN, { parentId: root.id, kind: "FOLDER", name: "必修一" });
    const project = await createNode(deps(), ADMIN, { parentId: child.id, kind: "PROJECT", name: "TH-141" });
    expect(project.provisionStatus).toBe("READY"); // provisioning 状态机:mock init 后 READY
    expect(project.storageDir).toContain(project.id);
    await expect(createNode(deps(), ADMIN, { parentId: project.id, kind: "FOLDER", name: "非法" }))
      .rejects.toMatchObject({ statusCode: 400, code: "invalid_parent" });
  });

  it("D14:活跃兄弟名大小写不敏感唯一 → 409", async () => {
    await createNode(deps(), ADMIN, { parentId: null, kind: "FOLDER", name: "Physics" });
    await expect(createNode(deps(), ADMIN, { parentId: null, kind: "FOLDER", name: "physics" }))
      .rejects.toMatchObject({ statusCode: 409, code: "name_conflict" });
  });
});

describe("treeService · D11 独立权限开关", () => {
  it("关闭时项目级非创建者 grant 冻结,创建者仍 MANAGE", async () => {
    const project = await createNode(deps(), ADMIN, {
      parentId: null, kind: "PROJECT", name: "TH-141",
      grants: [{ principalType: "USER", principalId: "u_alice", role: "EDIT" }],
    });
    await expect(getEffectiveRole(deps(), ALICE, project.id))
      .rejects.toMatchObject({ statusCode: 404 }); // 冻结 = 无权限 = D8 不可见
    expect(await getEffectiveRole(deps(), ADMIN, project.id)).toBe("MANAGE");
    await prisma.fileLibProjectSettings.update({
      where: { nodeId: project.id },
      data: { independentPermissionsEnabled: true },
    });
    expect(await getEffectiveRole(deps(), ALICE, project.id)).toBe("EDIT"); // 恢复
  });
});

describe("treeService · rename / move / delete", () => {
  it("rename 需 MANAGE;兄弟名冲突 409", async () => {
    const root = await createNode(deps(), ADMIN, { parentId: null, kind: "FOLDER", name: "物理" });
    await createNode(deps(), ADMIN, { parentId: root.id, kind: "FOLDER", name: "必修一" });
    const other = await createNode(deps(), ADMIN, { parentId: root.id, kind: "FOLDER", name: "必修二" });
    await expect(renameNode(deps(), ADMIN, other.id, "必修一"))
      .rejects.toMatchObject({ statusCode: 409 });
    await expect(renameNode(deps(), BOB, other.id, "改名"))
      .rejects.toMatchObject({ statusCode: 404 });
    expect((await renameNode(deps(), ADMIN, other.id, "选修")).name).toBe("选修");
  });

  it("move:拒绝移入自己的子树;目标父需 EDIT+;Group 授权链路生效", async () => {
    const root = await createNode(deps(), ADMIN, { parentId: null, kind: "FOLDER", name: "物理" });
    const a = await createNode(deps(), ADMIN, { parentId: root.id, kind: "FOLDER", name: "A" });
    const b = await createNode(deps(), ADMIN, { parentId: a.id, kind: "FOLDER", name: "B" });
    await expect(moveNode(deps(), ADMIN, a.id, b.id))
      .rejects.toMatchObject({ statusCode: 400, code: "move_into_own_subtree" });

    // bob 在 g_physics 组;组在 root 上有 EDIT → bob 可把 B 移到 root(D12:本节点 MANAGE + 目标父 EDIT)
    await createNode(deps(), ADMIN, {
      parentId: null, kind: "FOLDER", name: "共享区",
      grants: [{ principalType: "GROUP", principalId: "g_physics", role: "EDIT" }],
    });
    const shared = (await listChildren(deps(), ADMIN, null)).find((n) => n.name === "共享区")!;
    await expect(moveNode(deps(), BOB, b.id, shared.id))
      .rejects.toMatchObject({ statusCode: 404 }); // bob 对 B 无 MANAGE → 404
    await prisma.fileLibGrant.create({
      data: { organizationId: DEFAULT_ORG_ID, nodeId: b.id, principalType: "GROUP", principalId: "g_physics", role: "MANAGE" },
    });
    const moved = await moveNode(deps(), BOB, b.id, shared.id);
    expect(moved.parentId).toBe(shared.id);
    const sharedRow = await prisma.fileLibNode.findUnique({ where: { id: shared.id } });
    expect(moved.pathIds).toBe(`${sharedRow!.pathIds}/${b.id}`);
  });

  it("move 到 root 需网站管理员", async () => {
    const root = await createNode(deps(), ADMIN, { parentId: null, kind: "FOLDER", name: "物理" });
    const a = await createNode(deps(), ADMIN, { parentId: root.id, kind: "FOLDER", name: "A" });
    await createNode(deps(), ADMIN, {
      parentId: null, kind: "FOLDER", name: "给爱丽丝",
      grants: [{ principalType: "USER", principalId: "u_alice", role: "MANAGE" }],
    });
    await expect(moveNode(deps(), ALICE, a.id, null))
      .rejects.toMatchObject({ statusCode: 404 }); // ALICE 对 a 无权限
    await expect(moveNode(deps(), ADMIN, a.id, null)).resolves.toMatchObject({ parentId: null });
  });

  it("D15:祖先删除 → 整支后代不可见(404)", async () => {
    const root = await createNode(deps(), ADMIN, { parentId: null, kind: "FOLDER", name: "物理" });
    const a = await createNode(deps(), ADMIN, { parentId: root.id, kind: "FOLDER", name: "A" });
    await softDeleteNode(deps(), ADMIN, root.id);
    await expect(getEffectiveRole(deps(), ADMIN, a.id)).rejects.toMatchObject({ statusCode: 404 });
    await expect(renameNode(deps(), ADMIN, a.id, "B")).rejects.toMatchObject({ statusCode: 404 });
  });
});

describe("treeService · D17 breadcrumb", () => {
  it("无 View 的祖先只给占位,不泄露名字", async () => {
    const root = await createNode(deps(), ADMIN, { parentId: null, kind: "FOLDER", name: "物理" });
    const a = await createNode(deps(), ADMIN, { parentId: root.id, kind: "FOLDER", name: "A" });
    await prisma.fileLibGrant.create({
      data: { organizationId: DEFAULT_ORG_ID, nodeId: a.id, principalType: "USER", principalId: "u_alice", role: "VIEW" },
    });
    const crumbs = await breadcrumb(deps(), ALICE, a.id);
    expect(crumbs).toHaveLength(2);
    expect(crumbs[0]).toMatchObject({ id: null, name: null }); // root 对 ALICE 不可见
    expect(crumbs[1]).toMatchObject({ id: a.id, name: "A" });
  });
});

describe("treeService · 审计落库(C3)", () => {
  it("创建/改名/移动/删除均写 AuditEntry", async () => {
    const root = await createNode(deps(), ADMIN, { parentId: null, kind: "FOLDER", name: "物理" });
    await renameNode(deps(), ADMIN, root.id, "物理学");
    await softDeleteNode(deps(), ADMIN, root.id);
    const actions = (await prisma.auditEntry.findMany({
      where: { organizationId: DEFAULT_ORG_ID },
      select: { action: true },
      orderBy: { createdAt: "asc" },
    })).map((e) => e.action);
    expect(actions).toEqual(["folder.create", "folder.rename", "folder.delete"]);
  });
});
