import { beforeEach, describe, expect, it, vi } from "vitest";
import { PermissionKey, SystemRole, type PermissionKey as PermissionKeyValue } from "@capakraken/shared";
import { apiRateLimiter } from "../middleware/rate-limit.js";
import {
  ASSISTANT_CONFIRMATION_PREFIX,
  canExecuteMutationTool,
  clearPendingAssistantApproval,
  consumePendingAssistantApproval,
  createPendingAssistantApproval,
  getAvailableAssistantTools,
  listPendingAssistantApprovals,
  peekPendingAssistantApproval,
  resetAssistantApprovalStorageWarningStateForTests,
  selectAssistantToolsForRequest,
} from "../router/assistant.js";
import { TOOL_DEFINITIONS } from "../router/assistant-tools.js";
import { logger } from "../lib/logger.js";

function getToolNames(
  permissions: PermissionKeyValue[],
  userRole: SystemRole = SystemRole.ADMIN,
) {
  return getAvailableAssistantTools(new Set(permissions), userRole).map((tool) => tool.function.name);
}

function getSelectedToolNames(
  permissions: PermissionKeyValue[],
  messages: Array<{ role: "user" | "assistant"; content: string }>,
  userRole: SystemRole = SystemRole.ADMIN,
  pageContext?: string,
) {
  return selectAssistantToolsForRequest(
    getAvailableAssistantTools(new Set(permissions), userRole),
    messages,
    pageContext,
  ).map((tool) => tool.function.name);
}

const TEST_USER_ID = "assistant-test-user";
const TEST_CONVERSATION_ID = "assistant-test-conversation";

function createApprovalStoreMock() {
  const records = new Map<string, {
    id: string;
    userId: string;
    conversationId: string;
    toolName: string;
    toolArguments: string;
    summary: string;
    status: "PENDING" | "APPROVED" | "CANCELLED" | "EXPIRED";
    approvedAt: Date | null;
    cancelledAt: Date | null;
    createdAt: Date;
    expiresAt: Date;
    updatedAt: Date;
  }>();

  return {
    assistantApproval: {
      findFirst: vi.fn(async ({
        where,
        orderBy,
      }: {
        where: {
          id?: string;
          userId?: string;
          conversationId?: string;
          status?: "PENDING" | "APPROVED" | "CANCELLED" | "EXPIRED";
        };
        orderBy?: { createdAt: "desc" | "asc" };
      }) => {
        const matches = [...records.values()]
          .filter((record) => (
            (!where.id || record.id === where.id)
            && (!where.userId || record.userId === where.userId)
            && (!where.conversationId || record.conversationId === where.conversationId)
            && (!where.status || record.status === where.status)
          ))
          .sort((a, b) => (
            orderBy?.createdAt === "asc"
              ? a.createdAt.getTime() - b.createdAt.getTime()
              : b.createdAt.getTime() - a.createdAt.getTime()
          ));
        return matches[0] ?? null;
      }),
      findMany: vi.fn(async ({
        where,
        orderBy,
      }: {
        where: {
          userId?: string;
          conversationId?: string;
          status?: "PENDING" | "APPROVED" | "CANCELLED" | "EXPIRED";
          expiresAt?: { lte?: Date; gt?: Date };
        };
        orderBy?: { createdAt: "desc" | "asc" };
      }) => (
        [...records.values()]
          .filter((record) => (
            (!where.userId || record.userId === where.userId)
            && (!where.conversationId || record.conversationId === where.conversationId)
            && (!where.status || record.status === where.status)
            && (!where.expiresAt?.lte || record.expiresAt <= where.expiresAt.lte)
            && (!where.expiresAt?.gt || record.expiresAt > where.expiresAt.gt)
          ))
          .sort((a, b) => (
            orderBy?.createdAt === "asc"
              ? a.createdAt.getTime() - b.createdAt.getTime()
              : b.createdAt.getTime() - a.createdAt.getTime()
          ))
      )),
      create: vi.fn(async ({
        data,
      }: {
        data: {
          userId: string;
          conversationId: string;
          toolName: string;
          toolArguments: string;
          summary: string;
          createdAt: Date;
          expiresAt: Date;
        };
      }) => {
        const record = {
          id: `approval-${records.size + 1}`,
          ...data,
          status: "PENDING" as const,
          approvedAt: null,
          cancelledAt: null,
          updatedAt: data.createdAt,
        };
        records.set(record.id, record);
        return record;
      }),
      updateMany: vi.fn(async ({
        where,
        data,
      }: {
        where: {
          id?: string;
          userId?: string;
          conversationId?: string;
          status?: "PENDING" | "APPROVED" | "CANCELLED" | "EXPIRED";
          expiresAt?: { lte?: Date; gt?: Date };
        };
        data: Partial<{
          status: "APPROVED" | "CANCELLED" | "EXPIRED";
          cancelledAt: Date;
          approvedAt: Date;
        }>;
      }) => {
        let count = 0;
        for (const [id, record] of records.entries()) {
          if (where.id && record.id !== where.id) continue;
          if (where.userId && record.userId !== where.userId) continue;
          if (where.conversationId && record.conversationId !== where.conversationId) continue;
          if (where.status && record.status !== where.status) continue;
          if (where.expiresAt?.lte && record.expiresAt > where.expiresAt.lte) continue;
          if (where.expiresAt?.gt && record.expiresAt <= where.expiresAt.gt) continue;
          records.set(id, {
            ...record,
            ...data,
            updatedAt: new Date(),
          });
          count += 1;
        }
        return { count };
      }),
      update: vi.fn(async ({
        where,
        data,
      }: {
        where: { id: string };
        data: {
          status: "APPROVED";
          approvedAt: Date;
        };
      }) => {
        const record = records.get(where.id);
        if (!record) throw new Error("Record not found");
        const next = {
          ...record,
          ...data,
          updatedAt: new Date(),
        };
        records.set(where.id, next);
        return next;
      }),
    },
  };
}

function createMissingApprovalTableError() {
  return Object.assign(
    new Error("The table `public.assistant_approvals` does not exist in the current database."),
    {
      code: "P2021",
      meta: { table: "public.assistant_approvals" },
    },
  );
}

