import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterAll, beforeEach, describe, expect, it } from "vitest";
import { OrganizationAgentConfiguration } from "../../src/agent/configuration.js";
import { DEFAULT_ORG_ID, prisma, resetDb, seedTestOrganization } from "./helpers.js";

describe("Organization Agent configuration management", () => {
  let root: string;
  let configuration: OrganizationAgentConfiguration;

  beforeEach(async () => {
    await resetDb();
    root = await mkdtemp(join(tmpdir(), "cph-agent-config-"));
    configuration = new OrganizationAgentConfiguration(prisma, join(root, "store"));
  });

  afterAll(async () => {
    await prisma.$disconnect();
  });

  it("installs versioned skills and selects them as part of a dynamic role bundle", async () => {
    const typst = await makeSkill(root, "typst");
    const outline = await makeSkill(root, "outline");
    await configuration.installSkill({ organizationId: DEFAULT_ORG_ID, sourceDir: typst, version: "0.15.0" });
    await configuration.installSkill({ organizationId: DEFAULT_ORG_ID, sourceDir: outline, version: "1" });
    await configuration.upsertRole({
      organizationId: DEFAULT_ORG_ID,
      roleId: "draft",
      label: "课程草稿",
      defaultModel: "anthropic/claude-sonnet-5",
      systemPrompt: "write carefully",
      tools: ["read_file", "write_file", "cph_build"],
      sortOrder: 10,
    });
    await prisma.project.create({
      data: { id: "project-a", organizationId: DEFAULT_ORG_ID, name: "A", workspaceDir: "/tmp/a" },
    });
    await prisma.agentSession.create({
      data: {
        id: "session-old-role-config",
        projectId: "project-a",
        provider: "openrouter",
        roleId: "draft",
        model: "anthropic/claude-sonnet-5",
        metadata: {},
      },
    });
    await configuration.setRoleSkills({
      organizationId: DEFAULT_ORG_ID,
      roleId: "draft",
      skillNames: ["outline", "typst"],
    });

    const role = await prisma.organizationAgentRole.findUniqueOrThrow({
      where: { organizationId_roleId: { organizationId: DEFAULT_ORG_ID, roleId: "draft" } },
      include: { skillBindings: { orderBy: { sortOrder: "asc" }, include: { skill: true } } },
    });
    expect(role).toMatchObject({ label: "课程草稿", systemPrompt: "write carefully" });
    expect(role.tools).toEqual(["read_file", "write_file", "cph_build"]);
    expect(role.skillBindings.map((binding) => binding.skill.name)).toEqual(["outline", "typst"]);
    await expect(prisma.agentSession.findUniqueOrThrow({ where: { id: "session-old-role-config" } }))
      .resolves.toMatchObject({
        archivedAt: expect.any(Date),
        metadata: expect.objectContaining({ userResumable: false }),
      });
  });

  it("rejects unknown, disabled and cross-Organization skills", async () => {
    await seedTestOrganization("org_other", "other");
    const typst = await makeSkill(root, "typst");
    await configuration.installSkill({ organizationId: "org_other", sourceDir: typst, version: "1" });
    await configuration.upsertRole({
      organizationId: DEFAULT_ORG_ID,
      roleId: "draft",
      label: "Draft",
      tools: [],
    });

    await expect(configuration.setRoleSkills({
      organizationId: DEFAULT_ORG_ID,
      roleId: "draft",
      skillNames: ["typst"],
    })).rejects.toThrow("active skills not found in organization");
  });

  it("switches the Organization default role atomically", async () => {
    await configuration.upsertRole({
      organizationId: DEFAULT_ORG_ID,
      roleId: "review",
      label: "审校",
      tools: ["read_file"],
      isDefault: true,
    });

    await expect(prisma.organizationAgentRole.findMany({
      where: { organizationId: DEFAULT_ORG_ID, isDefault: true, disabledAt: null },
      select: { roleId: true },
    })).resolves.toEqual([{ roleId: "review" }]);
    await expect(configuration.upsertRole({
      organizationId: DEFAULT_ORG_ID,
      roleId: "review",
      label: "审校",
      isDefault: false,
    })).rejects.toThrow("cannot unset the active default role without selecting a replacement");
  });

  it("rejects a zero-default committed state at the database boundary", async () => {
    await expect(prisma.organizationAgentRole.updateMany({
      where: { organizationId: DEFAULT_ORG_ID, isDefault: true, disabledAt: null },
      data: { isDefault: false },
    })).rejects.toThrow("must have exactly one active default Agent role");
  });

  it("rejects creating an Organization without its default role in the same transaction", async () => {
    await expect(prisma.organization.create({
      data: { id: "org_without_role", slug: "without-role", name: "Without Role" },
    })).rejects.toThrow("must have exactly one active default Agent role");
  });

  async function makeSkill(parent: string, name: string): Promise<string> {
    const source = join(parent, "sources", name);
    await mkdir(source, { recursive: true });
    await writeFile(join(source, "SKILL.md"), `---\nname: ${name}\ndescription: ${name} skill\n---\n# ${name}\n`);
    return source;
  }
});
