import type { WebSocket } from '@cloudpss/fetch';
import type { ConnectionID, RpcMetadata } from './types/payload.js';
import { decodePayload, deserializeError } from './utils/serialize.js';
import { VERSION } from './version.js';

const AUTH_TIMEOUT = 5000;

/** 接受认证消息 */
export async function waitAuth(
    socket: WebSocket,
    seq?: number,
    id?: ConnectionID,
): Promise<[seq: number, id: ConnectionID, meta: RpcMetadata]> {
    return new Promise((resolve, reject) => {
        const isResponse = seq != null && id != null;
        const timeout = setTimeout(() => {
            reject(new Error(`Failed to authenticate, no ${isResponse ? 'response' : 'request'} in ${AUTH_TIMEOUT}ms`));
            finalize();
        }, AUTH_TIMEOUT);
        const handleMessage = (ev: MessageEvent): void => {
            try {
                const payload = decodePayload(ev.data);
                if (!payload || payload.type !== 'auth') {
                    if (isResponse) return reject(new Error(`Failed to authenticate, bad response from server`));
                    else return reject(new Error(`Failed to authenticate, bad request from client`));
                }
                if (payload.version !== VERSION) {
                    return reject(
                        new Error(
                            `Failed to authenticate, version not match, expected ${VERSION}, got ${payload.version}`,
                        ),
                    );
                }
                if (isResponse) {
                    if (payload.seq !== seq) {
                        return reject(new Error(`Failed to authenticate, bad response seq from server`));
                    }
                    if (payload.id !== id) {
                        return reject(new Error(`Failed to authenticate, bad response id from server`));
                    }
                }
                if (payload.error) {
                    return reject(deserializeError(payload.error));
                }
                const metadata = typeof payload.metadata != 'object' ? {} : (payload.metadata ?? {});
                resolve([payload.seq, payload.id, metadata]);
            } catch (ex) {
                reject(ex as Error);
            } finally {
                finalize();
            }
        };
        const finalize = (): void => {
            clearTimeout(timeout);
            socket.removeEventListener('message', handleMessage);
        };
        socket.addEventListener('message', handleMessage, { once: true });
    });
}