describe("assistant router tool gating", () => {
  let approvalStore = createApprovalStoreMock();

  beforeEach(async () => {
    approvalStore = createApprovalStoreMock();
    await apiRateLimiter.reset();
    resetAssistantApprovalStorageWarningStateForTests();
  });

  it("hides advanced tools unless the dedicated assistant permission is granted", () => {
    const withoutAdvanced = getToolNames([
      PermissionKey.VIEW_PLANNING,
      PermissionKey.VIEW_COSTS,
    ]);
    const withAdvanced = getToolNames([
      PermissionKey.VIEW_PLANNING,
      PermissionKey.VIEW_COSTS,
      PermissionKey.USE_ASSISTANT_ADVANCED_TOOLS,
    ]);

    expect(withoutAdvanced).not.toContain("find_best_project_resource");
    expect(withAdvanced).toContain("find_best_project_resource");
    expect(withAdvanced).toContain("get_chargeability_report");
    expect(withAdvanced).toContain("get_resource_computation_graph");
    expect(withAdvanced).toContain("get_project_computation_graph");
  });

  it("keeps user self-service tools available to plain authenticated users", () => {
    const userNames = getToolNames([], SystemRole.USER);

    expect(userNames).toContain("get_current_user");
    expect(userNames).toContain("get_dashboard_layout");
    expect(userNames).toContain("save_dashboard_layout");
    expect(userNames).toContain("get_favorite_project_ids");
    expect(userNames).toContain("toggle_favorite_project");
    expect(userNames).toContain("get_column_preferences");
    expect(userNames).toContain("set_column_preferences");
    expect(userNames).toContain("get_mfa_status");
    expect(userNames).toContain("list_notifications");
    expect(userNames).toContain("get_unread_notification_count");
    expect(userNames).toContain("list_tasks");
    expect(userNames).toContain("get_task_counts");
    expect(userNames).toContain("create_reminder");
    expect(userNames).toContain("list_reminders");
    expect(userNames).toContain("update_reminder");
    expect(userNames).toContain("delete_reminder");
  });

  it("keeps admin-only user tools hidden from non-admin roles", () => {
    const adminNames = getToolNames([], SystemRole.ADMIN);
    const managerNames = getToolNames([], SystemRole.MANAGER);
    const userNames = getToolNames([], SystemRole.USER);

    expect(adminNames).toContain("list_users");
    expect(adminNames).toContain("get_active_user_count");
    expect(adminNames).toContain("create_user");
    expect(adminNames).toContain("set_user_password");
    expect(adminNames).toContain("update_user_role");
    expect(adminNames).toContain("update_user_name");
    expect(adminNames).toContain("link_user_resource");
    expect(adminNames).toContain("auto_link_users_by_email");
    expect(adminNames).toContain("set_user_permissions");
    expect(adminNames).toContain("reset_user_permissions");
    expect(adminNames).toContain("get_effective_user_permissions");
    expect(adminNames).toContain("disable_user_totp");

    expect(managerNames).not.toContain("list_users");
    expect(managerNames).not.toContain("create_user");
    expect(managerNames).not.toContain("set_user_permissions");
    expect(managerNames).not.toContain("disable_user_totp");
    expect(userNames).not.toContain("list_users");
    expect(userNames).not.toContain("get_active_user_count");
    expect(userNames).not.toContain("create_user");
    expect(userNames).not.toContain("set_user_password");
    expect(userNames).not.toContain("update_user_role");
    expect(userNames).not.toContain("update_user_name");
    expect(userNames).not.toContain("link_user_resource");
    expect(userNames).not.toContain("auto_link_users_by_email");
    expect(userNames).not.toContain("set_user_permissions");
    expect(userNames).not.toContain("reset_user_permissions");
    expect(userNames).not.toContain("get_effective_user_permissions");
    expect(userNames).not.toContain("disable_user_totp");
  });

  it("caps the OpenAI tool payload to 128 definitions even for fully privileged admins", () => {
    const allPermissions = Object.values(PermissionKey);
    const selectedNames = getSelectedToolNames(
      allPermissions,
      [{ role: "user", content: "Bitte gib mir einen Überblick über das System." }],
      SystemRole.ADMIN,
    );

    expect(selectedNames.length).toBeLessThanOrEqual(128);
    expect(selectedNames).toContain("get_current_user");
    expect(selectedNames).toContain("search_resources");
    expect(selectedNames).toContain("search_projects");
  });

  it("prioritizes holiday and resource tools for German holiday questions", () => {
    const allPermissions = Object.values(PermissionKey);
    const selectedNames = getSelectedToolNames(
      allPermissions,
      [{ role: "user", content: "Kannst du mir alle Feiertage nennen, die Peter Parker in 2026 zustehen?" }],
      SystemRole.ADMIN,
    );

    expect(selectedNames.length).toBeLessThanOrEqual(128);
    expect(selectedNames).toContain("search_resources");
    expect(selectedNames).toContain("get_resource");
    expect(selectedNames).toContain("get_resource_holidays");
    expect(selectedNames).toContain("list_holidays_by_region");
    expect(selectedNames).toContain("list_holiday_calendars");
  });

  it("keeps assignable users and manager notification lifecycle tools behind manager/admin role", () => {
    const managerNames = getToolNames([], SystemRole.MANAGER);
    const adminNames = getToolNames([], SystemRole.ADMIN);
    const userNames = getToolNames([], SystemRole.USER);

    expect(managerNames).toContain("list_assignable_users");
    expect(managerNames).toContain("create_notification");
    expect(managerNames).toContain("create_task_for_user");
    expect(managerNames).toContain("assign_task");
    expect(managerNames).toContain("send_broadcast");
    expect(managerNames).toContain("list_broadcasts");
    expect(managerNames).toContain("get_broadcast_detail");
    expect(adminNames).toContain("list_assignable_users");
    expect(adminNames).toContain("create_task_for_user");
    expect(adminNames).toContain("send_broadcast");
    expect(userNames).not.toContain("list_assignable_users");
    expect(userNames).not.toContain("create_notification");
    expect(userNames).not.toContain("create_task_for_user");
    expect(userNames).not.toContain("assign_task");
    expect(userNames).not.toContain("send_broadcast");
    expect(userNames).not.toContain("list_broadcasts");
    expect(userNames).not.toContain("get_broadcast_detail");
  });

  it("continues to hide cost-aware advanced tools when viewCosts is missing", () => {
    const names = getToolNames([PermissionKey.USE_ASSISTANT_ADVANCED_TOOLS]);

    expect(names).not.toContain("find_best_project_resource");
    expect(names).not.toContain("get_chargeability_report");
    expect(names).not.toContain("get_resource_computation_graph");
    expect(names).not.toContain("get_project_computation_graph");
  });

  it("keeps controller-grade readmodels hidden from plain users while allowing controller roles", () => {
    const controllerNames = getToolNames([
      PermissionKey.VIEW_COSTS,
      PermissionKey.USE_ASSISTANT_ADVANCED_TOOLS,
    ], SystemRole.CONTROLLER);
    const userNames = getToolNames([
      PermissionKey.VIEW_COSTS,
      PermissionKey.USE_ASSISTANT_ADVANCED_TOOLS,
    ], SystemRole.USER);

    expect(controllerNames).toContain("query_change_history");
    expect(controllerNames).toContain("get_entity_timeline");
    expect(controllerNames).toContain("search_by_skill");
    expect(controllerNames).toContain("list_comments");
    expect(controllerNames).toContain("create_comment");
    expect(controllerNames).toContain("resolve_comment");
    expect(controllerNames).toContain("export_resources_csv");
    expect(controllerNames).toContain("export_projects_csv");
    expect(controllerNames).toContain("list_audit_log_entries");
    expect(controllerNames).toContain("get_audit_log_entry");
    expect(controllerNames).toContain("get_audit_log_timeline");
    expect(controllerNames).toContain("get_audit_activity_summary");
    expect(controllerNames).toContain("get_chargeability_report");
    expect(controllerNames).toContain("get_resource_computation_graph");
    expect(controllerNames).toContain("get_project_computation_graph");
    expect(userNames).not.toContain("query_change_history");
    expect(userNames).not.toContain("get_entity_timeline");
    expect(userNames).not.toContain("search_by_skill");
    expect(userNames).not.toContain("list_comments");
    expect(userNames).not.toContain("create_comment");
    expect(userNames).not.toContain("resolve_comment");
    expect(userNames).not.toContain("export_resources_csv");
    expect(userNames).not.toContain("export_projects_csv");
    expect(userNames).not.toContain("list_audit_log_entries");
    expect(userNames).not.toContain("get_audit_log_entry");
    expect(userNames).not.toContain("get_audit_log_timeline");
    expect(userNames).not.toContain("get_audit_activity_summary");
    expect(userNames).not.toContain("get_chargeability_report");
    expect(userNames).not.toContain("get_resource_computation_graph");
    expect(userNames).not.toContain("get_project_computation_graph");
  });

  it("keeps planning read tools behind the explicit planning permission", () => {
    const userWithoutPlanning = getToolNames([], SystemRole.USER);
    const userWithPlanning = getToolNames([PermissionKey.VIEW_PLANNING], SystemRole.USER);

    expect(userWithoutPlanning).not.toContain("list_allocations");
    expect(userWithoutPlanning).not.toContain("list_demands");
    expect(userWithoutPlanning).not.toContain("list_blueprints");
    expect(userWithoutPlanning).not.toContain("get_blueprint");
    expect(userWithoutPlanning).not.toContain("list_clients");
    expect(userWithoutPlanning).not.toContain("list_roles");
    expect(userWithoutPlanning).not.toContain("list_management_levels");
    expect(userWithoutPlanning).not.toContain("list_utilization_categories");
    expect(userWithoutPlanning).not.toContain("check_resource_availability");
    expect(userWithoutPlanning).not.toContain("find_capacity");
    expect(userWithoutPlanning).not.toContain("get_staffing_suggestions");
    expect(userWithoutPlanning).not.toContain("find_best_project_resource");
    expect(userWithPlanning).toContain("list_allocations");
    expect(userWithPlanning).toContain("list_demands");
    expect(userWithPlanning).toContain("list_blueprints");
    expect(userWithPlanning).toContain("get_blueprint");
    expect(userWithPlanning).toContain("list_clients");
    expect(userWithPlanning).toContain("list_roles");
    expect(userWithPlanning).toContain("list_management_levels");
    expect(userWithPlanning).toContain("list_utilization_categories");
    expect(userWithPlanning).toContain("check_resource_availability");
    expect(userWithPlanning).toContain("find_capacity");
    expect(userWithPlanning).not.toContain("get_staffing_suggestions");
    expect(userWithPlanning).not.toContain("find_best_project_resource");
  });

  it("keeps cost-aware staffing assistant tools behind cost and advanced gates", () => {
    const planningOnly = getToolNames([PermissionKey.VIEW_PLANNING], SystemRole.USER);
    const planningAndCosts = getToolNames([
      PermissionKey.VIEW_PLANNING,
      PermissionKey.VIEW_COSTS,
    ], SystemRole.USER);
    const planningCostsAndAdvanced = getToolNames([
      PermissionKey.VIEW_PLANNING,
      PermissionKey.VIEW_COSTS,
      PermissionKey.USE_ASSISTANT_ADVANCED_TOOLS,
    ], SystemRole.USER);

    expect(planningOnly).not.toContain("get_staffing_suggestions");
    expect(planningOnly).not.toContain("find_best_project_resource");
    expect(planningAndCosts).toContain("get_staffing_suggestions");
    expect(planningAndCosts).not.toContain("find_best_project_resource");
    expect(planningCostsAndAdvanced).toContain("get_staffing_suggestions");
    expect(planningCostsAndAdvanced).toContain("find_best_project_resource");
  });

  it("keeps controller-only project and dashboard reads hidden from plain users", () => {
    const controllerNames = getToolNames([
      PermissionKey.VIEW_COSTS,
      PermissionKey.USE_ASSISTANT_ADVANCED_TOOLS,
    ], SystemRole.CONTROLLER);
    const userNames = getToolNames([
      PermissionKey.VIEW_COSTS,
      PermissionKey.USE_ASSISTANT_ADVANCED_TOOLS,
      PermissionKey.VIEW_PLANNING,
    ], SystemRole.USER);

    expect(controllerNames).toContain("search_projects");
    expect(controllerNames).toContain("get_project");
    expect(controllerNames).toContain("get_statistics");
    expect(controllerNames).toContain("get_dashboard_detail");
    expect(controllerNames).toContain("get_skill_gaps");
    expect(controllerNames).toContain("get_project_health");
    expect(controllerNames).toContain("get_budget_forecast");
    expect(userNames).not.toContain("search_projects");
    expect(userNames).not.toContain("get_project");
    expect(userNames).not.toContain("get_statistics");
    expect(userNames).not.toContain("get_dashboard_detail");
    expect(userNames).not.toContain("get_skill_gaps");
    expect(userNames).not.toContain("get_project_health");
    expect(userNames).not.toContain("get_budget_forecast");
  });

  it("requires both controller role and advanced assistant access for timeline detail tools", () => {
    const controllerWithAdvanced = getToolNames([
      PermissionKey.USE_ASSISTANT_ADVANCED_TOOLS,
    ], SystemRole.CONTROLLER);
    const controllerWithoutAdvanced = getToolNames([], SystemRole.CONTROLLER);
    const userWithAdvanced = getToolNames([
      PermissionKey.USE_ASSISTANT_ADVANCED_TOOLS,
    ], SystemRole.USER);

    expect(controllerWithAdvanced).toContain("get_timeline_entries_view");
    expect(controllerWithAdvanced).toContain("get_timeline_holiday_overlays");
    expect(controllerWithAdvanced).toContain("get_project_timeline_context");
    expect(controllerWithAdvanced).toContain("preview_project_shift");
    expect(controllerWithoutAdvanced).not.toContain("get_timeline_entries_view");
    expect(controllerWithoutAdvanced).not.toContain("get_timeline_holiday_overlays");
    expect(controllerWithoutAdvanced).not.toContain("get_project_timeline_context");
    expect(controllerWithoutAdvanced).not.toContain("preview_project_shift");
    expect(userWithAdvanced).not.toContain("get_timeline_entries_view");
    expect(userWithAdvanced).not.toContain("get_timeline_holiday_overlays");
    expect(userWithAdvanced).not.toContain("get_project_timeline_context");
    expect(userWithAdvanced).not.toContain("preview_project_shift");
  });

  it("keeps timeline write parity tools behind manager/admin role, manageAllocations, and advanced assistant access", () => {
    const managerNames = getToolNames([
      PermissionKey.MANAGE_ALLOCATIONS,
      PermissionKey.USE_ASSISTANT_ADVANCED_TOOLS,
    ], SystemRole.MANAGER);
    const userNames = getToolNames([
      PermissionKey.MANAGE_ALLOCATIONS,
      PermissionKey.USE_ASSISTANT_ADVANCED_TOOLS,
    ], SystemRole.USER);
    const missingAdvancedNames = getToolNames([
      PermissionKey.MANAGE_ALLOCATIONS,
    ], SystemRole.MANAGER);

    expect(managerNames).toContain("update_timeline_allocation_inline");
    expect(managerNames).toContain("apply_timeline_project_shift");
    expect(managerNames).toContain("quick_assign_timeline_resource");
    expect(managerNames).toContain("batch_quick_assign_timeline_resources");
    expect(managerNames).toContain("batch_shift_timeline_allocations");
    expect(userNames).not.toContain("update_timeline_allocation_inline");
    expect(userNames).not.toContain("apply_timeline_project_shift");
    expect(userNames).not.toContain("quick_assign_timeline_resource");
    expect(userNames).not.toContain("batch_quick_assign_timeline_resources");
    expect(userNames).not.toContain("batch_shift_timeline_allocations");
    expect(missingAdvancedNames).not.toContain("update_timeline_allocation_inline");
    expect(missingAdvancedNames).not.toContain("quick_assign_timeline_resource");
  });

  it("keeps estimate lifecycle mutations behind manager/admin role and their router permissions", () => {
    const managerProjectNames = getToolNames([PermissionKey.MANAGE_PROJECTS], SystemRole.MANAGER);
    const managerAllocationNames = getToolNames([PermissionKey.MANAGE_ALLOCATIONS], SystemRole.MANAGER);
    const userProjectNames = getToolNames([PermissionKey.MANAGE_PROJECTS], SystemRole.USER);

    expect(managerProjectNames).toContain("create_estimate");
    expect(managerProjectNames).toContain("clone_estimate");
    expect(managerProjectNames).toContain("update_estimate_draft");
    expect(managerProjectNames).toContain("submit_estimate_version");
    expect(managerProjectNames).toContain("approve_estimate_version");
    expect(managerProjectNames).toContain("create_estimate_revision");
    expect(managerProjectNames).toContain("create_estimate_export");
    expect(managerProjectNames).toContain("generate_estimate_weekly_phasing");
    expect(managerProjectNames).toContain("update_estimate_commercial_terms");
    expect(managerProjectNames).not.toContain("create_estimate_planning_handoff");
    expect(managerAllocationNames).toContain("create_estimate_planning_handoff");
    expect(managerAllocationNames).not.toContain("create_estimate");
    expect(userProjectNames).not.toContain("create_estimate");
    expect(userProjectNames).not.toContain("clone_estimate");
    expect(userProjectNames).not.toContain("update_estimate_draft");
    expect(userProjectNames).not.toContain("submit_estimate_version");
    expect(userProjectNames).not.toContain("approve_estimate_version");
    expect(userProjectNames).not.toContain("create_estimate_revision");
    expect(userProjectNames).not.toContain("create_estimate_export");
    expect(userProjectNames).not.toContain("generate_estimate_weekly_phasing");
    expect(userProjectNames).not.toContain("update_estimate_commercial_terms");
    expect(userProjectNames).not.toContain("create_estimate_planning_handoff");
  });

  it("keeps estimate read tools aligned to controller/manager/admin visibility and cost requirements", () => {
    const controllerNames = getToolNames([PermissionKey.VIEW_COSTS], SystemRole.CONTROLLER);
    const controllerWithoutCosts = getToolNames([], SystemRole.CONTROLLER);
    const managerNames = getToolNames([PermissionKey.VIEW_COSTS], SystemRole.MANAGER);
    const managerWithoutCosts = getToolNames([], SystemRole.MANAGER);
    const userNames = getToolNames([PermissionKey.VIEW_COSTS], SystemRole.USER);

    expect(controllerNames).toContain("search_estimates");
    expect(controllerNames).toContain("get_estimate_detail");
    expect(controllerNames).toContain("list_estimate_versions");
    expect(controllerNames).toContain("get_estimate_version_snapshot");
    expect(controllerNames).toContain("get_estimate_weekly_phasing");
    expect(controllerNames).toContain("get_estimate_commercial_terms");
    expect(controllerWithoutCosts).toContain("search_estimates");
    expect(controllerWithoutCosts).not.toContain("get_estimate_detail");
    expect(controllerWithoutCosts).toContain("list_estimate_versions");
    expect(controllerWithoutCosts).not.toContain("get_estimate_version_snapshot");
    expect(controllerWithoutCosts).toContain("get_estimate_weekly_phasing");
    expect(controllerWithoutCosts).toContain("get_estimate_commercial_terms");
    expect(managerNames).toContain("search_estimates");
    expect(managerNames).toContain("get_estimate_detail");
    expect(managerNames).toContain("list_estimate_versions");
    expect(managerNames).toContain("get_estimate_version_snapshot");
    expect(managerNames).toContain("get_estimate_weekly_phasing");
    expect(managerNames).toContain("get_estimate_commercial_terms");
    expect(managerWithoutCosts).toContain("search_estimates");
    expect(managerWithoutCosts).toContain("list_estimate_versions");
    expect(managerWithoutCosts).not.toContain("get_estimate_version_snapshot");
    expect(userNames).not.toContain("search_estimates");
    expect(userNames).not.toContain("get_estimate_detail");
    expect(userNames).not.toContain("list_estimate_versions");
    expect(userNames).not.toContain("get_estimate_version_snapshot");
    expect(userNames).not.toContain("get_estimate_weekly_phasing");
    expect(userNames).not.toContain("get_estimate_commercial_terms");
  });

  it("keeps import/dispo parity tools aligned to router roles and permissions", () => {
    const managerNames = getToolNames([PermissionKey.IMPORT_DATA], SystemRole.MANAGER);
    const controllerNames = getToolNames([], SystemRole.CONTROLLER);
    const adminNames = getToolNames([], SystemRole.ADMIN);
    const userNames = getToolNames([PermissionKey.IMPORT_DATA], SystemRole.USER);

    expect(managerNames).toContain("import_csv_data");
    expect(controllerNames).toContain("export_resources_csv");
    expect(controllerNames).toContain("export_projects_csv");
    expect(adminNames).toContain("list_dispo_import_batches");
    expect(adminNames).toContain("get_dispo_import_batch");
    expect(adminNames).toContain("stage_dispo_import_batch");
    expect(adminNames).toContain("validate_dispo_import_batch");
    expect(adminNames).toContain("cancel_dispo_import_batch");
    expect(adminNames).toContain("list_dispo_staged_resources");
    expect(adminNames).toContain("list_dispo_staged_projects");
    expect(adminNames).toContain("list_dispo_staged_assignments");
    expect(adminNames).toContain("list_dispo_staged_vacations");
    expect(adminNames).toContain("list_dispo_staged_unresolved_records");
    expect(adminNames).toContain("resolve_dispo_staged_record");
    expect(adminNames).toContain("commit_dispo_import_batch");
    expect(userNames).not.toContain("import_csv_data");
    expect(userNames).not.toContain("export_resources_csv");
    expect(userNames).not.toContain("export_projects_csv");
    expect(userNames).not.toContain("list_dispo_import_batches");
    expect(userNames).not.toContain("get_dispo_import_batch");
    expect(userNames).not.toContain("stage_dispo_import_batch");
    expect(userNames).not.toContain("validate_dispo_import_batch");
    expect(userNames).not.toContain("list_dispo_staged_resources");
    expect(userNames).not.toContain("commit_dispo_import_batch");
  });

  it("keeps settings and webhook admin tools hidden while preserving protected parity tools", () => {
    const adminNames = getToolNames([], SystemRole.ADMIN);
    const userNames = getToolNames([], SystemRole.USER);

    const managerNames = getToolNames([], SystemRole.MANAGER);
    expect(adminNames).toContain("get_system_settings");
    expect(adminNames).toContain("update_system_settings");
    expect(adminNames).toContain("test_ai_connection");
    expect(adminNames).toContain("test_smtp_connection");
    expect(adminNames).toContain("clear_stored_runtime_secrets");
    expect(adminNames).toContain("test_gemini_connection");
    expect(adminNames).toContain("list_system_role_configs");
    expect(adminNames).toContain("update_system_role_config");
    expect(adminNames).toContain("list_webhooks");
    expect(adminNames).toContain("get_webhook");
    expect(adminNames).toContain("create_webhook");
    expect(adminNames).toContain("update_webhook");
    expect(adminNames).toContain("delete_webhook");
    expect(adminNames).toContain("test_webhook");
    expect(adminNames).toContain("get_ai_configured");

    expect(managerNames).not.toContain("get_system_settings");
    expect(managerNames).not.toContain("update_system_settings");
    expect(managerNames).not.toContain("clear_stored_runtime_secrets");
    expect(managerNames).not.toContain("test_ai_connection");
    expect(managerNames).not.toContain("get_ai_configured");
    expect(managerNames).not.toContain("list_system_role_configs");
    expect(managerNames).not.toContain("update_system_role_config");
    expect(managerNames).not.toContain("list_webhooks");
    expect(managerNames).not.toContain("create_webhook");

    expect(userNames).not.toContain("get_system_settings");
    expect(userNames).not.toContain("update_system_settings");
    expect(userNames).not.toContain("test_ai_connection");
    expect(userNames).not.toContain("get_ai_configured");
    expect(userNames).not.toContain("clear_stored_runtime_secrets");
    expect(userNames).not.toContain("list_system_role_configs");
    expect(userNames).not.toContain("update_system_role_config");
    expect(userNames).not.toContain("list_webhooks");
    expect(userNames).not.toContain("create_webhook");
  });

  it("keeps holiday calendar catalog tools admin-only while leaving preview available", () => {
    const adminNames = getToolNames([], SystemRole.ADMIN);
    const managerNames = getToolNames([], SystemRole.MANAGER);
    const userNames = getToolNames([], SystemRole.USER);

    expect(adminNames).toContain("list_holiday_calendars");
    expect(adminNames).toContain("get_holiday_calendar");
    expect(adminNames).toContain("preview_resolved_holiday_calendar");
    expect(adminNames).toContain("create_holiday_calendar");
    expect(managerNames).not.toContain("list_holiday_calendars");
    expect(managerNames).not.toContain("get_holiday_calendar");
    expect(managerNames).toContain("preview_resolved_holiday_calendar");
    expect(userNames).not.toContain("list_holiday_calendars");
    expect(userNames).not.toContain("get_holiday_calendar");
    expect(userNames).toContain("preview_resolved_holiday_calendar");
    expect(managerNames).not.toContain("create_holiday_calendar");
    expect(managerNames).not.toContain("update_holiday_calendar");
    expect(managerNames).not.toContain("delete_holiday_calendar");
    expect(managerNames).not.toContain("create_holiday_calendar_entry");
    expect(managerNames).not.toContain("update_holiday_calendar_entry");
    expect(managerNames).not.toContain("delete_holiday_calendar_entry");
  });

  it("keeps country and metro-city mutation tools admin-only while leaving read tools available", () => {
    const adminNames = getToolNames([], SystemRole.ADMIN);
    const managerNames = getToolNames([], SystemRole.MANAGER);
    const userNames = getToolNames([], SystemRole.USER);
    const userWithResourceOverview = getToolNames([PermissionKey.VIEW_ALL_RESOURCES], SystemRole.USER);
    const userWithManagedResources = getToolNames([PermissionKey.MANAGE_RESOURCES], SystemRole.USER);

    expect(adminNames).toContain("list_countries");
    expect(adminNames).toContain("create_country");
    expect(adminNames).toContain("update_country");
    expect(adminNames).toContain("create_metro_city");
    expect(adminNames).toContain("update_metro_city");
    expect(adminNames).toContain("delete_metro_city");
    expect(managerNames).toContain("list_countries");
    expect(managerNames).not.toContain("create_country");
    expect(managerNames).not.toContain("update_country");
    expect(managerNames).not.toContain("create_metro_city");
    expect(managerNames).not.toContain("update_metro_city");
    expect(managerNames).not.toContain("delete_metro_city");
    expect(userNames).not.toContain("search_resources");
    expect(userNames).not.toContain("get_country");
    expect(userNames).not.toContain("list_org_units");
    expect(userWithResourceOverview).toContain("search_resources");
    expect(userWithResourceOverview).toContain("get_country");
    expect(userWithResourceOverview).toContain("list_org_units");
    expect(userWithManagedResources).toContain("search_resources");
    expect(userWithManagedResources).toContain("get_country");
    expect(userWithManagedResources).toContain("list_org_units");
  });

  it("blocks mutation tools until the user confirms a prior assistant summary", () => {
    expect(canExecuteMutationTool([
      { role: "user", content: "Lege bitte ein Projekt an" },
    ], "create_project")).toBe(false);

    expect(canExecuteMutationTool([
      { role: "user", content: "Lege bitte ein Projekt an" },
      { role: "assistant", content: "Ich werde jetzt das Projekt erstellen." },
      { role: "user", content: "ja" },
    ], "create_project")).toBe(false);

    expect(canExecuteMutationTool([
      { role: "user", content: "Lege bitte ein Projekt an" },
      { role: "assistant", content: `${ASSISTANT_CONFIRMATION_PREFIX} Ich werde das Projekt \"Apollo\" in DRAFT anlegen. Bitte bestätigen.` },
      { role: "user", content: "ja, bitte ausführen" },
    ], "create_project")).toBe(true);
  });

  it("requires a matching server-side pending approval for mutation execution when provided", async () => {
    const pendingApproval = await createPendingAssistantApproval(
      approvalStore,
      TEST_USER_ID,
      TEST_CONVERSATION_ID,
      "create_project",
      JSON.stringify({ name: "Apollo", status: "DRAFT" }),
    );

    expect(canExecuteMutationTool([
      { role: "assistant", content: `${ASSISTANT_CONFIRMATION_PREFIX} create project (name=Apollo). Bitte bestätigen.` },
      { role: "user", content: "ja" },
    ], "create_project", pendingApproval)).toBe(true);

    expect(canExecuteMutationTool([
      { role: "assistant", content: `${ASSISTANT_CONFIRMATION_PREFIX} create project (name=Apollo). Bitte bestätigen.` },
      { role: "user", content: "ja" },
    ], "delete_project", pendingApproval)).toBe(false);
  });

  it("stores and consumes pending approvals independently from chat text", async () => {
    const approval = await createPendingAssistantApproval(
      approvalStore,
      TEST_USER_ID,
      TEST_CONVERSATION_ID,
      "create_project",
      JSON.stringify({ name: "Gelddruckmaschine", status: "DRAFT" }),
    );

    await expect(peekPendingAssistantApproval(approvalStore, TEST_USER_ID, TEST_CONVERSATION_ID)).resolves.toMatchObject({
      id: approval.id,
      toolName: "create_project",
      summary: expect.stringContaining("create project"),
    });

    await expect(consumePendingAssistantApproval(approvalStore, TEST_USER_ID, TEST_CONVERSATION_ID)).resolves.toMatchObject({
      id: approval.id,
      toolName: "create_project",
    });
    await expect(peekPendingAssistantApproval(approvalStore, TEST_USER_ID, TEST_CONVERSATION_ID)).resolves.toBeNull();
  });

  it("expires stale pending approvals", async () => {
    await createPendingAssistantApproval(
      approvalStore,
      TEST_USER_ID,
      TEST_CONVERSATION_ID,
      "create_project",
      JSON.stringify({ name: "Apollo" }),
      { ttlMs: -1 },
    );

    await expect(peekPendingAssistantApproval(approvalStore, TEST_USER_ID, TEST_CONVERSATION_ID)).resolves.toBeNull();
  });

  it("clears pending approvals for cancellation semantics", async () => {
    await createPendingAssistantApproval(
      approvalStore,
      TEST_USER_ID,
      TEST_CONVERSATION_ID,
      "create_project",
      JSON.stringify({ name: "Apollo" }),
    );

    await clearPendingAssistantApproval(approvalStore, TEST_USER_ID, TEST_CONVERSATION_ID);

    await expect(peekPendingAssistantApproval(approvalStore, TEST_USER_ID, TEST_CONVERSATION_ID)).resolves.toBeNull();
  });

  it("isolates pending approvals by conversation", async () => {
    const otherConversationId = `${TEST_CONVERSATION_ID}-other`;

    await createPendingAssistantApproval(
      approvalStore,
      TEST_USER_ID,
      TEST_CONVERSATION_ID,
      "create_project",
      JSON.stringify({ name: "Apollo" }),
    );
    await createPendingAssistantApproval(
      approvalStore,
      TEST_USER_ID,
      otherConversationId,
      "create_project",
      JSON.stringify({ name: "Hermes" }),
    );

    await clearPendingAssistantApproval(approvalStore, TEST_USER_ID, TEST_CONVERSATION_ID);

    await expect(peekPendingAssistantApproval(approvalStore, TEST_USER_ID, TEST_CONVERSATION_ID)).resolves.toBeNull();
    await expect(peekPendingAssistantApproval(approvalStore, TEST_USER_ID, otherConversationId)).resolves.toMatchObject({
      toolName: "create_project",
      summary: expect.stringContaining("Hermes"),
    });
  });

  it("lists only still-pending approvals for the current user across conversations", async () => {
    const otherConversationId = `${TEST_CONVERSATION_ID}-other`;

    await createPendingAssistantApproval(
      approvalStore,
      TEST_USER_ID,
      TEST_CONVERSATION_ID,
      "create_project",
      JSON.stringify({ name: "Apollo" }),
    );
    await createPendingAssistantApproval(
      approvalStore,
      TEST_USER_ID,
      otherConversationId,
      "create_project",
      JSON.stringify({ name: "Hermes" }),
    );

    const cancelled = await createPendingAssistantApproval(
      approvalStore,
      TEST_USER_ID,
      `${TEST_CONVERSATION_ID}-cancelled`,
      "create_project",
      JSON.stringify({ name: "Cancelled" }),
    );
    await approvalStore.assistantApproval.updateMany({
      where: { id: cancelled.id, userId: TEST_USER_ID, status: "PENDING" },
      data: { status: "CANCELLED", cancelledAt: new Date() },
    });

    await createPendingAssistantApproval(
      approvalStore,
      "other-user",
      `${TEST_CONVERSATION_ID}-foreign`,
      "create_project",
      JSON.stringify({ name: "Foreign" }),
    );
    await createPendingAssistantApproval(
      approvalStore,
      TEST_USER_ID,
      `${TEST_CONVERSATION_ID}-expired`,
      "create_project",
      JSON.stringify({ name: "Expired" }),
      { ttlMs: -1 },
    );

    const approvals = await listPendingAssistantApprovals(approvalStore, TEST_USER_ID);
    const approvalSummaries = approvals.map((approval) => approval.summary).join(" ");

    expect(approvals).toHaveLength(2);
    expect([...approvals.map((approval) => approval.conversationId)].sort()).toEqual([
      otherConversationId,
      TEST_CONVERSATION_ID,
    ].sort());
    expect(approvals.every((approval) => approval.userId === TEST_USER_ID)).toBe(true);
    expect(approvalSummaries).toContain("Apollo");
    expect(approvalSummaries).toContain("Hermes");
    expect(approvalSummaries).not.toContain("Cancelled");
    expect(approvalSummaries).not.toContain("Expired");
    expect(approvalSummaries).not.toContain("Foreign");
  });

  it("degrades approval reads gracefully when approval storage is missing", async () => {
    const warnSpy = vi.spyOn(logger, "warn").mockImplementation(() => logger);
    const missingTableError = createMissingApprovalTableError();
    const missingStore = {
      assistantApproval: {
        findFirst: vi.fn(async () => {
          throw missingTableError;
        }),
        findMany: vi.fn(async () => {
          throw missingTableError;
        }),
        create: vi.fn(async () => {
          throw missingTableError;
        }),
        updateMany: vi.fn(async () => {
          throw missingTableError;
        }),
      },
    };

    await expect(listPendingAssistantApprovals(missingStore, TEST_USER_ID)).resolves.toEqual([]);
    await expect(peekPendingAssistantApproval(missingStore, TEST_USER_ID, TEST_CONVERSATION_ID)).resolves.toBeNull();
    await expect(consumePendingAssistantApproval(missingStore, TEST_USER_ID, TEST_CONVERSATION_ID)).resolves.toBeNull();
    await expect(clearPendingAssistantApproval(missingStore, TEST_USER_ID, TEST_CONVERSATION_ID)).resolves.toBeUndefined();
    expect(warnSpy).toHaveBeenCalledTimes(1);
  });

  it("returns an explicit error when approval storage is missing for mutation confirmation", async () => {
    const warnSpy = vi.spyOn(logger, "warn").mockImplementation(() => logger);
    const missingTableError = createMissingApprovalTableError();
    const missingStore = {
      assistantApproval: {
        findFirst: vi.fn(async () => {
          throw missingTableError;
        }),
        findMany: vi.fn(async () => {
          throw missingTableError;
        }),
        create: vi.fn(async () => {
          throw missingTableError;
        }),
        updateMany: vi.fn(async () => {
          throw missingTableError;
        }),
      },
    };

    await expect(createPendingAssistantApproval(
      missingStore,
      TEST_USER_ID,
      TEST_CONVERSATION_ID,
      "create_project",
      JSON.stringify({ name: "Apollo" }),
    )).rejects.toThrow("Assistant approval storage is unavailable");
    expect(warnSpy).toHaveBeenCalledTimes(1);
  });

  it("does not require confirmation for read-only assistant tools", () => {
    expect(canExecuteMutationTool([
      { role: "user", content: "Zeig mir meine Notifications" },
    ], "list_notifications")).toBe(true);
  });

  it("keeps assistant tool descriptions aligned with runtime permissions", () => {
    const toolDescriptions = new Map(
      TOOL_DEFINITIONS.map((tool) => [tool.function.name, tool.function.description]),
    );

    expect(toolDescriptions.get("create_estimate")).toContain("manageProjects");
    expect(toolDescriptions.get("create_estimate_planning_handoff")).toContain("manageAllocations");
    expect(toolDescriptions.get("get_estimate_detail")).toContain("Controller/manager/admin");
    expect(toolDescriptions.get("list_estimate_versions")).toContain("Controller/manager/admin");
    expect(toolDescriptions.get("get_estimate_version_snapshot")).toContain("viewCosts");
    expect(toolDescriptions.get("get_estimate_weekly_phasing")).toContain("Controller/manager/admin");
    expect(toolDescriptions.get("get_estimate_commercial_terms")).toContain("Controller/manager/admin");
    expect(toolDescriptions.get("create_vacation")).toContain("authenticated user");
    expect(toolDescriptions.get("approve_vacation")).toContain("Manager or admin role");
    expect(toolDescriptions.get("reject_vacation")).toContain("Manager or admin role");
    expect(toolDescriptions.get("cancel_vacation")).toContain("Users can cancel their own requests");
    expect(toolDescriptions.get("set_entitlement")).toContain("Manager or admin role");
    expect(toolDescriptions.get("create_role")).toContain("manageRoles");
    expect(toolDescriptions.get("update_role")).toContain("manageRoles");
    expect(toolDescriptions.get("delete_role")).toContain("manageRoles");
    expect(toolDescriptions.get("create_org_unit")).toContain("Admin role");
    expect(toolDescriptions.get("update_org_unit")).toContain("Admin role");
    expect(toolDescriptions.get("list_users")).toContain("Admin role");
    expect(toolDescriptions.get("list_assignable_users")).toContain("Manager or admin role");
    expect(toolDescriptions.get("get_current_user")).toContain("authenticated user's own profile");
    expect(toolDescriptions.get("search_resources")).toContain("Resource overview access required");
    expect(toolDescriptions.get("search_by_skill")).toContain("Controller/manager/admin access required");
    expect(toolDescriptions.get("list_comments")).toContain("Currently only estimate comments are enabled");
    expect(toolDescriptions.get("list_comments")).toContain("Controller/manager/admin access required");
    expect(toolDescriptions.get("create_comment")).toContain("Currently only estimate comments are enabled");
    expect(toolDescriptions.get("create_comment")).toContain("Controller/manager/admin access required");
    expect(toolDescriptions.get("resolve_comment")).toContain("Currently only estimate comments are enabled");
    expect(toolDescriptions.get("list_notifications")).toContain("current user");
    expect(toolDescriptions.get("get_unread_notification_count")).toContain("current user");
    expect(toolDescriptions.get("list_tasks")).toContain("current user");
    expect(toolDescriptions.get("get_task_counts")).toContain("current user");
    expect(toolDescriptions.get("create_notification")).toContain("Manager or admin role");
    expect(toolDescriptions.get("create_task_for_user")).toContain("Manager or admin role");
    expect(toolDescriptions.get("send_broadcast")).toContain("Manager or admin role");
    expect(toolDescriptions.get("list_broadcasts")).toContain("Manager or admin role");
    expect(toolDescriptions.get("get_broadcast_detail")).toContain("Manager or admin role");
    expect(toolDescriptions.get("create_client")).toContain("manager or admin role");
    expect(toolDescriptions.get("update_client")).toContain("manager or admin role");
    expect(toolDescriptions.get("create_holiday_calendar")).toContain("Admin role");
    expect(toolDescriptions.get("create_holiday_calendar_entry")).toContain("Admin role");
    expect(toolDescriptions.get("query_change_history")).toContain("Controller/manager/admin");
    expect(toolDescriptions.get("get_entity_timeline")).toContain("Controller/manager/admin");
    expect(toolDescriptions.get("export_resources_csv")).toContain("Controller/manager/admin");
    expect(toolDescriptions.get("export_projects_csv")).toContain("Controller/manager/admin");
    expect(toolDescriptions.get("import_csv_data")).toContain("importData");
    expect(toolDescriptions.get("import_csv_data")).toContain("manager/admin");
    expect(toolDescriptions.get("list_dispo_import_batches")).toContain("Admin role");
    expect(toolDescriptions.get("get_dispo_import_batch")).toContain("Admin role");
    expect(toolDescriptions.get("stage_dispo_import_batch")).toContain("Admin role");
    expect(toolDescriptions.get("validate_dispo_import_batch")).toContain("Admin role");
    expect(toolDescriptions.get("commit_dispo_import_batch")).toContain("Always confirm first");
    expect(toolDescriptions.get("get_system_settings")).toContain("Admin role");
    expect(toolDescriptions.get("update_system_settings")).toContain("Always confirm first");
    expect(toolDescriptions.get("get_ai_configured")).toContain("Admin role");
    expect(toolDescriptions.get("list_system_role_configs")).toContain("Admin role");
    expect(toolDescriptions.get("update_system_settings")).toContain("Runtime secrets must be provisioned");
    expect(toolDescriptions.get("clear_stored_runtime_secrets")).toContain("deployment secret management");
    expect(toolDescriptions.get("update_system_role_config")).toContain("Admin role");
    expect(toolDescriptions.get("list_webhooks")).toContain("Secrets are masked");
    expect(toolDescriptions.get("create_webhook")).toContain("Always confirm first");
    expect(toolDescriptions.get("test_webhook")).toContain("Always confirm first");
    expect(toolDescriptions.get("list_audit_log_entries")).toContain("Controller/manager/admin");
    expect(toolDescriptions.get("get_audit_log_entry")).toContain("Controller/manager/admin");
    expect(toolDescriptions.get("get_audit_log_timeline")).toContain("Controller/manager/admin");
    expect(toolDescriptions.get("get_audit_activity_summary")).toContain("Controller/manager/admin");
    expect(toolDescriptions.get("get_chargeability_report")).toContain("controller/manager/admin");
    expect(toolDescriptions.get("get_chargeability_report")).toContain("viewCosts");
    expect(toolDescriptions.get("get_resource_computation_graph")).toContain("useAssistantAdvancedTools");
    expect(toolDescriptions.get("get_project_computation_graph")).toContain("controller/manager/admin");
    expect(toolDescriptions.get("update_timeline_allocation_inline")).toContain("manager/admin");
    expect(toolDescriptions.get("apply_timeline_project_shift")).toContain("manageAllocations");
    expect(toolDescriptions.get("quick_assign_timeline_resource")).toContain("useAssistantAdvancedTools");
    expect(toolDescriptions.get("batch_quick_assign_timeline_resources")).toContain("manageAllocations");
    expect(toolDescriptions.get("batch_shift_timeline_allocations")).toContain("manager/admin");
  });

  it("aligns assistant tool visibility with router role and permission rules", () => {
    const managerWithRolePermission = getToolNames(
      [PermissionKey.MANAGE_ROLES],
      SystemRole.MANAGER,
    );
    const managerWithoutRolePermission = getToolNames([], SystemRole.MANAGER);

    expect(managerWithRolePermission).toContain("create_role");
    expect(managerWithRolePermission).toContain("update_role");
    expect(managerWithRolePermission).toContain("delete_role");
    expect(managerWithRolePermission).toContain("create_client");
    expect(managerWithRolePermission).toContain("update_client");
    expect(managerWithRolePermission).not.toContain("create_org_unit");
    expect(managerWithRolePermission).not.toContain("update_org_unit");

    expect(managerWithoutRolePermission).not.toContain("create_role");
    expect(managerWithoutRolePermission).not.toContain("update_role");
    expect(managerWithoutRolePermission).not.toContain("delete_role");
    expect(managerWithoutRolePermission).toContain("create_client");
    expect(managerWithoutRolePermission).toContain("update_client");

    const adminWithRolePermission = getToolNames(
      [PermissionKey.MANAGE_ROLES],
      SystemRole.ADMIN,
    );
    expect(adminWithRolePermission).toContain("create_org_unit");
    expect(adminWithRolePermission).toContain("update_org_unit");

    const standardUserTools = getToolNames([], SystemRole.USER);
    expect(standardUserTools).toContain("get_vacation_balance");
    expect(standardUserTools).toContain("create_vacation");
    expect(standardUserTools).toContain("cancel_vacation");
    expect(standardUserTools).not.toContain("approve_vacation");
    expect(standardUserTools).not.toContain("reject_vacation");
    expect(standardUserTools).not.toContain("set_entitlement");

    const managerVacationTools = getToolNames([], SystemRole.MANAGER);
    expect(managerVacationTools).toContain("approve_vacation");
    expect(managerVacationTools).toContain("reject_vacation");
    expect(managerVacationTools).toContain("set_entitlement");
  });

  it("keeps estimate tool parameter enums aligned with the current estimate schema", () => {
    const definitionByName = new Map(
      TOOL_DEFINITIONS.map((tool) => [tool.function.name, tool.function]),
    );

    const createEstimateStatus = (
      definitionByName.get("create_estimate")?.parameters as {
        properties?: Record<string, { enum?: unknown[] }>;
      }
    )?.properties?.status?.enum;
    const updateEstimateStatus = (
      definitionByName.get("update_estimate_draft")?.parameters as {
        properties?: Record<string, { enum?: unknown[] }>;
      }
    )?.properties?.status?.enum;
    const estimateExportFormats = (
      definitionByName.get("create_estimate_export")?.parameters as {
        properties?: Record<string, { enum?: unknown[] }>;
      }
    )?.properties?.format?.enum;

    expect(createEstimateStatus).toEqual(["DRAFT", "IN_REVIEW", "APPROVED", "ARCHIVED"]);
    expect(updateEstimateStatus).toEqual(["DRAFT", "IN_REVIEW", "APPROVED", "ARCHIVED"]);
    expect(estimateExportFormats).toEqual(["XLSX", "CSV", "JSON", "SAP", "MMP"]);
  });
});
