import { Injectable } from '@nestjs/common';
import { PrismaService } from '@core/database/prisma/prisma.service';
import {
  IamAiToolGrantNotFoundException,
  IamAiToolGrantRoleExistsException,
  IamAiToolGrantUserExistsException,
  IamAiToolUnknownException,
  IamAiToolLockedCannotRemoveException,
} from '../exceptions/iam.exceptions';
import {
  BatchCreateRoleGrantsDto,
  CreateRoleGrantDto,
  CreateUserGrantDto,
  RoleGrantQueryDto,
  UserGrantQueryDto,
} from './dto';
import {
  AvailableTool,
  STATIC_AVAILABLE_TOOLS,
  AVAILABLE_TOOL_NAMES,
  LOCKED_SET,
  LOCKED_TOOL_NAMES,
} from './available-tools.config';

/**
 * 单条生效工具记录（附带来源）
 */
export interface EffectiveTool {
  toolName: string;
  sources: EffectiveToolSource[];
}

export type EffectiveToolSource =
  | { type: 'role'; roleId: string; roleName: string; grantId: string }
  | { type: 'user'; userId: string; grantId: string; reason: string | null };

/**
 * 反查某工具下的所有生效用户
 */
export interface ToolSubject {
  userId: string;
  userDisplayName: string;
  userEmail: string;
  sources: EffectiveToolSource[];
}

@Injectable()
export class AIToolsService {
  constructor(private prisma: PrismaService) {}

  // ==================== 角色级授权 CRUD ====================

  async listRoleGrants(query: RoleGrantQueryDto) {
    // 不 include role：Prisma 默认 strict relation，孤儿 grant（role 已被并发删除/
    // 测试 cleanup 关 FK 时残留）会触发 "Field role is required, got null" 异常，
    // 让整个 list endpoint 返回 500。如需 role 详情，调用方按 roleId 单独查 /roles/:id。
    // 同模式见 listRoleGrantsAggregated（PR #145）：从 Role 起查，孤儿 grant 自然忽略。
    return this.prisma.aIToolGrant.findMany({
      where: query.roleId ? { roleId: query.roleId } : {},
      orderBy: [{ roleId: 'asc' }, { toolName: 'asc' }],
    });
  }

  async createRoleGrant(dto: CreateRoleGrantDto, actorUserId?: string) {
    await this.ensureToolAvailable(dto.toolName);
    await this.ensureRoleExists(dto.roleId);

    const existing = await this.prisma.aIToolGrant.findUnique({
      where: { roleId_toolName: { roleId: dto.roleId, toolName: dto.toolName } },
    });
    if (existing) {
      throw new IamAiToolGrantRoleExistsException(dto.roleId, dto.toolName);
    }

    return this.prisma.aIToolGrant.create({
      data: {
        roleId: dto.roleId,
        toolName: dto.toolName,
        createdBy: actorUserId,
      },
      include: {
        role: { select: { id: true, name: true, code: true } },
      },
    });
  }

  /**
   * 批量创建角色级授权
   * - 事务内处理：要么全部成功，要么全部回滚
   * - 使用 upsert 语义：已存在的 (roleId, toolName) 不报错，直接跳过
   * - 所有 toolNames 必须在可用工具清单中，任一未知即整体失败
   */
  async batchCreateRoleGrants(
    dto: BatchCreateRoleGrantsDto,
    actorUserId?: string,
  ) {
    await this.ensureRoleExists(dto.roleId);
    for (const tool of dto.toolNames) {
      await this.ensureToolAvailable(tool);
    }

    return this.prisma.$transaction(async (tx) => {
      const created: Array<{ toolName: string; status: 'created' | 'skipped' }> = [];
      for (const toolName of dto.toolNames) {
        const existing = await tx.aIToolGrant.findUnique({
          where: { roleId_toolName: { roleId: dto.roleId, toolName } },
        });
        if (existing) {
          created.push({ toolName, status: 'skipped' });
          continue;
        }
        await tx.aIToolGrant.create({
          data: { roleId: dto.roleId, toolName, createdBy: actorUserId },
        });
        created.push({ toolName, status: 'created' });
      }
      return {
        roleId: dto.roleId,
        results: created,
        createdCount: created.filter((r) => r.status === 'created').length,
        skippedCount: created.filter((r) => r.status === 'skipped').length,
      };
    });
  }

