// Typed wrappers around `GalaxyClient.executeCommand` for the user-
// account command catalog. Each wrapper builds a FlatBuffers request
// payload via the generated TS bindings, calls `executeCommand`, then
// decodes the `AccountResponse` reply. Errors with a non-`ok`
// `result_code` surface as a thrown `AccountError` carrying the
// canonical backend code (`invalid_request`, `subject_not_found`,
// `forbidden`, `conflict`, `internal_error`).

import { Builder, ByteBuffer } from "flatbuffers";

import type { GalaxyClient } from "./galaxy-client";
import {
	AccountResponse,
	AccountView,
	ErrorResponse as FbsErrorResponse,
	GetMyAccountRequest,
	UpdateMyProfileRequest,
	UpdateMySettingsRequest,
} from "../proto/galaxy/fbs/user";

const RESULT_CODE_OK = "ok";

export class AccountError extends Error {
	readonly code: string;
	readonly resultCode: string;

	constructor(resultCode: string, code: string, message: string) {
		super(message);
		this.name = "AccountError";
		this.resultCode = resultCode;
		this.code = code;
	}
}

export interface Account {
	userId: string;
	email: string;
	userName: string;
	displayName: string;
	preferredLanguage: string;
	timeZone: string;
	declaredCountry: string;
	entitlement: AccountEntitlement;
}

// AccountEntitlement is the narrow view of the FBS EntitlementSnapshot
// the UI currently consumes. `isPaid` gates lobby affordances tied to
// the paid tier (F8-04b: private-games subpage + create-game button).
// Other snapshot fields (plan code, expiry timestamps) are intentionally
// omitted until a feature needs them.
export interface AccountEntitlement {
	isPaid: boolean;
}

export async function getMyAccount(client: GalaxyClient): Promise<Account> {
	const builder = new Builder(32);
	GetMyAccountRequest.startGetMyAccountRequest(builder);
	builder.finish(GetMyAccountRequest.endGetMyAccountRequest(builder));
	const payload = await execute(client, "user.account.get", builder.asUint8Array());
	return decodeAccountResponse(payload);
}

export async function updateMyProfile(
	client: GalaxyClient,
	displayName: string,
): Promise<Account> {
	const builder = new Builder(128);
	const displayNameOff = builder.createString(displayName);
	UpdateMyProfileRequest.startUpdateMyProfileRequest(builder);
	UpdateMyProfileRequest.addDisplayName(builder, displayNameOff);
	builder.finish(UpdateMyProfileRequest.endUpdateMyProfileRequest(builder));
	const payload = await execute(client, "user.profile.update", builder.asUint8Array());
	return decodeAccountResponse(payload);
}

export async function updateMySettings(
	client: GalaxyClient,
	preferredLanguage: string,
	timeZone: string,
): Promise<Account> {
	const builder = new Builder(128);
	const preferredLanguageOff = builder.createString(preferredLanguage);
	const timeZoneOff = builder.createString(timeZone);
	UpdateMySettingsRequest.startUpdateMySettingsRequest(builder);
	UpdateMySettingsRequest.addPreferredLanguage(builder, preferredLanguageOff);
	UpdateMySettingsRequest.addTimeZone(builder, timeZoneOff);
	builder.finish(UpdateMySettingsRequest.endUpdateMySettingsRequest(builder));
	const payload = await execute(client, "user.settings.update", builder.asUint8Array());
	return decodeAccountResponse(payload);
}

async function execute(
	client: GalaxyClient,
	messageType: string,
	payloadBytes: Uint8Array,
): Promise<Uint8Array> {
	const result = await client.executeCommand(messageType, payloadBytes);
	if (result.resultCode !== RESULT_CODE_OK) {
		throw decodeAccountError(result.resultCode, result.payloadBytes);
	}
	return result.payloadBytes;
}

function decodeAccountError(resultCode: string, payload: Uint8Array): AccountError {
	let code = resultCode;
	let message = resultCode;
	try {
		const errorResponse = FbsErrorResponse.getRootAsErrorResponse(new ByteBuffer(payload));
		const body = errorResponse.error();
		if (body) {
			code = body.code() ?? resultCode;
			message = body.message() ?? resultCode;
		}
	} catch (_err) {
		// fall through with the raw result code
	}
	return new AccountError(resultCode, code, message);
}

function decodeAccountResponse(payload: Uint8Array): Account {
	if (payload.length === 0) {
		throw new AccountError("internal_error", "internal_error", "empty account response");
	}
	const response = AccountResponse.getRootAsAccountResponse(new ByteBuffer(payload));
	const view = response.account();
	if (view === null) {
		throw new AccountError("internal_error", "internal_error", "account missing in response");
	}
	return decodeAccountView(view);
}

// Exported for unit tests that build a synthetic AccountView via the
// FBS bindings and assert the resulting Account shape. Runtime callers
// reach the same decode path through `getMyAccount` / `updateMyProfile`
// / `updateMySettings`.
export function decodeAccountView(view: AccountView): Account {
	const entitlement = view.entitlement();
	return {
		userId: view.userId() ?? "",
		email: view.email() ?? "",
		userName: view.userName() ?? "",
		displayName: view.displayName() ?? "",
		preferredLanguage: view.preferredLanguage() ?? "",
		timeZone: view.timeZone() ?? "",
		declaredCountry: view.declaredCountry() ?? "",
		entitlement: {
			isPaid: entitlement?.isPaid() ?? false,
		},
	};
}
