import { describe, expect, vi } from "vitest";
import {
	get_devices,
	get_video_stream,
	set_available_devices,
	set_local_stream
} from "./stream_utils";

let test_device: MediaDeviceInfo = {
	deviceId: "test-device",
	kind: "videoinput",
	label: "Test Device",
	groupId: "camera",
	toJSON: () => ({
		deviceId: "test-device",
		kind: "videoinput",
		label: "Test Device",
		groupId: "camera"
	})
};

const mock_enumerateDevices = vi.fn(async () => {
	return new Promise<MediaDeviceInfo[]>((resolve) => {
		resolve([test_device]);
	});
});
const mock_getUserMedia = vi.fn(async () => {
	return new Promise<MediaStream>((resolve) => {
		resolve(new MediaStream());
	});
});

class MockMediaStream extends EventTarget {
	id: string;
	active: boolean;

	constructor() {
		super();
		this.id = "mock-stream-id";
		this.active = true;
	}

	getTracks(): MediaStreamTrack[] {
		return [];
	}

	getAudioTracks(): MediaStreamTrack[] {
		return [];
	}

	getVideoTracks(): MediaStreamTrack[] {
		return [];
	}

	addTrack(): void {}
	removeTrack(): void {}
	getTrackById(): MediaStreamTrack | null {
		return null;
	}

	clone(): MediaStream {
		return this as unknown as MediaStream;
	}
}

// @ts-ignore - Override global MediaStream for testing
window.MediaStream = MockMediaStream as any;

Object.defineProperty(global.navigator, "mediaDevices", {
	value: {
		getUserMedia: mock_getUserMedia,
		enumerateDevices: mock_enumerateDevices
	}
});

describe("stream_utils", () => {
	test("get_devices should enumerate media devices", async () => {
		const devices = await get_devices();
		expect(devices).toEqual([test_device]);
	});

	test("set_local_stream should set the local stream to the video source", () => {
		const mock_stream = new MockMediaStream() as unknown as MediaStream;

		const mock_video_source = {
			srcObject: null as MediaStream | null,
			muted: false,
			play: vi.fn()
		};

		// @ts-ignore
		set_local_stream(mock_stream, mock_video_source);

		expect(mock_video_source.srcObject).toEqual(mock_stream);
		expect(mock_video_source.muted).toBeTruthy();
		expect(mock_video_source.play).toHaveBeenCalled();
	});

	test("get_video_stream requests user media with the correct constraints and sets the local stream", async () => {
		const mock_video_source = {
			srcObject: null as MediaStream | null,
			muted: false,
			play: vi.fn().mockResolvedValue(undefined)
		} as unknown as HTMLVideoElement;
		const mock_stream = new MockMediaStream() as unknown as MediaStream;

		global.navigator.mediaDevices.getUserMedia = vi
			.fn()
			.mockResolvedValue(mock_stream);

		await get_video_stream(true, mock_video_source, null);

		expect(navigator.mediaDevices.getUserMedia).toHaveBeenCalledWith({
			video: { width: { ideal: 1920 }, height: { ideal: 1440 } },
			audio: true
		});

		expect(mock_video_source.srcObject).toBe(mock_stream);
		expect(mock_video_source.muted).toBe(true);
		expect(mock_video_source.play).toHaveBeenCalled();
	});

	test("set_available_devices should return only video input devices", () => {
		const mockDevices: MediaDeviceInfo[] = [
			{
				deviceId: "camera1",
				kind: "videoinput",
				label: "Camera 1",
				groupId: "camera",
				toJSON: () => ({
					deviceId: "camera1",
					kind: "videoinput",
					label: "Camera 1",
					groupId: "camera"
				})
			},
			{
				deviceId: "camera2",
				kind: "videoinput",
				label: "Camera 2",
				groupId: "camera",
				toJSON: () => ({
					deviceId: "camera2",
					kind: "videoinput",
					label: "Camera 2",
					groupId: "camera"
				})
			},
			{
				deviceId: "audio1",
				kind: "audioinput",
				label: "Audio 2",
				groupId: "audio",
				toJSON: () => ({
					deviceId: "audio1",
					kind: "audioinput",
					label: "Audio 2",
					groupId: "audio"
				})
			}
		];

		const videoDevices = set_available_devices(mockDevices);
		expect(videoDevices).toEqual(mockDevices.splice(0, 2));
	});
});