  async deleteRoleGrant(id: string) {
    const existing = await this.prisma.aIToolGrant.findUnique({ where: { id } });
    if (!existing) {
      throw new IamAiToolGrantNotFoundException();
    }
    await this.prisma.aIToolGrant.delete({ where: { id } });
    return { success: true };
  }

  // ==================== 用户级授权 CRUD ====================

  async listUserGrants(query: UserGrantQueryDto) {
    return this.prisma.aIToolGrantUser.findMany({
      where: query.userId ? { userId: query.userId } : {},
      include: {
        user: {
          select: { id: true, username: true, displayName: true, email: true },
        },
      },
      orderBy: [{ userId: 'asc' }, { toolName: 'asc' }],
    });
  }

  async createUserGrant(dto: CreateUserGrantDto, actorUserId?: string) {
    await this.ensureToolAvailable(dto.toolName);
    await this.ensureUserExists(dto.userId);

    const existing = await this.prisma.aIToolGrantUser.findUnique({
      where: { userId_toolName: { userId: dto.userId, toolName: dto.toolName } },
    });
    if (existing) {
      throw new IamAiToolGrantUserExistsException(dto.userId, dto.toolName);
    }

    return this.prisma.aIToolGrantUser.create({
      data: {
        userId: dto.userId,
        toolName: dto.toolName,
        reason: dto.reason,
        createdBy: actorUserId,
      },
      include: {
        user: {
          select: { id: true, username: true, displayName: true, email: true },
        },
      },
    });
  }

  async deleteUserGrant(id: string) {
    const existing = await this.prisma.aIToolGrantUser.findUnique({ where: { id } });
    if (!existing) {
      throw new IamAiToolGrantNotFoundException();
    }
    await this.prisma.aIToolGrantUser.delete({ where: { id } });
    return { success: true };
  }

  // ==================== 查询 ====================

  /**
   * 获取可用工具清单
   * MVP 阶段返回静态配置；OpenClaw 同步脚本落地后应切换到读
   * /srv/apps/{env}/available-tools.json，切换逻辑见 available-tools.config.ts 注释
   */
  getAvailableTools(): AvailableTool[] {
    return STATIC_AVAILABLE_TOOLS;
  }

  /**
   * 查某用户最终能用的工具清单 + 每条工具的来源
   * 合并公式：
   *   最终生效 = (⋃ 用户所有角色的 AIToolGrant) ∪ 用户的 AIToolGrantUser
   * profile 基线不在本接口计算范围内（那是 OpenClaw 侧的事）
   */
  async getUserEffectiveTools(userId: string): Promise<EffectiveTool[]> {
    await this.ensureUserExists(userId);

    // 1. 查用户的所有角色分配（跨组织）
    const userRoles = await this.prisma.userRole.findMany({
      where: { userId },
      include: { role: { select: { id: true, name: true } } },
    });
    const roleIds = userRoles.map((ur) => ur.roleId);
    const roleNameById = new Map(userRoles.map((ur) => [ur.roleId, ur.role.name]));

    // 2. 查这些角色的所有 AI 工具授权（无角色 fallback Employee）
    let roleGrants = roleIds.length
      ? await this.prisma.aIToolGrant.findMany({
          where: { roleId: { in: roleIds } },
        })
      : [];
    if (roleIds.length === 0) {
      const empRole = await this.prisma.role.findFirst({ where: { code: 'Employee' } });
      if (empRole) {
        roleGrants = await this.prisma.aIToolGrant.findMany({ where: { roleId: empRole.id } });
        roleNameById.set(empRole.id, empRole.name);
      }
    }

    // 3. 查用户的直接授权（区分 grant/revoke）
    const userGrantsAll = await this.prisma.aIToolGrantUser.findMany({
      where: { userId },
    });
    const userGrantsAdd = userGrantsAll.filter((g) => g.effect === 'grant');
    const userRevokedTools = new Set(
      userGrantsAll.filter((g) => g.effect === 'revoke').map((g) => g.toolName),
    );

    // 4. 按 toolName 合并，记录来源；排除 revoke
    const byToolName = new Map<string, EffectiveTool>();

    for (const g of roleGrants) {
      if (userRevokedTools.has(g.toolName)) continue; // 用户级 revoke 优先
      const roleName = roleNameById.get(g.roleId) ?? '';
      const entry = byToolName.get(g.toolName) ?? {
        toolName: g.toolName,
        sources: [],
      };
      entry.sources.push({
        type: 'role',
        roleId: g.roleId,
        roleName,
        grantId: g.id,
      });
      byToolName.set(g.toolName, entry);
    }

    for (const g of userGrantsAdd) {
      const entry = byToolName.get(g.toolName) ?? {
        toolName: g.toolName,
        sources: [],
      };
      entry.sources.push({
        type: 'user',
        userId: g.userId,
        grantId: g.id,
        reason: g.reason,
      });
      byToolName.set(g.toolName, entry);
    }

    return Array.from(byToolName.values()).sort((a, b) =>
      a.toolName.localeCompare(b.toolName),
    );
  }

