import { dispatchSocket } from "(app)/dispatch/socket";
import {
	handleDisconnect,
	handleLocalTrackUnpublished,
	handleTrackSubscribed,
	handleTrackUnsubscribed,
} from "_helpers/liveKitEventHandler";
import {
	ConnectionQuality,
	LocalTrackPublication,
	Participant,
	Room,
	RoomEvent,
	RpcInvocationData,
	Track,
} from "livekit-client";
import { pilotSocket } from "(app)/pilot/socket";
import { create } from "zustand";
import axios from "axios";
import { useDispatchConnectionStore } from "_store/dispatch/connectionStore";
import { changeDispatcherAPI } from "_querys/dispatcher";
import { getRadioStream } from "_helpers/radioEffect";
import { usePilotConnectionStore } from "_store/pilot/connectionStore";

let interval: NodeJS.Timeout;

type TalkState = {
	addSpeakingParticipant: (participant: Participant) => void;
	connect: (roomName: string, role: string) => void;
	connectionQuality: ConnectionQuality;
	disconnect: () => void;
	isTalking: boolean;
	localRadioTrack: LocalTrackPublication | undefined;
	message: string | null;
	removeMessage: () => void;
	removeSpeakingParticipant: (speakingParticipants: Participant) => void;
	remoteParticipants: number;
	resetSpeakingParticipants: (source: string) => void;
	room: Room | null;
	setSettings: (settings: Partial<TalkState["settings"]>) => void;
	settings: {
		micDeviceId: string | null;
		micVolume: number;
		radioVolume: number;
		dmeVolume: number;
	};
	speakingParticipants: Participant[];
	state: "connecting" | "connected" | "disconnected" | "error";
	toggleTalking: () => void;
	transmitBlocked: boolean;
};
const getToken = async (roomName: string) => {
	const response = await axios.get(`/api/livekit-token?roomName=${roomName}`);
	const data = response.data;
	return data.token;
};

