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

export interface CreateSessionInput {
  organizationId: string;
  createdById: string;
  title?: string;
  projectId?: string;
  /** 选中的 persona，激活 PERSONA-scoped memory 注入（M1-step2） */
  personaId?: string;
  surface?: AgentSurface;
  /** 多 Agent 拓扑：sub-agent session 指向父 session id */
  parentSessionId?: string;
}

export interface ListSessionsInput {
  organizationId: string;
  createdById?: string;
  status?: AgentSessionStatus;
  limit?: number;
  /** 过滤项目：UUID 或字符串 'null' 表示"无项目归属"；缺省返全部 */
  projectId?: string | 'null';
}

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

  async createSession(input: CreateSessionInput): Promise<AgentSession> {
    return this.prisma.agentSession.create({
      data: {
        organizationId: input.organizationId,
        createdById: input.createdById,
        title: input.title ?? null,
        projectId: input.projectId ?? null,
        personaId: input.personaId ?? null,
        surface: input.surface ?? 'WEB',
        parentSessionId: input.parentSessionId ?? null,
      },
    });
  }

  async listSessions(input: ListSessionsInput): Promise<AgentSession[]> {
    return this.prisma.agentSession.findMany({
      where: {
        organizationId: input.organizationId,
        ...(input.createdById ? { createdById: input.createdById } : {}),
        ...(input.status ? { status: input.status } : {}),
        ...(input.projectId === 'null'
          ? { projectId: null }
          : input.projectId
            ? { projectId: input.projectId }
            : {}),
        closedAt: null,
        // 防御性过滤：title 为空 + 没有任何 message = 孤儿空壳，不展示。
        // 跟 lazy-create 形成双层保护（前端不该再产生空 session，但保留兜底）。
        NOT: {
          AND: [{ title: null }, { messages: { none: {} } }],
        },
      },
      orderBy: { createdAt: 'desc' },
      take: input.limit ?? 50,
    });
  }

  async getSession(sessionId: string, organizationId: string) {
    const session = await this.prisma.agentSession.findUnique({
      where: { id: sessionId },
      include: {
        messages: {
          orderBy: { sequence: 'asc' },
        },
      },
    });

    if (!session) throw new NotFoundException('Agent session not found');
    // 跨组织防越权（INV-1 day-1 必备的最简实现，PR 后续 DataScope detector 会替换）
    if (session.organizationId !== organizationId) {
      throw new ForbiddenException('Cross-organization access denied');
    }
    return session;
  }

  /**
   * Rewind：把 session 内 sequence > afterSequence 的消息全部硬删，让前端可在
   * 旧用户消息位置编辑后重发新 prompt（ChatGPT 风格"edit & resubmit"）。
   *
   * 不留分支历史（v1 简化）；trajectory 哈希链保留完整审计。
   * 跨 org 走 getSession 校验。
   */
  @SkipAssertAccess('入口即 getSession(sessionId, organizationId)，跨 org 直接 Forbidden')
  async rewindAfter(
    sessionId: string,
    organizationId: string,
    afterSequence: number,
  ): Promise<{ ok: true; deleted: number }> {
    // 防 data-loss：undefined / 非整数 / 负数透传到 Prisma `{ gt: undefined }` 会让
    // sequence 过滤被忽略，本次 deleteMany 删掉整 session 全部 message。
    if (!Number.isInteger(afterSequence) || afterSequence < 0) {
      throw new BadRequestException(
        `afterSequence must be a non-negative integer, got: ${afterSequence}`,
      );
    }
    await this.getSession(sessionId, organizationId);
    const result = await this.prisma.agentMessage.deleteMany({
      where: { sessionId, sequence: { gt: afterSequence } },
    });
    return { ok: true, deleted: result.count };
  }

  /**
   * 部分更新已有 session 的 title / personaId / projectId。
   * - 跨 org 走 getSession → 403
   * - personaId / projectId 必须属于同 org（应用层校验，schema 层 FK 只保存在性）
   * - 传 null 表示清空
   */
  @SkipAssertAccess('入口即 getSession(sessionId, organizationId)，跨 org 直接 Forbidden')
  async updateSession(
    sessionId: string,
    organizationId: string,
    patch: { title?: string | null; personaId?: string | null; projectId?: string | null },
  ): Promise<AgentSession> {
    await this.getSession(sessionId, organizationId);
    const data: Prisma.AgentSessionUpdateInput = {};
    if (patch.title !== undefined) data.title = patch.title;
    if (patch.personaId !== undefined) {
      if (patch.personaId === null) {
        data.persona = { disconnect: true };
      } else {
        const p = await this.prisma.agentPersona.findUnique({ where: { id: patch.personaId } });
        if (!p || p.organizationId !== organizationId) {
          throw new ForbiddenException('persona not in organization');
        }
        data.persona = { connect: { id: patch.personaId } };
      }
    }
    if (patch.projectId !== undefined) {
      // AgentProject 没在 AgentSession 上声明反向 relation —— 用 scalar 字段直接赋
      if (patch.projectId === null) {
        data.projectId = null;
      } else {
        const p = await this.prisma.agentProject.findUnique({ where: { id: patch.projectId } });
        if (!p || p.organizationId !== organizationId) {
          throw new ForbiddenException('project not in organization');
        }
        data.projectId = patch.projectId;
      }
    }
    return this.prisma.agentSession.update({ where: { id: sessionId }, data });
  }

  @SkipAssertAccess('入口即 getSession(sessionId, organizationId)，跨 org 直接 Forbidden')
  async softDeleteSession(sessionId: string, organizationId: string): Promise<AgentSession> {
    // 先校验归属
    await this.getSession(sessionId, organizationId);
    return this.prisma.agentSession.update({
      where: { id: sessionId },
      data: {
        closedAt: new Date(),
        status: 'CLOSED',
      },
    });
  }
}