  /**
   * 反查：某个工具下有哪些生效用户 + 来源
   * 用于管理员排查"谁在用这个工具"
   */
  async getToolSubjects(toolName: string): Promise<ToolSubject[]> {
    // 1. 角色级：从 Role 起查（而非 AIToolGrant 起 include role），
    //    避免 Prisma strict relation 因孤儿 grant 抛 "role is required, got null"。
    //    同模式见 listRoleGrantsAggregated（PR #145）。
    const rolesWithGrant = await this.prisma.role.findMany({
      where: { aiToolGrants: { some: { toolName } } },
      select: {
        id: true,
        name: true,
        aiToolGrants: { where: { toolName }, select: { id: true } },
      },
    });
    const roleGrants = rolesWithGrant
      .filter((r) => r.aiToolGrants.length > 0)
      .map((r) => ({
        id: r.aiToolGrants[0].id,
        roleId: r.id,
        role: { id: r.id, name: r.name },
      }));
    const roleIds = roleGrants.map((g) => g.roleId);
    const userRoles = roleIds.length
      ? await this.prisma.userRole.findMany({
          where: { roleId: { in: roleIds } },
          include: {
            user: {
              select: {
                id: true,
                displayName: true,
                email: true,
                deletedAt: true,
              },
            },
          },
        })
      : [];

    // 2. 用户级：直接授权（grant）和显式取消（revoke）
    const userGrantsAll = await this.prisma.aIToolGrantUser.findMany({
      where: { toolName },
      include: {
        user: {
          select: {
            id: true,
            displayName: true,
            email: true,
            deletedAt: true,
          },
        },
      },
    });
    const userGrantsAdd = userGrantsAll.filter((g) => g.effect === 'grant');
    const userRevokedIds = new Set(
      userGrantsAll.filter((g) => g.effect === 'revoke').map((g) => g.userId),
    );

    // 3. 合并、去重、按 userId 聚合来源；排除 revoke 的用户
    const byUserId = new Map<string, ToolSubject>();
    const grantByRoleId = new Map(roleGrants.map((g) => [g.roleId, g]));

    for (const ur of userRoles) {
      if (ur.user.deletedAt) continue;
      if (userRevokedIds.has(ur.userId)) continue; // 用户级 revoke 优先
      const grant = grantByRoleId.get(ur.roleId)!;
      const entry = byUserId.get(ur.userId) ?? {
        userId: ur.userId,
        userDisplayName: ur.user.displayName,
        userEmail: ur.user.email,
        sources: [],
      };
      entry.sources.push({
        type: 'role',
        roleId: grant.roleId,
        roleName: grant.role.name,
        grantId: grant.id,
      });
      byUserId.set(ur.userId, entry);
    }

    for (const ug of userGrantsAdd) {
      if (ug.user.deletedAt) continue;
      const entry = byUserId.get(ug.userId) ?? {
        userId: ug.userId,
        userDisplayName: ug.user.displayName,
        userEmail: ug.user.email,
        sources: [],
      };
      entry.sources.push({
        type: 'user',
        userId: ug.userId,
        grantId: ug.id,
        reason: ug.reason,
      });
      byUserId.set(ug.userId, entry);
    }

    return Array.from(byUserId.values()).sort((a, b) =>
      a.userDisplayName.localeCompare(b.userDisplayName),
    );
  }

