import { Hono } from 'hono';
import { getAgentPanelByHandoffToken, getAgentPanelByName, listAgentPanels, type AgentPanel } from 'officerdb';
import { composeHandoff, deliverToAgentPanel } from './deliver';
import { logger } from '../chat/logger';

/**
 * The door an AGENT knocks on — not a human, and not a browser.
 *
 * This is mounted above the account gate, so it does not carry a platform JWT and does not go through
 * `userMiddleware`. That exemption is declared and justified in `capabilities/totality.ts`; the claim
 * it makes is that this surface is authenticated by a per-panel bearer token instead, and that the
 * token's authority is tiny by construction:
 *
 *   - it identifies exactly one agent panel, so the SENDER cannot be forged and needs no `from` field
 *   - it can address only peers in that panel's own dashboard, by name
 *   - it can do one thing: deliver a prompt. No reads of anything else, no writes, no session keys.
 *
 * Deliberately its own router rather than a route inside `/chat`: the two have different callers,
 * different credentials and different blast radii, and folding one into the other would have hidden
 * that. See docs/agent-coordination.md.
 *
 * Note this is a bare `Hono` and not `createRouter()` — createRouter's helpers assume the `user` and
 * `body` context values that `userMiddleware`/`bodyParser` set, and neither runs here.
 */
export const agentHandoffRouter = new Hono();

const TOKEN_HEADER = 'x-officer-agent-token';

async function senderFrom(header: string | undefined): Promise<AgentPanel | undefined> {
  const token = header?.trim();
  if (!token) return undefined;
  return getAgentPanelByHandoffToken(token);
}

/** Errors here are read by a language model, so they say what to do instead of just what went wrong. */
const plain = (text: string, status: 400 | 401 | 404 | 500) => new Response(`${text}\n`, { status });

// GET /agent-handoff/peers — who else is on my dashboard. Discovery, so a wrong name is recoverable.
agentHandoffRouter.get('/peers', async (ctx) => {
  const sender = await senderFrom(ctx.req.header(TOKEN_HEADER));
  if (!sender) return plain(`Unknown or missing ${TOKEN_HEADER}.`, 401);

  const peers = await listAgentPanels(sender.userId, sender.dashboardId);
  return ctx.json({
    you: sender.name,
    dashboardId: sender.dashboardId,
    peers: peers.filter((p) => p.id !== sender.id).map((p) => ({ name: p.name, cwd: p.cwd, role: p.rolePrompt })),
  });
});

// POST /agent-handoff — deliver a message to a named peer as a turn in their own session.
agentHandoffRouter.post('/', async (ctx) => {
  const sender = await senderFrom(ctx.req.header(TOKEN_HEADER));
  if (!sender) return plain(`Unknown or missing ${TOKEN_HEADER}.`, 401);

  let body: { to?: unknown; message?: unknown };
  try {
    body = await ctx.req.json();
  } catch {
    return plain('Body must be JSON: {"to": "<peer name>", "message": "<text>"}', 400);
  }

  const to = typeof body.to === 'string' ? body.to.trim() : '';
  const message = typeof body.message === 'string' ? body.message.trim() : '';
  if (!to) return plain('Missing "to". It is the name of a peer on your dashboard.', 400);
  if (!message) return plain('Missing "message". An empty handoff would tell the other agent nothing.', 400);

  if (to === sender.name) return plain(`You are \`${sender.name}\`. An agent cannot hand off to itself.`, 400);

  const target = await getAgentPanelByName(sender.userId, sender.dashboardId, to);
  if (!target) {
    // Loud, and useful: a typo is the likeliest cause and the fix is in the error.
    const peers = await listAgentPanels(sender.userId, sender.dashboardId);
    const names = peers
      .filter((p) => p.id !== sender.id)
      .map((p) => `\`${p.name}\``)
      .join(', ');
    return plain(
      `No agent named \`${to}\` on dashboard \`${sender.dashboardId}\`.\n` +
        (names ? `Known peers: ${names}` : 'There are no other agents on this dashboard yet.'),
      404,
    );
  }

  try {
    await deliverToAgentPanel(target, composeHandoff(sender.name, message));
  } catch (err) {
    const detail = err instanceof Error ? err.message : String(err);
    logger.error('Handoff delivery failed', { from: sender.name, to: target.name, error: detail });
    // Never swallow this. A handoff that silently vanished is the exact failure this whole mechanism
    // exists to avoid — the sender would sit waiting for a reply that was never going to come.
    return plain(`Could not deliver to \`${target.name}\`: ${detail}`, 500);
  }

  return ctx.json({ delivered: true, from: sender.name, to: target.name });
});
