import { BlueprintTarget, FieldType } from "@capakraken/shared";
import { beforeEach, describe, expect, it, vi } from "vitest";

const { createAuditEntry } = vi.hoisted(() => ({
  createAuditEntry: vi.fn(),
}));

vi.mock("../lib/audit.js", () => ({
  createAuditEntry,
}));

import {
  batchDeleteBlueprints,
  createBlueprint,
  getGlobalBlueprintFieldDefs,
  updateBlueprintRolePresets,
} from "../router/blueprint-procedure-support.js";

function createContext(db: Record<string, unknown>) {
  return {
    db: db as never,
    dbUser: { id: "user_admin" } as never,
  };
}

describe("blueprint procedure support", () => {
  beforeEach(() => {
    createAuditEntry.mockReset();
  });

  it("creates a blueprint and audits it", async () => {
    const create = vi.fn().mockResolvedValue({
      id: "bp_1",
      name: "Consulting Blueprint",
      target: BlueprintTarget.PROJECT,
    });

    const result = await createBlueprint(
      createContext({
        blueprint: { create },
      }),
      {
        name: "Consulting Blueprint",
        target: BlueprintTarget.PROJECT,
        description: "Default setup",
        fieldDefs: [],
        defaults: { market: "EU" },
        validationRules: [],
      },
    );

    expect(create).toHaveBeenCalledWith({
      data: {
        name: "Consulting Blueprint",
        target: BlueprintTarget.PROJECT,
        description: "Default setup",
        fieldDefs: [],
        defaults: { market: "EU" },
        validationRules: [],
      },
    });
    expect(result).toEqual({
      id: "bp_1",
      name: "Consulting Blueprint",
      target: BlueprintTarget.PROJECT,
    });
    expect(createAuditEntry).toHaveBeenCalledWith(
      expect.objectContaining({
        entityType: "Blueprint",
        action: "CREATE",
        entityId: "bp_1",
        userId: "user_admin",
      }),
    );
  });

  it("updates only role presets with the dedicated audit summary", async () => {
    const findUnique = vi.fn().mockResolvedValue({
      id: "bp_1",
      name: "Consulting Blueprint",
      rolePresets: [],
    });
    const update = vi.fn().mockResolvedValue({
      id: "bp_1",
      name: "Consulting Blueprint",
      rolePresets: [{ roleId: "role_1", allocation: 0.5 }],
    });

    const rolePresets = [{ roleId: "role_1", allocation: 0.5 }];
    const result = await updateBlueprintRolePresets(
      createContext({
        blueprint: { findUnique, update },
      }),
      {
        id: "bp_1",
        rolePresets,
      },
    );

    expect(findUnique).toHaveBeenCalledWith({
      where: { id: "bp_1" },
    });
    expect(update).toHaveBeenCalledWith({
      where: { id: "bp_1" },
      data: { rolePresets },
    });
    expect(result.rolePresets).toEqual(rolePresets);
    expect(createAuditEntry).toHaveBeenCalledWith(
      expect.objectContaining({
        entityType: "Blueprint",
        summary: "Updated role presets",
        before: { rolePresets: [] },
        after: { rolePresets },
      }),
    );
  });

  it("soft deletes multiple blueprints inside one transaction and audits each record", async () => {
    const update = vi
      .fn()
      .mockResolvedValueOnce({ id: "bp_1", name: "Consulting Blueprint", isActive: false })
      .mockResolvedValueOnce({ id: "bp_2", name: "Animation Blueprint", isActive: false });
    const $transaction = vi
      .fn()
      .mockImplementation(async (operations: Promise<unknown>[]) => Promise.all(operations));

    const result = await batchDeleteBlueprints(
      createContext({
        $transaction,
        blueprint: { update },
      }),
      {
        ids: ["bp_1", "bp_2"],
      },
    );

    expect($transaction).toHaveBeenCalledTimes(1);
    expect(update).toHaveBeenNthCalledWith(1, {
      where: { id: "bp_1" },
      data: { isActive: false },
    });
    expect(update).toHaveBeenNthCalledWith(2, {
      where: { id: "bp_2" },
      data: { isActive: false },
    });
    expect(createAuditEntry).toHaveBeenCalledTimes(2);
    expect(result).toEqual({ count: 2 });
  });

  it("expands global field definitions from active global blueprints", async () => {
    const findMany = vi.fn().mockResolvedValue([
      {
        id: "bp_global",
        name: "Global Project Blueprint",
        fieldDefs: [
          {
            id: "field_market",
            key: "market",
            label: "Market",
            order: 0,
            type: FieldType.TEXT,
            required: false,
          },
        ],
      },
    ]);

    const result = await getGlobalBlueprintFieldDefs(
      createContext({
        blueprint: { findMany },
      }),
      {
        target: BlueprintTarget.PROJECT,
      },
    );

    expect(findMany).toHaveBeenCalledWith({
      where: { target: BlueprintTarget.PROJECT, isGlobal: true, isActive: true },
      select: { id: true, name: true, fieldDefs: true },
    });
    expect(result).toEqual([
      expect.objectContaining({
        blueprintId: "bp_global",
        blueprintName: "Global Project Blueprint",
        key: "market",
      }),
    ]);
  });
});