export const useAudioStore = create<TalkState>((set, get) => ({
	isTalking: false,
	localRadioTrack: undefined,
	transmitBlocked: false,
	message: null,
	speakingParticipants: [],
	micVolume: 1,
	settings: {
		micDeviceId: null,
		micVolume: 1,
		radioVolume: 0.8,
		dmeVolume: 0.8,
	},
	state: "disconnected" as const,
	remoteParticipants: 0,
	connectionQuality: ConnectionQuality.Unknown,
	room: null,
	resetSpeakingParticipants: (source: string) => {
		set({
			speakingParticipants: [],
			isTalking: false,
			transmitBlocked: false,
			message: `Ruf beendet durch ${source || "eine unsichtbare Macht"}`,
		});
		get().room?.localParticipant.setMicrophoneEnabled(false);
	},
	addSpeakingParticipant: (participant) => {
		set((state) => {
			if (!state.speakingParticipants.some((p) => p.identity === participant.identity)) {
				return { speakingParticipants: [...state.speakingParticipants, participant] };
			}
			return state;
		});
	},
	removeMessage: () => {
		set({ message: null });
	},
	removeSpeakingParticipant: (participant) => {
		const newSpeaktingParticipants = get().speakingParticipants.filter(
			(p) => !(p.identity === participant.identity),
		);
		set(() => ({
			speakingParticipants: newSpeaktingParticipants,
		}));
		if (newSpeaktingParticipants.length === 0 && get().transmitBlocked) {
			get().room?.localParticipant.setMicrophoneEnabled(true);
			set({ transmitBlocked: false, message: null, isTalking: true });
		}
	},
	setSettings: (newSettings) => {
		const oldSettings = get().settings;
		set((s) => ({
			settings: {
				...s.settings,
				...newSettings,
			},
		}));
		if (
			get().state === "connected" &&
			(oldSettings.micDeviceId !== newSettings.micDeviceId ||
				oldSettings.micVolume !== newSettings.micVolume)
		) {
			const { room, disconnect, connect } = get();
			const role = room?.localParticipant.attributes.role;
			if (room?.name || role) {
				disconnect();
				connect(room?.name || "", role || "user");
			}
		}
	},
	toggleTalking: () => {
		const { room, isTalking, speakingParticipants, transmitBlocked } = get();
		if (!room) return;

		if (speakingParticipants.length > 0 && !isTalking && !transmitBlocked) {
			// Wenn andere sprechen, nicht reden
			set({
				message: "Rufgruppe besetzt",
				transmitBlocked: true,
			});
			return;
		} else if (!isTalking && transmitBlocked) {
			set({
				message: null,
				transmitBlocked: false,
			});
			return;
		}

		const { status: DispatcherConnectionStatus } = useDispatchConnectionStore.getState();
		const { status: PilotConnectionStatus } = usePilotConnectionStore.getState();
		if (
			!isTalking &&
			!(DispatcherConnectionStatus === "connected" || PilotConnectionStatus === "connected")
		) {
			useAudioStore.setState({
				message: "Keine Verbindung",
			});
			return;
		}

		room.localParticipant.setMicrophoneEnabled(!isTalking);

		set((state) => ({ isTalking: !state.isTalking, transmitBlocked: false }));
	},
	connect: async (roomName, role) => {
		set({ state: "connecting" });

		try {
			// Clean old room
			const connectedRoom = get().room;
			if (interval) clearInterval(interval);
			if (connectedRoom) {
				connectedRoom.disconnect();
				connectedRoom.removeAllListeners();
			}

			const url = process.env.NEXT_PUBLIC_LIVEKIT_URL;
			if (!url) return console.error("NEXT_PUBLIC_LIVEKIT_URL not set");

			const token = await getToken(roomName);
			if (!token) throw new Error("Fehlende Berechtigung");
			const room = new Room({});
			await room.prepareConnection(url, token);
			room
				// Connection events
				.on(RoomEvent.Connected, async () => {
					const dispatchState = useDispatchConnectionStore.getState();

					if (dispatchState.status === "connected" && dispatchState.connectedDispatcher?.id) {
						changeDispatcherAPI(dispatchState.connectedDispatcher?.id, {
							zone: roomName,
							ghostMode: dispatchState.ghostMode,
						});
					}

					const inputStream = await navigator.mediaDevices.getUserMedia({
						audio: {
							deviceId: get().settings.micDeviceId ?? undefined,
						},
					});
					// Funk-Effekt anwenden
					const radioStream = getRadioStream(inputStream, get().settings.micVolume);
					if (!radioStream) throw new Error("Konnte Funkstream nicht erzeugen");

					const [track] = radioStream.getAudioTracks();
					if (!track) throw new Error("Konnte Audio-Track nicht erzeugen");

					const publishedTrack = await room.localParticipant.publishTrack(track, {
						name: "radio-audio",
						source: Track.Source.Microphone,
					});
					await publishedTrack.mute();

					set({ localRadioTrack: publishedTrack });

					set({ state: "connected", room, isTalking: false, message: null });
				})
				.on(RoomEvent.Disconnected, () => {
					set({
						state: "disconnected",
						speakingParticipants: [],
						transmitBlocked: false,
						isTalking: false,
					});

					handleDisconnect();
				})
				.on(RoomEvent.ConnectionQualityChanged, (connectionQuality) => set({ connectionQuality }))

				// Track events
				.on(RoomEvent.TrackSubscribed, handleTrackSubscribed)
				.on(RoomEvent.TrackUnsubscribed, handleTrackUnsubscribed)
				.on(RoomEvent.LocalTrackUnpublished, handleLocalTrackUnpublished);
			await room.connect(url, token, {});

			room.localParticipant.setAttributes({
				role,
			});
			set({ room });

			room.registerRpcMethod("force-mute", async (data: RpcInvocationData) => {
				const { by } = JSON.parse(data.payload);
				get().resetSpeakingParticipants(by);
				return "OK";
			});

			interval = setInterval(() => {
				// Filter forgotten participants
				const oldSpeakingParticipants = get().speakingParticipants;
				const speakingParticipants = oldSpeakingParticipants.filter((oP) => {
					return Array.from(room.remoteParticipants.values()).find(
						(p) => p.identity === oP.identity,
					);
				});

				if (oldSpeakingParticipants.length !== speakingParticipants.length) {
					set({
						remoteParticipants: room.numParticipants === 0 ? 0 : room.numParticipants - 1, // Unreliable and delayed
						speakingParticipants,
					});
				} else {
					set({
						remoteParticipants: room.numParticipants === 0 ? 0 : room.numParticipants - 1, // Unreliable and delayed
					});
				}
			}, 500);
		} catch (error: Error | unknown) {
			console.error("Error occured: ", error);
			if (error instanceof Error) {
				set({ state: "error", message: error.message });
			} else {
				set({ state: "error", message: "Unknown error" });
			}
		}
	},
	disconnect: () => {
		get().room?.disconnect();
	},
}));

interface PTTData {
	shouldTransmit: boolean;
	source: string;
}

const handlePTT = (data: PTTData) => {
	const { shouldTransmit } = data;
	const { room, speakingParticipants } = useAudioStore.getState();
	if (!room) return;

	if (speakingParticipants.length > 0 && shouldTransmit) {
		// Wenn andere sprechen, nicht reden
		useAudioStore.setState({
			message: "Rufgruppe besetzt",
			transmitBlocked: true,
		});
		return;
	}
	const { status: DispatcherConnectionStatus } = useDispatchConnectionStore.getState();
	const { status: PilotConnectionStatus } = usePilotConnectionStore.getState();
	if (
		shouldTransmit &&
		!(DispatcherConnectionStatus === "connected" || PilotConnectionStatus === "connected")
	) {
		useAudioStore.setState({
			message: "Keine Verbindung",
		});
		return;
	}

	useAudioStore.setState({
		isTalking: shouldTransmit,
		transmitBlocked: false,
	});

	if (shouldTransmit) {
		room.localParticipant.setMicrophoneEnabled(true);
	} else {
		room.localParticipant.setMicrophoneEnabled(false);
	}
};

pilotSocket.on("ptt", handlePTT);
dispatchSocket.on("ptt", handlePTT);
