/**
 * Tests for the network-transport reporter behind the WiFi-only download gate.
 *
 * TRACES: UR-053 | DR-074 | UT-066
 */

import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';

const setNetworkState = vi.fn();
const getDownloadsAllowed = vi.fn();

vi.mock('$lib/api/bindings', () => ({
	commands: {
		setNetworkState: (...args: unknown[]) => setNetworkState(...args),
		getDownloadsAllowed: () => getDownloadsAllowed()
	}
}));

import {
	isNetworkDetectionSupported,
	reportNetworkState,
	startNetworkReporting,
	areDownloadsAllowed
} from './networkType';

/** Install a fake Android bridge on window. */
function installBridge(overrides: Partial<Record<string, unknown>> = {}) {
	const bridge = {
		currentType: vi.fn(() => 'wifi'),
		isUnmetered: vi.fn(() => true),
		isAcceptable: vi.fn(() => true),
		isSupported: vi.fn(() => true),
		...overrides
	};
	(window as unknown as Record<string, unknown>).AndroidNetworkType = bridge;
	return bridge;
}

function removeBridge() {
	delete (window as unknown as Record<string, unknown>).AndroidNetworkType;
}

describe('networkType service', () => {
	beforeEach(() => {
		vi.clearAllMocks();
		setNetworkState.mockResolvedValue(null);
		getDownloadsAllowed.mockResolvedValue(true);
		removeBridge();
	});

	afterEach(() => {
		removeBridge();
	});

	describe('isNetworkDetectionSupported', () => {
		it('is false with no Android bridge (desktop)', () => {
			expect(isNetworkDetectionSupported()).toBe(false);
		});

		it('is true when the Android bridge is present', () => {
			installBridge();
			expect(isNetworkDetectionSupported()).toBe(true);
		});

		it('is false when the bridge throws', () => {
			installBridge({
				isSupported: vi.fn(() => {
					throw new Error('bridge exploded');
				})
			});
			expect(isNetworkDetectionSupported()).toBe(false);
		});
	});

	describe('reportNetworkState', () => {
		it('does not call the backend on desktop', async () => {
			await reportNetworkState();
			expect(setNetworkState).not.toHaveBeenCalled();
		});

		it('reports transport and metered-ness from the bridge', async () => {
			installBridge({
				currentType: vi.fn(() => 'cellular'),
				isUnmetered: vi.fn(() => false)
			});

			await reportNetworkState();

			expect(setNetworkState).toHaveBeenCalledWith({
				networkType: 'cellular',
				unmetered: false
			});
		});

		it('reports metered WiFi as WiFi-but-metered, not as unmetered', async () => {
			// A phone hotspot: WiFi transport, metered connection.
			installBridge({
				currentType: vi.fn(() => 'wifi'),
				isUnmetered: vi.fn(() => false)
			});

			await reportNetworkState();

			expect(setNetworkState).toHaveBeenCalledWith({
				networkType: 'wifi',
				unmetered: false
			});
		});

		it('swallows backend errors so the UI never breaks', async () => {
			installBridge();
			setNetworkState.mockRejectedValue(new Error('ipc down'));

			await expect(reportNetworkState()).resolves.toBeUndefined();
		});
	});

	describe('startNetworkReporting', () => {
		it('reports once immediately and again on network change', async () => {
			installBridge();

			const stop = startNetworkReporting();
			await vi.waitFor(() => expect(setNetworkState).toHaveBeenCalledTimes(1));

			window.dispatchEvent(new CustomEvent('jellytau-network-changed'));
			await vi.waitFor(() => expect(setNetworkState).toHaveBeenCalledTimes(2));

			stop();
		});

		it('stops reporting after teardown', async () => {
			installBridge();

			const stop = startNetworkReporting();
			await vi.waitFor(() => expect(setNetworkState).toHaveBeenCalledTimes(1));
			stop();

			window.dispatchEvent(new CustomEvent('jellytau-network-changed'));
			// Give any stray listener a chance to fire before asserting.
			await new Promise((resolve) => setTimeout(resolve, 10));
			expect(setNetworkState).toHaveBeenCalledTimes(1);
		});
	});

	describe('areDownloadsAllowed', () => {
		it('returns the backend verdict', async () => {
			getDownloadsAllowed.mockResolvedValue(false);
			expect(await areDownloadsAllowed()).toBe(false);
		});

		it('fails open if the query errors, so the UI never falsely blames WiFi', async () => {
			getDownloadsAllowed.mockRejectedValue(new Error('ipc down'));
			expect(await areDownloadsAllowed()).toBe(true);
		});
	});
});