  /**
   * 「手动触发同步」接口
   *
   * OpenClaw 同步脚本是跑在服务器 host crontab 上的独立进程（每 5 分钟 pull 一次），
   * Workspace 后端**没有主动推送 / 远程触发**能力。这个接口只是一个信息性响应，
   * 告诉管理员："下次同步会在 5 分钟内自动发生，请耐心等待"。
   *
   * 未来如果要支持"立即触发"，需要 OpenClaw 侧暴露一个 HTTP 端点供 Workspace 回调，
   * 目前不在 MVP 范围。
   */
  triggerSync(): { scheduled: true; message: string; intervalMinutes: number } {
    return {
      scheduled: true,
      message:
        '授权变更将在 5 分钟内由 OpenClaw 同步脚本自动拉取并生效；Workspace 后端不主动推送。',
      intervalMinutes: 5,
    };
  }

  // ==================== 内部辅助 ====================

  private async ensureToolAvailable(toolName: string): Promise<void> {
    const available = this.getAvailableTools();
    if (!available.some((t) => t.name === toolName)) {
      throw new IamAiToolUnknownException(toolName);
    }
  }

  private async ensureRoleExists(roleId: string): Promise<void> {
    const role = await this.prisma.role.findUnique({ where: { id: roleId } });
    if (!role) {
      throw new IamAiToolGrantNotFoundException();
    }
  }

  private async ensureUserExists(userId: string): Promise<void> {
    const user = await this.prisma.user.findFirst({
      where: { id: userId, deletedAt: null },
    });
    if (!user) {
      throw new IamAiToolGrantNotFoundException();
    }
  }

  private ensureAllToolsAvailable(tools: string[]): void {
    for (const t of tools) {
      if (!AVAILABLE_TOOL_NAMES.has(t)) {
        throw new IamAiToolUnknownException(t);
      }
    }
  }

  private mergeLockedSet(tools: string[]): string[] {
    const s = new Set(tools);
    for (const t of LOCKED_TOOL_NAMES) s.add(t);
    return [...s].sort();
  }

  // ==================== v2.3 角色级 ====================

  /**
   * 按角色聚合返回所有角色（除 SyncBot），含 0 授权的角色（LEFT JOIN 风格）。
   * 排序与「角色与权限」页一致：createdAt desc。
   */
  async listRoleGrantsAggregated(search?: string) {
    const where: Record<string, unknown> = { code: { not: 'SyncBot' } };
    if (search?.trim()) {
      const q = search.trim();
      where.OR = [
        { name: { contains: q, mode: 'insensitive' } },
        { code: { contains: q, mode: 'insensitive' } },
      ];
    }

    const roles = await this.prisma.role.findMany({
      where,
      select: {
        id: true,
        name: true,
        code: true,
        createdAt: true,
        aiToolGrants: {
          select: { toolName: true, updatedAt: true },
        },
      },
      orderBy: { createdAt: 'desc' },
    });

    return roles.map((r) => {
      const tools = r.aiToolGrants.map((g) => g.toolName).sort();
      const latest = r.aiToolGrants.reduce<Date | null>(
        (acc, g) => (acc === null || g.updatedAt > acc ? g.updatedAt : acc),
        null,
      );
      return {
        roleId: r.id,
        roleName: r.name,
        roleCode: r.code,
        tools,
        toolCount: tools.length,
        updatedAt: latest ?? r.createdAt,
      };
    });
  }

