import type { PrismaClient, Agent, Prisma, LlmKind, LlmStatus } from '@prisma/client';

export interface CreateAgentRepoInput {
  name: string;
  description?: string;
  systemPrompt?: string;
  llmId: string;
  projectId?: string | null;
  defaultPersonalityId?: string | null;
  proxyModelName?: string | null;
  defaultParams?: Record<string, unknown>;
  extras?: Record<string, unknown>;
  ownerId: string;
  // v7: optional visibility scope (default 'public' if omitted).
  visibility?: 'public' | 'private';
  // Virtual-agent lifecycle (omit for kind=public).
  kind?: LlmKind;
  providerSessionId?: string | null;
  status?: LlmStatus;
  lastHeartbeatAt?: Date | null;
  inactiveSince?: Date | null;
}

export interface UpdateAgentRepoInput {
  description?: string;
  systemPrompt?: string;
  llmId?: string;
  projectId?: string | null;
  defaultPersonalityId?: string | null;
  proxyModelName?: string | null;
  defaultParams?: Record<string, unknown>;
  extras?: Record<string, unknown>;
  // v7: visibility is mutable (operator can flip a private virtual to
  // public for org-wide sharing without re-creating).
  visibility?: 'public' | 'private';
  // Virtual-agent lifecycle. AgentService is the only public writer; the
  // VirtualAgentService methods (Stage 2) bypass the public CRUD path.
  kind?: LlmKind;
  providerSessionId?: string | null;
  status?: LlmStatus;
  lastHeartbeatAt?: Date | null;
  inactiveSince?: Date | null;
}

export interface IAgentRepository {
  findAll(): Promise<Agent[]>;
  findById(id: string): Promise<Agent | null>;
  findByName(name: string): Promise<Agent | null>;
  findByProjectId(projectId: string): Promise<Agent[]>;
  create(data: CreateAgentRepoInput): Promise<Agent>;
  update(id: string, data: UpdateAgentRepoInput): Promise<Agent>;
  delete(id: string): Promise<void>;
  // Virtual-agent lifecycle helpers.
  findBySessionId(sessionId: string): Promise<Agent[]>;
  findByLlmId(llmId: string): Promise<Agent[]>;
  findStaleVirtuals(heartbeatCutoff: Date): Promise<Agent[]>;
  findExpiredInactives(deletionCutoff: Date): Promise<Agent[]>;
}

export class AgentRepository implements IAgentRepository {
  constructor(private readonly prisma: PrismaClient) {}

  async findAll(): Promise<Agent[]> {
    return this.prisma.agent.findMany({ orderBy: { name: 'asc' } });
  }

  async findById(id: string): Promise<Agent | null> {
    return this.prisma.agent.findUnique({ where: { id } });
  }

  async findByName(name: string): Promise<Agent | null> {
    return this.prisma.agent.findUnique({ where: { name } });
  }

  async findByProjectId(projectId: string): Promise<Agent[]> {
    return this.prisma.agent.findMany({
      where: { projectId },
      orderBy: { name: 'asc' },
    });
  }

  async create(data: CreateAgentRepoInput): Promise<Agent> {
    return this.prisma.agent.create({
      data: {
        name: data.name,
        description: data.description ?? '',
        systemPrompt: data.systemPrompt ?? '',
        llmId: data.llmId,
        projectId: data.projectId ?? null,
        defaultPersonalityId: data.defaultPersonalityId ?? null,
        proxyModelName: data.proxyModelName ?? null,
        defaultParams: (data.defaultParams ?? {}) as Prisma.InputJsonValue,
        extras: (data.extras ?? {}) as Prisma.InputJsonValue,
        ownerId: data.ownerId,
        ...(data.visibility !== undefined ? { visibility: data.visibility } : {}),
        ...(data.kind !== undefined ? { kind: data.kind } : {}),
        ...(data.providerSessionId !== undefined ? { providerSessionId: data.providerSessionId } : {}),
        ...(data.status !== undefined ? { status: data.status } : {}),
        ...(data.lastHeartbeatAt !== undefined ? { lastHeartbeatAt: data.lastHeartbeatAt } : {}),
        ...(data.inactiveSince !== undefined ? { inactiveSince: data.inactiveSince } : {}),
      },
    });
  }

  async update(id: string, data: UpdateAgentRepoInput): Promise<Agent> {
    const updateData: Prisma.AgentUpdateInput = {};
    if (data.description !== undefined) updateData.description = data.description;
    if (data.systemPrompt !== undefined) updateData.systemPrompt = data.systemPrompt;
    if (data.llmId !== undefined) {
      updateData.llm = { connect: { id: data.llmId } };
    }
    if (data.projectId !== undefined) {
      updateData.project = data.projectId === null
        ? { disconnect: true }
        : { connect: { id: data.projectId } };
    }
    if (data.defaultPersonalityId !== undefined) {
      updateData.defaultPersonality = data.defaultPersonalityId === null
        ? { disconnect: true }
        : { connect: { id: data.defaultPersonalityId } };
    }
    if (data.proxyModelName !== undefined) {
      updateData.proxyModelName = data.proxyModelName;
    }
    if (data.defaultParams !== undefined) {
      updateData.defaultParams = data.defaultParams as Prisma.InputJsonValue;
    }
    if (data.extras !== undefined) {
      updateData.extras = data.extras as Prisma.InputJsonValue;
    }
    if (data.visibility !== undefined) updateData.visibility = data.visibility;
    if (data.kind !== undefined) updateData.kind = data.kind;
    if (data.providerSessionId !== undefined) updateData.providerSessionId = data.providerSessionId;
    if (data.status !== undefined) updateData.status = data.status;
    if (data.lastHeartbeatAt !== undefined) updateData.lastHeartbeatAt = data.lastHeartbeatAt;
    if (data.inactiveSince !== undefined) updateData.inactiveSince = data.inactiveSince;
    // Bump optimistic version on every update.
    updateData.version = { increment: 1 };
    return this.prisma.agent.update({ where: { id }, data: updateData });
  }

  async delete(id: string): Promise<void> {
    await this.prisma.agent.delete({ where: { id } });
  }

  // ── Virtual-agent lifecycle queries ──

  async findBySessionId(sessionId: string): Promise<Agent[]> {
    return this.prisma.agent.findMany({
      where: { providerSessionId: sessionId },
      orderBy: { name: 'asc' },
    });
  }

  async findByLlmId(llmId: string): Promise<Agent[]> {
    return this.prisma.agent.findMany({
      where: { llmId },
      orderBy: { name: 'asc' },
    });
  }

  async findStaleVirtuals(heartbeatCutoff: Date): Promise<Agent[]> {
    return this.prisma.agent.findMany({
      where: {
        kind: 'virtual',
        status: 'active',
        lastHeartbeatAt: { lt: heartbeatCutoff },
      },
    });
  }

  async findExpiredInactives(deletionCutoff: Date): Promise<Agent[]> {
    return this.prisma.agent.findMany({
      where: {
        kind: 'virtual',
        status: 'inactive',
        inactiveSince: { lt: deletionCutoff },
      },
    });
  }
}
