import { randomUUID } from 'crypto';
import { and, asc, eq } from 'drizzle-orm';
import { db } from '../db';
import { agentPanels } from './schema';
import type { AgentPanelRow } from './schema';

export type AgentPanel = AgentPanelRow;

/** What the browser is allowed to see. The handoff token is a bearer credential and never crosses. */
export type AgentPanelView = Omit<AgentPanel, 'handoffToken' | 'userId'>;

export const toAgentPanelView = ({ handoffToken: _t, userId: _u, ...rest }: AgentPanel): AgentPanelView => rest;

/** Every agent on one dashboard, in creation order — this is the address book. */
export function listAgentPanels(userId: number, dashboardId: string): Promise<AgentPanel[]> {
  return db
    .select()
    .from(agentPanels)
    .where(and(eq(agentPanels.userId, userId), eq(agentPanels.dashboardId, dashboardId)))
    .orderBy(asc(agentPanels.id));
}

export async function getAgentPanelByPanelId(
  userId: number,
  dashboardId: string,
  panelId: string,
): Promise<AgentPanel | undefined> {
  const [row] = await db
    .select()
    .from(agentPanels)
    .where(
      and(eq(agentPanels.userId, userId), eq(agentPanels.dashboardId, dashboardId), eq(agentPanels.panelId, panelId)),
    )
    .limit(1);
  return row;
}

/** Resolve an address. The whole point of the table. */
export async function getAgentPanelByName(
  userId: number,
  dashboardId: string,
  name: string,
): Promise<AgentPanel | undefined> {
  const [row] = await db
    .select()
    .from(agentPanels)
    .where(and(eq(agentPanels.userId, userId), eq(agentPanels.dashboardId, dashboardId), eq(agentPanels.name, name)))
    .limit(1);
  return row;
}

/** The sender's identity, established by the bearer token alone — see the schema comment. */
export async function getAgentPanelByHandoffToken(token: string): Promise<AgentPanel | undefined> {
  const [row] = await db.select().from(agentPanels).where(eq(agentPanels.handoffToken, token)).limit(1);
  return row;
}

export type CreateAgentPanelInput = {
  userId: number;
  dashboardId: string;
  panelId: string;
  name: string;
  cwd?: string | null;
  rolePrompt?: string | null;
};

/**
 * Mint an agent. The sessionKey is generated here and only here: it is the one identifier that must
 * never change for the life of the panel, because the sidecar's on-disk resume map and the durable
 * event log are both keyed on it.
 */
export async function createAgentPanel(input: CreateAgentPanelInput): Promise<AgentPanel> {
  const [row] = await db
    .insert(agentPanels)
    .values({
      userId: input.userId,
      dashboardId: input.dashboardId,
      panelId: input.panelId,
      name: input.name,
      sessionKey: randomUUID(),
      handoffToken: randomUUID(),
      cwd: input.cwd ?? null,
      rolePrompt: input.rolePrompt ?? null,
    })
    .returning();
  return row!;
}

export type UpdateAgentPanelInput = {
  name?: string;
  cwd?: string | null;
  rolePrompt?: string | null;
  panelId?: string;
};

/** Rename, re-home or re-scope an agent. `sessionKey` is intentionally not updatable. */
export async function updateAgentPanel(
  userId: number,
  id: number,
  patch: UpdateAgentPanelInput,
): Promise<AgentPanel | undefined> {
  const [row] = await db
    .update(agentPanels)
    .set({ ...patch, updatedAt: new Date() })
    .where(and(eq(agentPanels.userId, userId), eq(agentPanels.id, id)))
    .returning();
  return row;
}

/** Stamp the introduction as sent, so a reload cannot re-introduce an agent to itself. */
export async function markAgentPanelIntroduced(id: number): Promise<void> {
  await db.update(agentPanels).set({ introducedAt: new Date() }).where(eq(agentPanels.id, id));
}

export async function deleteAgentPanel(userId: number, id: number): Promise<void> {
  await db.delete(agentPanels).where(and(eq(agentPanels.userId, userId), eq(agentPanels.id, id)));
}
