import { ForbiddenException, Injectable, NotFoundException } from '@nestjs/common';
import { PrismaService } from '@core/database/prisma/prisma.service';
import { SkipAssertAccess } from '@common/decorators/skip-assert-access.decorator';
import type { AgentScratchpad } from '@prisma/client';

/**
 * PR8b AgentScratchpadService —— per-session 跨 turn 草稿本。
 *
 * 数据模型：(organizationId, sessionId, key) → JSON value。
 * 用途：sub-agent 把工作过程的中间数据存这里，主 agent 后续 turn 读，
 * 不必每次重新拉外部数据。
 *
 * 隔离：所有读写强制 organizationId 过滤；跨 org 看不到任何 key。
 */
@Injectable()
export class AgentScratchpadService {
  constructor(private readonly prisma: PrismaService) {}

  async get(args: { organizationId: string; sessionId: string; key: string }): Promise<unknown | null> {
    const row = await this.prisma.agentScratchpad.findUnique({
      where: { sessionId_key: { sessionId: args.sessionId, key: args.key } },
    });
    if (!row) return null;
    if (row.organizationId !== args.organizationId) {
      throw new NotFoundException(`scratchpad key ${args.key} not found`);
    }
    return row.value;
  }

  @SkipAssertAccess('upsert 前显式 findUnique + organizationId 比对；不匹配抛 Forbidden 不静默覆盖')
  async upsert(args: {
    organizationId: string;
    sessionId: string;
    key: string;
    value: unknown;
  }): Promise<AgentScratchpad> {
    // 防御纵深：unique 键是 (sessionId, key)，若 sessionId 在外 org 已有同 key 行，
    // 不加这层显式校验会被静默覆盖；与 get/delete 路径对齐。
    const existing = await this.prisma.agentScratchpad.findUnique({
      where: { sessionId_key: { sessionId: args.sessionId, key: args.key } },
    });
    if (existing && existing.organizationId !== args.organizationId) {
      throw new ForbiddenException('cross-organization scratchpad access denied');
    }
    return this.prisma.agentScratchpad.upsert({
      where: { sessionId_key: { sessionId: args.sessionId, key: args.key } },
      create: {
        organizationId: args.organizationId,
        sessionId: args.sessionId,
        key: args.key,
        value: args.value as never,
      },
      update: { value: args.value as never },
    });
  }

  @SkipAssertAccess('删除前显式 findUnique + organizationId 比对；命中失败直接 return false 不调 delete')
  async delete(args: { organizationId: string; sessionId: string; key: string }): Promise<boolean> {
    const row = await this.prisma.agentScratchpad.findUnique({
      where: { sessionId_key: { sessionId: args.sessionId, key: args.key } },
    });
    if (!row || row.organizationId !== args.organizationId) return false;
    await this.prisma.agentScratchpad.delete({
      where: { sessionId_key: { sessionId: args.sessionId, key: args.key } },
    });
    return true;
  }

  async listKeys(args: { organizationId: string; sessionId: string }): Promise<string[]> {
    const rows = await this.prisma.agentScratchpad.findMany({
      where: { organizationId: args.organizationId, sessionId: args.sessionId },
      select: { key: true },
      orderBy: { updatedAt: 'desc' },
      take: 200,
    });
    return rows.map((r) => r.key);
  }
}