  /**
   * 事务设置角色的完整工具集合（PUT 语义）
   * 强制合入 LOCKED_SET，diff 后增删行
   */
  async setRoleGrants(
    roleId: string,
    inputTools: string[],
    actorUserId?: string,
  ) {
    await this.ensureRoleExists(roleId);
    this.ensureAllToolsAvailable(inputTools);
    const targetTools = this.mergeLockedSet(inputTools);

    const currentGrants = await this.prisma.aIToolGrant.findMany({
      where: { roleId },
    });
    const currentSet = new Set(currentGrants.map((g) => g.toolName));
    const targetSet = new Set(targetTools);

    const toAdd = targetTools.filter((t) => !currentSet.has(t));
    const toRemove = currentGrants.filter((g) => !targetSet.has(g.toolName));

    if (toAdd.length === 0 && toRemove.length === 0) {
      return {
        roleId,
        tools: targetTools,
        added: [],
        removed: [],
        toolCount: targetTools.length,
      };
    }

    await this.prisma.$transaction(async (tx) => {
      if (toRemove.length > 0) {
        await tx.aIToolGrant.deleteMany({
          where: { id: { in: toRemove.map((g) => g.id) } },
        });
      }
      for (const toolName of toAdd) {
        await tx.aIToolGrant.create({
          data: { roleId, toolName, createdBy: actorUserId },
        });
      }
    });

    return {
      roleId,
      tools: targetTools,
      added: toAdd,
      removed: toRemove.map((g) => g.toolName),
      toolCount: targetTools.length,
    };
  }

  // ==================== v2.3 用户级 ====================

  /**
   * 设置用户级调整（相对角色基线的加减）
   */
  async setUserGrants(
    userId: string,
    params: { added: string[]; removed: string[]; reason: string },
    actorUserId?: string,
  ) {
    await this.ensureUserExists(userId);
    this.ensureAllToolsAvailable([...params.added, ...params.removed]);

    for (const t of params.removed) {
      if (LOCKED_SET.has(t)) {
        throw new IamAiToolLockedCannotRemoveException(t);
      }
    }

    await this.prisma.$transaction(async (tx) => {
      // 增量操作：只处理本次请求涉及的工具，不影响其他已有调整
      const touchedTools = [...params.added, ...params.removed];
      if (touchedTools.length > 0) {
        await tx.aIToolGrantUser.deleteMany({
          where: { userId, toolName: { in: touchedTools } },
        });
      }

      for (const toolName of params.added) {
        await tx.aIToolGrantUser.create({
          data: {
            userId,
            toolName,
            effect: 'grant',
            reason: params.reason,
            createdBy: actorUserId,
          },
        });
      }
      for (const toolName of params.removed) {
        await tx.aIToolGrantUser.create({
          data: {
            userId,
            toolName,
            effect: 'revoke',
            reason: params.reason,
            createdBy: actorUserId,
          },
        });
      }
    });

    return {
      userId,
      added: params.added.sort(),
      removed: params.removed.sort(),
      reason: params.reason,
    };
  }

  // ==================== v2.3 用户概览 ====================

