import type { ConnectionID, RpcMetadata } from './types/payload.js';
import { decodePayload, deserializeError } from './utils/serialize.js';
import { VERSION } from './version.js';
import { WebSocketAppCode } from './codes.js';

const AUTH_TIMEOUT = 5000;

/** 认证结果 */
type AuthResult = [seq: number, id: ConnectionID, meta: RpcMetadata];

/** 检查认证消息 */
function checkAuthMessage(data: unknown, seq?: number, id?: ConnectionID): AuthResult {
    const isResponse = seq != null && id != null;
    const payload = decodePayload(data);
    if (payload?.type !== 'auth') {
        if (isResponse) throw new Error(`Failed to authenticate, bad response from server`);
        else throw new Error(`Failed to authenticate, bad request from client`);
    }
    if (payload.version !== VERSION) {
        throw new Error(`Failed to authenticate, version not match, expected ${VERSION}, got ${payload.version}`);
    }
    if (isResponse) {
        if (payload.seq !== seq) {
            throw new Error(`Failed to authenticate, bad response seq from server`);
        }
        if (payload.id !== id) {
            throw new Error(`Failed to authenticate, bad response id from server`);
        }
    }
    if (payload.error) {
        throw deserializeError(payload.error);
    }
    const metadata = typeof payload.metadata != 'object' || payload.metadata == null ? {} : payload.metadata;
    return [payload.seq, payload.id, metadata];
}

/** 接受认证消息 */
export async function waitAuth(socket: WebSocket, seq?: number, id?: ConnectionID): Promise<AuthResult> {
    return new Promise((resolve, reject) => {
        const onTimeout = (): void => {
            reject(new Error(`Failed to authenticate, no remote message in ${AUTH_TIMEOUT}ms`));
            finalize(false);
        };
        const onClose = (ev: CloseEvent): void => {
            reject(new Error(`Failed to authenticate, socket closed, code=${ev.code}: ${ev.reason}`));
            finalize(false);
        };
        const onMessage = (ev: MessageEvent): void => {
            try {
                const result = checkAuthMessage(ev.data, seq, id);
                resolve(result);
                finalize(true);
            } catch (ex) {
                reject(ex as Error);
                finalize(false);
            }
        };
        const timeout = setTimeout(onTimeout, AUTH_TIMEOUT);
        socket.addEventListener('close', onClose);
        socket.addEventListener('message', onMessage);
        const finalize = (ok: boolean): void => {
            clearTimeout(timeout);
            socket.removeEventListener('message', onMessage);
            socket.removeEventListener('close', onClose);
            if (ok) return;
            socket.close(WebSocketAppCode.AUTH_ERROR);
        };
    });
}
