/**
 * @description 天润呼叫中心对接接口
 */
import { Fn, isClient, isWorker, MaybeRefOrGetter, toRef, tryOnScopeDispose, useIntervalFn } from '@vueuse/shared/index';
import { ref, Ref, watch } from 'vue';
import { WebSocketStatus } from '@/hooks/useWebsocket';
import { ElMessage } from 'element-plus';
const DEFAULT_PING_MESSAGE = 'ping';
let UUID = '73836387-0000-0000-0000-0000-0000000000';
let _extn = ''; // 分机号
function resolveNestedOptions<T>(options: T | true): T {
	if (options === true) return {} as T;
	return options;
}
export function olaFn(url: MaybeRefOrGetter<string | URL | undefined>, options: any) {
	const {
		onConnected,
		onDisconnected,
		onError,
		onMessage,
		immediate = true,
		autoClose = true,
		protocols = [],
		username = '',
		password = '',
	} = options;

	const data: Ref<any | null> = ref(null);
	const status = ref<WebSocketStatus>('CLOSED');
	const wsRef = ref<WebSocket | undefined>();
	const urlRef = toRef(url);

	let heartbeatPause: Fn | undefined;
	let heartbeatResume: Fn | undefined;

	let explicitlyClosed = false;
	let retried = 0;

	let bufferedData: (string | ArrayBuffer | Blob)[] = [];

	let pongTimeoutWait: ReturnType<typeof setTimeout> | undefined;

	const _sendBuffer = () => {
		if (bufferedData.length && wsRef.value && status.value === 'OPEN') {
			for (const buffer of bufferedData) wsRef.value.send(buffer);
			bufferedData = [];
		}
	};

	const resetHeartbeat = () => {
		clearTimeout(pongTimeoutWait);
		pongTimeoutWait = undefined;
	};

	// Status code 1000 -> Normal Closure https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent/code
	const close: WebSocket['close'] = (code = 1000, reason) => {
		if (!isClient || !wsRef.value) return;
		explicitlyClosed = true;
		resetHeartbeat();
		heartbeatPause?.();
		wsRef.value.close(code, reason);
		wsRef.value = undefined;
	};

	const send = (data: string | ArrayBuffer | Blob | any, useBuffer = true) => {
		if (!wsRef.value || status.value !== 'OPEN') {
			if (useBuffer) bufferedData.push(data);
			ElMessage.error('请先签入');
			return false;
		}
		_sendBuffer();
		data.uuid = next_uuid();
		const sedMsg = JSON.stringify(data);
		wsRef.value.send(sedMsg);
		// console.log(`${getNowDateTime()}：呼叫中心发送消息：${sedMsg}`);
		return true;
	};
	const merge = (target: { [x: string]: any }, additional: { [x: string]: any; hasOwnProperty: (arg0: string) => any }) => {
		for (const i in additional) {
			if (additional.hasOwnProperty(i)) {
				target[i] = additional[i];
			}
		}
	};

	const next_uuid = () => {
		let u = (parseFloat(UUID.substring(29)) + 1).toString();
		u = u == '2147483647' ? '0' : u;
		while (u.length < 12) {
			u = '0' + u;
		}
		UUID = '73836387-0000-0000-0000-0000-' + u;
		return UUID;
	};

	const auth = (username: any, password: any) => {
		send({ cmd: 'auth', args: { username, password, accept: 'application/json' } });
	};

	const get_agent_state = (extn: any) => {
		send({ action: 'api', cmd: 'get_agent_state', args: { extn } });
	};

	const get_trunk_state = () => {
		send({ action: 'api', cmd: 'get_trunk_state' });
	};

	const login = (queue: any, extn: any, params: any) => {
		const args = { queue, extn };
		merge(args, params);
		_extn = extn;
		return send({ action: 'api', cmd: 'login', args: args });
	};

	const logout = (extn?: string) => {
		const extns = extn || _extn;
		send({ action: 'api', cmd: 'logout', args: { extn: extns } });
	};

	const collect_dtmf = (extn: any, soundfile: any) => {
		send({ action: 'api', cmd: 'collect_dtmf', args: { extn, soundfile } });
	};
	const collect_judge = (extn: any) => {
		send({ action: 'api', cmd: 'collect_judge', args: { extn: extn } });
	};

	const ping = () => {
		return send({ cmd: 'ping' }, false);
	};

	const subscribe = (k: any) => {
		return send({ cmd: 'subscribe', args: { key: k } });
	};

	const unsubscribe = (k: any) => {
		return send({ cmd: 'unsubscribe', args: { key: k } });
	};

	const go_ready = () => {
		send({ action: 'api', cmd: 'go_ready', args: { extn: _extn } });
	};

	const go_ready2 = (extn: any) => {
		send({ action: 'api', cmd: 'go_ready', args: { extn: extn } });
	};

	const go_break = (reason: any) => {
		send({ action: 'api', cmd: 'go_break', args: { extn: _extn, reason: reason } });
	};

	const go_break2 = (extn: any) => {
		send({ action: 'api', cmd: 'go_break', args: { extn: extn, reason: '' } });
	};

	const toggle_ready = () => {
		send({ action: 'api', cmd: 'toggle_ready', args: { extn: _extn } });
	};

	const answer = () => {
		send({ action: 'api', cmd: 'answer', args: { extn: _extn } });
	};

	const hangup = () => {
		send({ action: 'api', cmd: 'hangup_other', args: { extn: _extn } });
	};

	const dial = (dst: any, otherStr?: any, gateway?: any) => {
		send({ action: 'api', cmd: 'dial', args: { extn: _extn, dest: dst, gateway: gateway, otherStr: otherStr } });
	};

	const transfer = (dst: any, src?: any) => {
		let extn = src;

		if (extn == null || typeof extn == 'undefined') {
			extn = _extn;
		}

		send({ action: 'api', cmd: 'transfer', args: { extn: extn, dest: dst } });
	};

	const transfer_uuid = (uuid: any, dst: any) => {
		send({ action: 'api', cmd: 'transfer', args: { channel_uuid: uuid, dest: dst } });
	};

	const monitor = (dst: any, src: any) => {
		let extn = src;
		if (extn == null || typeof extn == 'undefined') {
			extn = _extn;
		}

		send({ action: 'api', cmd: 'monitor', args: { extn: extn, dest: dst } });
	};

	const monitor_uuid = (uuid: any) => {
		send({ action: 'api', cmd: 'monitor', args: { extn: _extn, channel_uuid: uuid } });
	};

	const intercept = (dst: any, src: any, gateway: any) => {
		let extn = src;

		if (extn == null || typeof extn == 'undefined') {
			extn = _extn;
		}

		send({ action: 'api', cmd: 'intercept', args: { extn: extn, dest: dst } });
	};

	const intercept_uuid = (uuid: any) => {
		send({ action: 'api', cmd: 'intercept', args: { extn: _extn, channel_uuid: uuid } });
	};

	const three_way = (dst: any, src: any, gateway?: any) => {
		let extn = src;
		if (extn == null || typeof extn == 'undefined') {
			extn = _extn;
		}
		send({ action: 'api', cmd: 'monitor', args: { extn: extn, dest: dst, three_way: 'true', goip_gateway: gateway } });
	};
	/* hangup the thrid party */
	const exit_three_way = (ext: any) => {
		send({ action: 'api', cmd: 'unmonitor', args: { extn: ext, three_way: 'true' } });
	};
	const three_way_uuid = (uuid: any) => {
		send({ action: 'api', cmd: 'monitor', args: { extn: _extn, channel_uuid: uuid, three_way: 'true' } });
	};

	const unmonitor = (ext: any) => {
		send({ action: 'api', cmd: 'unmonitor', args: { extn: ext } });
	};

	const whisper = (w: any) => {
		// who = "agent" / "caller" / "both" / "none"
		send({ action: 'api', cmd: 'whisper', args: { extn: _extn, who: w } });
	};

	const consult = (dst: any) => {
		send({ action: 'api', cmd: 'consult', args: { extn: _extn, dest: dst } });
	};
	const unconsult = (dst: any) => {
		send({ action: 'api', cmd: 'unconsult', args: { extn: _extn, dest: dst } });
	};
	const consult_to_three_way = (dst: any) => {
		send({ action: 'api', cmd: 'consult_to_three_way', args: { extn: _extn, dest: dst } });
	};

	const hold = () => {
		send({ action: 'api', cmd: 'hold', args: { extn: _extn } });
	};

	const unhold = () => {
		send({ action: 'api', cmd: 'unhold', args: { extn: _extn } });
	};

	const toggle_hold = () => {
		send({ action: 'api', cmd: 'toggle_hold', args: { extn: _extn } });
	};

	const take_call = (channel_uuid: any) => {
		send({ action: 'api', cmd: 'take_call', args: { extn: _extn, channel_uuid: channel_uuid } });
	};
	const next_queue = (uuid: any) => {
		send({ action: 'api', cmd: 'next_queue', args: { extn: _extn, channel_uuid: uuid } });
	};

	const conference = (dst: any) => {
		send({ action: 'api', cmd: 'conference', args: { extn: _extn, dest: dst } });
	};

	const conference_uuid = (uuid: any) => {
		send({ action: 'api', cmd: 'conference', args: { extn: _extn, channel_uuid: uuid } });
	};

	const broadcast = (numbers: any, mute: any) => {
		send({ action: 'api', cmd: 'broadcast', args: { extn: _extn, numbers, mute } });
	};

	/* send chat to a queue or an agent
      to = queue             send to queue
      to = queue.agent       send to agent
    */
	const chat = (to: any, message: any, content_type: any) => {
		send({ action: 'api', cmd: 'chat', args: { to: to, message: message, content_type: content_type } });
	};

	const message = (from: any, to: any, message: any, content_type: any) => {
		send({ action: 'api', cmd: 'message', args: { from: from, to: to, message: message, content_type: content_type } });
	};

	const alarm = (queue: any, state: any) => {
		send({ action: 'api', cmd: 'alarm', args: { queue, state } });
	};

	/* dispatching apis */

	const dlogin = (ext: any) => {
		send({ action: 'api', cmd: 'dlogin', args: { extn: ext } });
	};

	const dlogout = (ext: any) => {
		send({ action: 'api', cmd: 'dlogout', args: { extn: ext } });
	};

	const inject = (ext: any, uuid: any) => {
		send({ action: 'api', cmd: 'inject', args: { extn: ext, channel_uuid: uuid } });
	};

	const kill = (uuid: any, extn: any) => {
		send({ action: 'api', cmd: 'kill', args: { channel_uuid: uuid, extn: extn } });
	};

	const eavesdrop = (uuid: any) => {
		send({ action: 'api', cmd: 'eavesdrop', args: { channel_uuid: uuid } });
	};

	const conf = (name: any, action: any, member: any) => {
		send({ action: 'api', cmd: 'conf', args: { name: name, action: action, member: member } });
	};

	const answer_all = (ext: any, queue: any) => {
		send({ action: 'api', cmd: 'answer_all', args: { extn: ext, queue: queue } });
	};

	const group_call = (ext: any, queue: any, numbers: any, batch_accept: any) => {
		send({ action: 'api', cmd: 'group_call', args: { extn: ext, queue: queue, numbers: numbers, batch_accept: batch_accept } });
	};

	const sip_gateway = (profile: any, gateway: any, op: any) => {
		send({ action: 'api', cmd: 'sip_gateway', args: { profile: profile, gateway: gateway, op: op } });
	};

	const playback = (filename: any) => {
		send({ action: 'api', cmd: 'playback', args: { extn: _extn, soundfile: filename } });
	};

	const stop_playback = () => {
		send({ action: 'api', cmd: 'stop_playback', args: { extn: _extn } });
	};

	const merge_call = (ext1: any, ext2: any) => {
		send({ action: 'api', cmd: 'merge_call', args: { extn1: ext1, extn2: ext2 } });
	};

	/* common apis*/

	/*phone control api, only yealink support for now*/
	const api_handfree = (ext: any) => {
		send({ action: 'api', cmd: 'api_handfree', args: { extn: ext } });
	};
	const getStatus = () => {
		// @ts-ignore
		return wsRef.value.readyState;
	};
	const startSubscribe = () => {
		subscribe(`ola.agent.${options.username}`);
		subscribe(`ola.caller.${options.username}`);
		get_agent_state(options.username);
	};

	const _init = () => {
		if (explicitlyClosed || typeof urlRef.value === 'undefined') return;

		const ws = new WebSocket(urlRef.value, protocols);
		wsRef.value = ws;
		status.value = 'CONNECTING';

		ws.onopen = () => {
			status.value = 'OPEN';
			heartbeatResume?.();
			_sendBuffer();
			auth(options.username, options.password);
			startSubscribe();
			onConnected?.(ws!);
		};

		ws.onclose = (ev) => {
			status.value = 'CLOSED';
			onDisconnected?.(ev);

			if (!explicitlyClosed && options.autoReconnect) {
				const { retries = -1, delay = 1000, onFailed } = resolveNestedOptions(options.autoReconnect);
				retried += 1;

				// @ts-ignore
				if (retries < 0 || retried < retries) setTimeout(_init, delay);
				else if (typeof retries === 'function' && retries()) setTimeout(_init, delay);
				else onFailed?.();
			}
		};

		ws.onerror = (e) => {
			onError?.(e);
		};

		ws.onmessage = (e: MessageEvent) => {
			if (options.heartbeat) {
				resetHeartbeat();
				const { message = DEFAULT_PING_MESSAGE } = resolveNestedOptions(options.heartbeat);
				if (e.data === message) return;
			}

			data.value = e.data;
			onMessage?.(e.data);
		};
	};

	if (options.heartbeat) {
		const { message = DEFAULT_PING_MESSAGE, interval = 1000, pongTimeout = 1000 } = resolveNestedOptions(options.heartbeat);

		const { pause, resume } = useIntervalFn(
			() => {
				ping();
				if (pongTimeoutWait != null) return;
				pongTimeoutWait = setTimeout(() => {
					// auto-reconnect will be trigger with ws.onclose()
					close();
					explicitlyClosed = false;
				}, pongTimeout);
			},
			interval,
			{ immediate: false }
		);

		heartbeatPause = pause;
		heartbeatResume = resume;
	}

	if (autoClose) {
		if (isClient) window.addEventListener('beforeunload', () => close());
		tryOnScopeDispose(close);
	}

	const open = () => {
		if (!isClient && !isWorker) return;
		close();
		explicitlyClosed = false;
		retried = 0;
		_init();
	};

	if (immediate) open();

	watch(urlRef, open);

	return {
		data,
		status,
		close,
		send,
		open,
		ws: wsRef,
		login,
		logout,
		go_ready,
		go_break,
		hangup,
		hold,
		unhold,
		dial,
		exit_three_way,
		transfer,
		monitor,
		collect_dtmf,
		collect_judge,
		get_trunk_state,
		ping,
		unsubscribe,
		go_ready2,
		toggle_ready,
		answer,
		transfer_uuid,
		monitor_uuid,
		intercept,
		intercept_uuid,
		three_way,
		three_way_uuid,
		unmonitor,
		whisper,
		consult,
		unconsult,
		consult_to_three_way,
		toggle_hold,
		take_call,
		next_queue,
		conference,
		conference_uuid,
		broadcast,
		chat,
		message,
		alarm,
		dlogin,
		dlogout,
		inject,
		kill,
		eavesdrop,
		conf,
		answer_all,
		group_call,
		sip_gateway,
		playback,
		stop_playback,
		merge_call,
		api_handfree,
		getStatus,
		username: options.username,
		password: options.password,
		next_uuid,
	};
}