  async getUserGrantsOverview(params: {
    orgId?: string;
    deptId?: string;
    roleIds?: string[];
    search?: string;
    hasExtra?: boolean;
    hasRevoked?: boolean;
    page?: number;
    pageSize?: number;
  }) {
    const page = params.page ?? 1;
    const pageSize = params.pageSize ?? 20;
    const skip = (page - 1) * pageSize;

    // 构建用户查询条件
    const userWhere: Record<string, unknown> = { deletedAt: null };
    if (params.search?.trim()) {
      const q = `%${params.search.trim()}%`;
      userWhere.OR = [
        { username: { contains: params.search.trim(), mode: 'insensitive' } },
        { displayName: { contains: params.search.trim(), mode: 'insensitive' } },
        { email: { contains: params.search.trim(), mode: 'insensitive' } },
      ];
    }

    // 按角色过滤
    if (params.roleIds?.length) {
      const usersWithRoles = await this.prisma.userRole.findMany({
        where: { roleId: { in: params.roleIds } },
        select: { userId: true },
      });
      userWhere.id = { in: usersWithRoles.map((ur) => ur.userId) };
    }

    // 按组织/部门过滤（都走 UserDepartment 表）
    if (params.deptId) {
      const memberships = await this.prisma.userDepartment.findMany({
        where: { departmentId: params.deptId },
        select: { userId: true },
      });
      const ids = memberships.map((m: { userId: string }) => m.userId);
      userWhere.id = userWhere.id
        ? { in: (userWhere.id as { in: string[] }).in.filter((id: string) => ids.includes(id)) }
        : { in: ids };
    } else if (params.orgId) {
      const memberships = await this.prisma.userDepartment.findMany({
        where: { organizationId: params.orgId },
        select: { userId: true },
        distinct: ['userId'],
      });
      const ids = memberships.map((m: { userId: string }) => m.userId);
      userWhere.id = userWhere.id
        ? { in: (userWhere.id as { in: string[] }).in.filter((id: string) => ids.includes(id)) }
        : { in: ids };
    }

    // hasExtra / hasRevoked 过滤
    if (params.hasExtra || params.hasRevoked) {
      const effectFilters: string[] = [];
      if (params.hasExtra) effectFilters.push('grant');
      if (params.hasRevoked) effectFilters.push('revoke');
      const userIdsWithAdjustments = await this.prisma.aIToolGrantUser.findMany({
        where: { effect: { in: effectFilters } },
        select: { userId: true },
        distinct: ['userId'],
      });
      const ids = userIdsWithAdjustments.map((u) => u.userId);
      userWhere.id = userWhere.id
        ? { in: (userWhere.id as { in: string[] }).in.filter((id: string) => ids.includes(id)) }
        : { in: ids };
    }

    const [users, total] = await Promise.all([
      this.prisma.user.findMany({
        where: userWhere as any,
        select: {
          id: true,
          displayName: true,
          email: true,
          avatar: true,
        },
        orderBy: { username: 'asc' },
        skip,
        take: pageSize,
      }),
      this.prisma.user.count({ where: userWhere as any }),
    ]);

    // 批量获取角色、用户级调整
    const userIds = users.map((u) => u.id);

    const [userRoles, userAdjustments] = await Promise.all([
      this.prisma.userRole.findMany({
        where: { userId: { in: userIds } },
        include: { role: { select: { id: true, name: true, code: true } } },
      }),
      this.prisma.aIToolGrantUser.findMany({
        where: { userId: { in: userIds } },
      }),
    ]);

    // 批量获取角色级 grants
    const allRoleIds = [...new Set(userRoles.map((ur) => ur.roleId))];
    const roleGrants = allRoleIds.length
      ? await this.prisma.aIToolGrant.findMany({
          where: { roleId: { in: allRoleIds } },
        })
      : [];
    const toolsByRoleId = new Map<string, Set<string>>();
    for (const g of roleGrants) {
      const s = toolsByRoleId.get(g.roleId) ?? new Set();
      s.add(g.toolName);
      toolsByRoleId.set(g.roleId, s);
    }

    // Employee fallback：无角色用户继承 Employee 基线
    const employeeRole = await this.prisma.role.findFirst({ where: { code: 'Employee' } });
    const employeeTools = employeeRole ? (toolsByRoleId.get(employeeRole.id) ?? new Set<string>()) : new Set<string>();

    const items = users.map((user) => {
      const roles = userRoles
        .filter((ur) => ur.userId === user.id)
        .map((ur) => ({ id: ur.role.id, name: ur.role.name, code: ur.role.code }));

      const inherited = new Set<string>();
      if (roles.length === 0) {
        employeeTools.forEach((t) => inherited.add(t));
      }
      for (const r of roles) {
        const tools = toolsByRoleId.get(r.id);
        if (tools) tools.forEach((t) => inherited.add(t));
      }

      const adjustments = userAdjustments.filter((a) => a.userId === user.id);
      const addedCount = adjustments.filter((a) => a.effect === 'grant').length;
      const removedCount = adjustments.filter((a) => a.effect === 'revoke').length;
      const effective = new Set(inherited);
      for (const a of adjustments) {
        if (a.effect === 'grant') effective.add(a.toolName);
        if (a.effect === 'revoke') effective.delete(a.toolName);
      }
      for (const t of LOCKED_TOOL_NAMES) effective.add(t);

      return {
        userId: user.id,
        displayName: user.displayName,
        email: user.email,
        avatar: user.avatar,
        roles,
        inheritedToolCount: inherited.size,
        addedCount,
        removedCount,
        effectiveToolCount: effective.size,
      };
    });

    return { items, total, page, pageSize };
  }

