import { BlueprintTarget, FieldType, SystemRole } from "@capakraken/shared";
import { describe, expect, it, vi } from "vitest";
import { blueprintRouter } from "../router/blueprint.js";
import { createCallerFactory } from "../trpc.js";

const createCaller = createCallerFactory(blueprintRouter);

function createPlanningCaller(db: Record<string, unknown>) {
  return createCaller({
    session: {
      user: { email: "planning@example.com", name: "Planning", image: null },
      expires: "2099-01-01T00:00:00.000Z",
    },
    db: db as never,
    dbUser: {
      id: "user_planning",
      systemRole: SystemRole.MANAGER,
      permissionOverrides: null,
    },
    permissions: new Set(["view:planning"]),
  });
}

function createAdminCaller(db: Record<string, unknown>) {
  return createCaller({
    session: {
      user: { email: "admin@example.com", name: "Admin", image: null },
      expires: "2099-01-01T00:00:00.000Z",
    },
    db: db as never,
    dbUser: {
      id: "user_admin",
      systemRole: SystemRole.ADMIN,
      permissionOverrides: null,
    },
  });
}

function sampleBlueprint(overrides: Record<string, unknown> = {}) {
  return {
    id: "bp_1",
    name: "Consulting Blueprint",
    target: BlueprintTarget.PROJECT,
    description: "Default setup",
    fieldDefs: [],
    defaults: {},
    validationRules: [],
    rolePresets: [],
    isActive: true,
    isGlobal: false,
    createdAt: new Date(),
    updatedAt: new Date(),
    ...overrides,
  };
}

describe("blueprint router", () => {
  it("lists active blueprints with the expected filters", async () => {
    const findMany = vi.fn().mockResolvedValue([sampleBlueprint()]);

    const caller = createPlanningCaller({
      blueprint: { findMany },
    });
    const result = await caller.list({ target: BlueprintTarget.PROJECT });

    expect(findMany).toHaveBeenCalledWith({
      where: {
        target: BlueprintTarget.PROJECT,
        isActive: true,
      },
      orderBy: { name: "asc" },
    });
    expect(result).toHaveLength(1);
  });

  it("resolves a blueprint by identifier through the protected router query", async () => {
    const findUnique = vi.fn().mockResolvedValue(null);
    const findFirst = vi.fn().mockResolvedValueOnce(null).mockResolvedValueOnce({
      id: "bp_1",
      name: "Consulting Blueprint",
      target: BlueprintTarget.PROJECT,
      isActive: true,
    });

    const caller = createPlanningCaller({
      blueprint: { findUnique, findFirst },
    });
    const result = await caller.resolveByIdentifier({ identifier: " consulting " });

    expect(findUnique).toHaveBeenCalledWith({
      where: { id: "consulting" },
      select: {
        id: true,
        name: true,
        target: true,
        isActive: true,
      },
    });
    expect(findFirst).toHaveBeenNthCalledWith(2, {
      where: { name: { contains: "consulting", mode: "insensitive" } },
      select: {
        id: true,
        name: true,
        target: true,
        isActive: true,
      },
    });
    expect(result.name).toBe("Consulting Blueprint");
  });

  it("creates a blueprint and writes an audit entry", async () => {
    const create = vi.fn().mockResolvedValue(sampleBlueprint());
    const auditCreate = vi.fn().mockResolvedValue({});

    const caller = createAdminCaller({
      blueprint: { create },
      auditLog: { create: auditCreate },
    });
    const result = await caller.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(auditCreate).toHaveBeenCalledTimes(1);
    expect(result.id).toBe("bp_1");
  });

  it("updates a blueprint through the router and preserves the before snapshot", async () => {
    const update = vi.fn().mockResolvedValue(
      sampleBlueprint({
        name: "Updated Blueprint",
        description: "Updated setup",
      }),
    );
    const auditCreate = vi.fn().mockResolvedValue({});

    const caller = createAdminCaller({
      blueprint: {
        findUnique: vi.fn().mockResolvedValue(sampleBlueprint()),
        update,
      },
      auditLog: { create: auditCreate },
    });
    const result = await caller.update({
      id: "bp_1",
      data: {
        name: "Updated Blueprint",
        description: "Updated setup",
      },
    });

    expect(update).toHaveBeenCalledWith({
      where: { id: "bp_1" },
      data: {
        name: "Updated Blueprint",
        description: "Updated setup",
      },
    });
    expect(auditCreate).toHaveBeenCalledTimes(1);
    expect(result.name).toBe("Updated Blueprint");
  });

  it("updates role presets with the dedicated mutation payload", async () => {
    const update = vi.fn().mockResolvedValue(
      sampleBlueprint({
        rolePresets: [{ roleId: "role_1" }],
      }),
    );
    const auditCreate = vi.fn().mockResolvedValue({});

    const caller = createAdminCaller({
      blueprint: {
        findUnique: vi.fn().mockResolvedValue(sampleBlueprint({ rolePresets: [] })),
        update,
      },
      auditLog: { create: auditCreate },
    });
    const rolePresets = [{ roleId: "role_1", allocation: 0.5 }];
    const result = await caller.updateRolePresets({
      id: "bp_1",
      rolePresets,
    });

    expect(update).toHaveBeenCalledWith({
      where: { id: "bp_1" },
      data: { rolePresets },
    });
    expect(auditCreate).toHaveBeenCalledWith(
      expect.objectContaining({
        data: expect.objectContaining({
          summary: "Updated role presets",
        }),
      }),
    );
    expect(result.rolePresets).toEqual([{ roleId: "role_1" }]);
  });

  it("soft deletes blueprints in batch and audits each deleted blueprint", async () => {
    const update = vi
      .fn()
      .mockResolvedValueOnce(sampleBlueprint({ id: "bp_1" }))
      .mockResolvedValueOnce(sampleBlueprint({ id: "bp_2", name: "Animation Blueprint" }));
    const auditCreate = vi.fn().mockResolvedValue({});
    const transaction = vi
      .fn()
      .mockImplementation(async (operations: Promise<unknown>[]) => Promise.all(operations));

    const caller = createAdminCaller({
      blueprint: { update },
      auditLog: { create: auditCreate },
      $transaction: transaction,
    });
    const result = await caller.batchDelete({ 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(auditCreate).toHaveBeenCalledTimes(2);
    expect(result).toEqual({ count: 2 });
  });

  it("expands global field definitions with blueprint metadata", 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 caller = createPlanningCaller({
      blueprint: { findMany },
    });
    const result = await caller.getGlobalFieldDefs({ 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",
      }),
    ]);
  });
});
