/**
 * a2a-agent-server.js — A2A HTTP server entrypoint for spawned swarm agent processes.
 *
 * Purpose: when launched as a subprocess with the SF_A2A_AGENT_* environment
 * variables set, start an A2A JSON-RPC HTTP server for the named swarm agent
 * role, handle incoming DispatchEnvelope tasks by routing them through the
 * UOK coordination store, and signal readiness by printing a JSON line to
 * stdout so the parent A2ATransport process can record the endpoint URL.
 *
 * Launch environment:
 *   SF_A2A_AGENT_NAME  — stable agent name (e.g. "worker-1")
 *   SF_A2A_AGENT_ROLE  — agent role (coordinator|worker|scout|reviewer|…)
 *   SF_A2A_PORT        — HTTP port to listen on
 *   SF_A2A_BASE_PATH   — project root for SQLite state
 *
 * Consumer: A2ATransport.spawnAgent() in a2a-transport.js.
 */

import { randomUUID } from "node:crypto";
import { AGENT_CARD_PATH } from "@a2a-js/sdk";
import { DefaultRequestHandler, InMemoryTaskStore } from "@a2a-js/sdk/server";
import {
	agentCardHandler,
	jsonRpcHandler,
	UserBuilder,
} from "@a2a-js/sdk/server/express";
import express from "express";
import { buildAgentCard } from "./a2a-transport.js";
import { getErrorMessage } from "../error-utils.js";

const agentName = process.env.SF_A2A_AGENT_NAME;
const agentRole = process.env.SF_A2A_AGENT_ROLE ?? "worker";
const port = Number(process.env.SF_A2A_PORT ?? 34501);
const basePath = process.env.SF_A2A_BASE_PATH ?? process.cwd();

if (!agentName) {
	process.stderr.write("a2a-agent-server: SF_A2A_AGENT_NAME is required\n");
	process.exit(1);
}

/**
 * Minimal AgentExecutor that handles DispatchEnvelope messages.
 *
 * Purpose: receive incoming A2A task messages containing a DispatchEnvelope
 * payload, process them via the UOK coordination store, and publish a
 * structured result back to the caller so the parent SwarmDispatchLayer can
 * record the outcome in SQLite.
 *
 * Consumer: DefaultRequestHandler for each inbound A2A task.
 */
class SwarmAgentExecutor {
	/**
	 * @param {string} name - agent name
	 * @param {string} role - agent role
	 * @param {string} basePath - project root
	 */
	constructor(name, role, basePath) {
		this._name = name;
		this._role = role;
		this._basePath = basePath;
	}

	/**
	 * Execute an incoming A2A task.
	 *
	 * Purpose: extract the DispatchEnvelope from the message parts, record it
	 * in the agent's coordination store, and publish a result message so the
	 * caller receives structured acknowledgement.
	 *
	 * Consumer: DefaultRequestHandler on every inbound sendMessage call.
	 *
	 * @param {import('@a2a-js/sdk/server').RequestContext} requestContext
	 * @param {import('@a2a-js/sdk/server').ExecutionEventBus} eventBus
	 */
	async execute(requestContext, eventBus) {
		const incomingMessage = requestContext.message;
		let envelope = null;

		// Extract envelope from message parts.
		for (const part of incomingMessage?.parts ?? []) {
			if (part.kind === "data" && part.data) {
				envelope = part.data;
				break;
			}
			if (part.kind === "text") {
				try {
					envelope = JSON.parse(part.text);
				} catch {
					// not JSON text
				}
				if (envelope) break;
			}
		}

		let resultBody;
		if (!envelope) {
			resultBody = {
				error: "No DispatchEnvelope found in message parts",
				agentName: this._name,
				role: this._role,
			};
		} else {
			try {
				resultBody = await this._handleEnvelope(envelope);
			} catch (err) {
				resultBody = {
					error: getErrorMessage(err),
					agentName: this._name,
					role: this._role,
					unitId: envelope?.unitId,
				};
			}
		}

		const responseMessage = {
			kind: "message",
			messageId: randomUUID(),
			role: "agent",
			parts: [
				{
					kind: "data",
					data: resultBody,
					metadata: { contentType: "application/json" },
				},
			],
			contextId: requestContext.contextId,
		};

		eventBus.publish(responseMessage);
		eventBus.finished();
	}

	/**
	 * Record the envelope in the agent's coordination store and return an ack.
	 *
	 * Purpose: persist the dispatched unit so the SQLite coordination layer
	 * retains durability even when transport is A2A HTTP. The agent's inbox is
	 * updated so the standard UOK polling loops can pick up the task.
	 *
	 * Consumer: execute() after extracting the envelope from message parts.
	 *
	 * @param {object} envelope - DispatchEnvelope from the swarm dispatch layer
	 * @returns {Promise<object>} acknowledgement payload
	 */
	async _handleEnvelope(envelope) {
		// Lazy-import to avoid loading SQLite unless this server is actually running.
		const { MessageBus } = await import("./message-bus.js");

		const bus = new MessageBus(this._basePath);
		const from = `a2a:dispatch:${envelope.scope ?? "unknown"}:${envelope.unitId}`;
		const to = `agent:${this._name}`;
		const metadata = {
			unitId: envelope.unitId,
			unitType: envelope.unitType,
			workMode: envelope.workMode,
			transport: "a2a",
		};

		const messageId = bus.send(
			from,
			to,
			envelope.payload ?? envelope,
			metadata,
		);

		return {
			status: "accepted",
			messageId,
			agentName: this._name,
			role: this._role,
			unitId: envelope.unitId,
			unitType: envelope.unitType,
			workMode: envelope.workMode,
		};
	}

	async cancelTask(_taskId, _requestContext) {
		// Stateless for now; cancellation is handled via SQLite coordination store.
	}
}

async function main() {
	const agentCard = buildAgentCard(agentName, agentRole, port);

	const executor = new SwarmAgentExecutor(agentName, agentRole, basePath);
	const requestHandler = new DefaultRequestHandler(
		agentCard,
		new InMemoryTaskStore(),
		executor,
	);

	const app = express();

	app.use(
		`/${AGENT_CARD_PATH}`,
		agentCardHandler({ agentCardProvider: requestHandler }),
	);
	app.use(
		"/a2a/jsonrpc",
		jsonRpcHandler({
			requestHandler,
			userBuilder: UserBuilder.noAuthentication,
		}),
	);

	// Health check endpoint.
	app.get("/health", (_req, res) => {
		res.json({ ok: true, agentName, role: agentRole, port });
	});

	await new Promise((resolve, reject) => {
		const server = app.listen(port, "127.0.0.1", () => resolve(server));
		server.once("error", reject);
	});

	// Signal readiness to the parent process.
	process.stdout.write(
		JSON.stringify({ ready: true, port, agentName, role: agentRole }) + "\n",
	);

	process.on("SIGTERM", () => {
		process.exit(0);
	});
}

main().catch((err) => {
	process.stderr.write(`a2a-agent-server: fatal: ${err.message}\n`);
	process.exit(1);
});