  // ==================== v2.3 生效工具（增强版） ====================

  /**
   * v2.3: 返回所有生效工具 + LOCKED_SET + sources + meta
   */
  async getUserEffectiveToolsV2(userId: string) {
    await this.ensureUserExists(userId);

    const userRoles = await this.prisma.userRole.findMany({
      where: { userId },
      include: { role: { select: { id: true, name: true } } },
    });
    const roleIds = userRoles.map((ur) => ur.roleId);
    const roleNameById = new Map(userRoles.map((ur) => [ur.roleId, ur.role.name]));

    let roleGrants = roleIds.length
      ? await this.prisma.aIToolGrant.findMany({ where: { roleId: { in: roleIds } } })
      : [];

    // 无角色用户 fallback 到 Employee 基线
    if (roleIds.length === 0) {
      const employeeRole = await this.prisma.role.findFirst({ where: { code: 'Employee' } });
      if (employeeRole) {
        roleGrants = await this.prisma.aIToolGrant.findMany({
          where: { roleId: employeeRole.id },
        });
        roleNameById.set(employeeRole.id, employeeRole.name);
      }
    }

    const userAdjustments = await this.prisma.aIToolGrantUser.findMany({
      where: { userId },
    });

    const toolMap = new Map<string, { sources: EffectiveToolSource[] }>();
    const revokedTools: string[] = [];

    // 角色级
    for (const g of roleGrants) {
      const entry = toolMap.get(g.toolName) ?? { sources: [] };
      entry.sources.push({
        type: 'role',
        roleId: g.roleId,
        roleName: roleNameById.get(g.roleId) ?? '',
        grantId: g.id,
      });
      toolMap.set(g.toolName, entry);
    }

    // 用户级
    for (const a of userAdjustments) {
      if (a.effect === 'revoke') {
        toolMap.delete(a.toolName);
        revokedTools.push(a.toolName);
        continue;
      }
      const entry = toolMap.get(a.toolName) ?? { sources: [] };
      entry.sources.push({
        type: 'user',
        userId: a.userId,
        grantId: a.id,
        reason: a.reason,
      });
      toolMap.set(a.toolName, entry);
    }

    // LOCKED_SET 强制加入
    for (const t of LOCKED_TOOL_NAMES) {
      if (!toolMap.has(t)) {
        toolMap.set(t, { sources: [] });
      }
    }

    const catalog = new Map(STATIC_AVAILABLE_TOOLS.map((t) => [t.name, t]));
    const data = [...toolMap.entries()]
      .sort(([a], [b]) => a.localeCompare(b))
      .map(([name, entry]) => {
        const tool = catalog.get(name);
        const isLocked = LOCKED_SET.has(name);
        return {
          toolName: name,
          category: tool?.category ?? 'other',
          locked: isLocked,
          sources: isLocked && entry.sources.length === 0
            ? [{ type: 'locked' as const, label: '系统锁定' }]
            : entry.sources,
        };
      });

    return {
      data,
      meta: {
        totalTools: data.length,
        lockedCount: data.filter((d) => d.locked).length,
        inheritedCount: data.filter((d) => d.sources.some((s) => s.type === 'role')).length,
        userAddedCount: userAdjustments.filter((a) => a.effect === 'grant').length,
        userRemovedCount: revokedTools.length,
        userRemovedTools: revokedTools.sort(),
      },
    };
  }
}
