// `GalaxyClient` orchestrates one authenticated unary round-trip:
// build canonical request bytes via `Core.signRequest`, sign them
// through an injected `Signer`, post the envelope through the typed
// Connect client, then verify the response signature and payload hash
// before returning the decoded payload.
//
// Phase 5 ships the orchestration only — Phase 6 plugs the WebCrypto
// `Signer` and `KeyStore`, and Phase 7 wires the auth flow that
// actually creates the device session. Tests pass an exportable
// fixture key wrapped as a `Signer` so the flow can be exercised
// end-to-end without a live gateway.

import { create } from "@bufbuild/protobuf";
import type { Core } from "../platform/core/index";
import {
	ExecuteCommandRequestSchema,
	type ExecuteCommandResponse,
} from "../proto/galaxy/gateway/v1/edge_gateway_pb";
import type { EdgeGatewayClient } from "./connect";

/**
 * Signer produces a raw 64-byte Ed25519 signature over canonicalBytes.
 * Phase 6 backs this with WebCrypto + a non-exportable IDB key handle.
 * Phase 5 tests pass a function that closes over `crypto.sign(...)`
 * with an exportable fixture key.
 */
export type Signer = (canonicalBytes: Uint8Array) => Promise<Uint8Array>;

/**
 * Sha256 computes the raw 32-byte SHA-256 digest of payload. The
 * GalaxyClient receives this as a dependency so JSDOM tests can pass
 * Node's `crypto` and the browser path can use `crypto.subtle`.
 */
export type Sha256 = (payload: Uint8Array) => Promise<Uint8Array>;

export interface GalaxyClientOptions {
	core: Core;
	edge: EdgeGatewayClient;
	signer: Signer;
	sha256: Sha256;
	deviceSessionId: string;
	gatewayResponsePublicKey: Uint8Array;
	clock?: () => bigint;
	requestIdFactory?: () => string;
}

const PROTOCOL_VERSION = "v1";

export interface ExecuteCommandResult {
	resultCode: string;
	payloadBytes: Uint8Array;
}

export class GalaxyClient {
	private readonly core: Core;
	private readonly edge: EdgeGatewayClient;
	private readonly signer: Signer;
	private readonly sha256: Sha256;
	private readonly deviceSessionId: string;
	private readonly gatewayResponsePublicKey: Uint8Array;
	private readonly clock: () => bigint;
	private readonly requestIdFactory: () => string;

	constructor(options: GalaxyClientOptions) {
		this.core = options.core;
		this.edge = options.edge;
		this.signer = options.signer;
		this.sha256 = options.sha256;
		this.deviceSessionId = options.deviceSessionId;
		this.gatewayResponsePublicKey = options.gatewayResponsePublicKey;
		this.clock = options.clock ?? defaultClock;
		this.requestIdFactory =
			options.requestIdFactory ?? defaultRequestIdFactory;
	}

	async executeCommand(
		messageType: string,
		payload: Uint8Array,
		traceId = "",
	): Promise<ExecuteCommandResult> {
		const requestId = this.requestIdFactory();
		const timestampMs = this.clock();
		const payloadHash = await this.sha256(payload);

		const canonicalBytes = this.core.signRequest({
			protocolVersion: PROTOCOL_VERSION,
			deviceSessionId: this.deviceSessionId,
			messageType,
			timestampMs,
			requestId,
			payloadHash,
		});

		const signature = await this.signer(canonicalBytes);

		const response = await this.edge.executeCommand(
			create(ExecuteCommandRequestSchema, {
				protocolVersion: PROTOCOL_VERSION,
				deviceSessionId: this.deviceSessionId,
				messageType,
				timestampMs,
				requestId,
				payloadBytes: payload,
				payloadHash,
				signature,
				traceId,
			}),
		);

		this.verifyResponse(response, requestId);
		return {
			resultCode: response.resultCode,
			payloadBytes: response.payloadBytes,
		};
	}

	private verifyResponse(
		response: ExecuteCommandResponse,
		requestId: string,
	): void {
		if (response.requestId !== requestId) {
			throw new Error(
				`galaxy-client: response request_id ${response.requestId} does not match ${requestId}`,
			);
		}
		if (
			!this.core.verifyPayloadHash(
				response.payloadBytes,
				response.payloadHash,
			)
		) {
			throw new Error("galaxy-client: response payload_hash mismatch");
		}
		const ok = this.core.verifyResponse(
			this.gatewayResponsePublicKey,
			response.signature,
			{
				protocolVersion: response.protocolVersion,
				requestId: response.requestId,
				timestampMs: response.timestampMs,
				resultCode: response.resultCode,
				payloadHash: response.payloadHash,
			},
		);
		if (!ok) {
			throw new Error("galaxy-client: invalid response signature");
		}
	}
}

function defaultClock(): bigint {
	return BigInt(Date.now());
}

function defaultRequestIdFactory(): string {
	return crypto.randomUUID();
}
