import { PageServer } from './test-page-server.mjs'
import { assert_equal, assert } from './assert.mjs'
import { BackendServer } from './backend-server.mjs'

//TODO make sure we don't accept private diffs from external url maybe?



async function sleep(ms) {
	return await new Promise(resolve => setTimeout(resolve, ms))
}

async function get_loaded_timestamp(browser) {
	return await (await browser.$('#loaded-timestamp')).getText()
}

async function state_change(browser, initial_state_timestamp) {
	return await browser.waitUntil(async () => { return (await (await browser.$('#change-counter')).getText()) != initial_state_timestamp })
}

async function url_shortened(browser) {
	await browser.execute(async () => await window.url_shortened() )
}

async function get_state_timestamp(browser) {
	return await (await browser.$('#change-counter')).getText()
}

async function assert_end_state(browser, initial_state_counter, correct_end_state) {
	if (correct_end_state === undefined) {
		await new Promise(resolve => setTimeout(resolve, 200))
		assert_equal(1, 4, initial_state_counter, await get_state_timestamp(browser))
	} else {
		await state_change(browser, initial_state_counter)
		let end_state = await (await browser.$('#state')).getText()
		assert_equal(1, 4, correct_end_state, JSON.parse(end_state))
	}
}

async function open(browser, starting_url, correct_initial_state) {
	await browser.url(starting_url)
	await browser.waitUntil(async () => { return (await (await browser.$('#load')).getText()) === 'loaded' })
	await state_change(browser, "never")
	const initial_state_json = await (await browser.$('#state')).getText()
	let initial_state = JSON.parse(initial_state_json)
	assert_equal(1, 4, correct_initial_state, initial_state)
	return await get_state_timestamp(browser)
}

async function refresh(browser, correct_end_state) {
	await browser.refresh()
	await browser.waitUntil(async () => { return (await (await browser.$('#load')).getText()) === 'loaded' })
	await state_change(browser, "never")
	const initial_state_json = await (await browser.$('#state')).getText()
	let initial_state = JSON.parse(initial_state_json)
	assert_equal(1, 4, correct_end_state, initial_state)
	return await get_state_timestamp(browser)
}

async function click(browser, initial_state_counter, click_target, correct_end_state) {
	await (await browser.$(click_target)).click()
	await assert_end_state(browser, initial_state_counter, correct_end_state)
	return await get_state_timestamp(browser)
}

async function back(browser, initial_state_counter, correct_end_state) {
	await browser.back()
	await assert_end_state(browser, initial_state_counter, correct_end_state)
	return await get_state_timestamp(browser)
}

async function forward(browser, initial_state_counter, correct_end_state) {
	await browser.forward()
	await assert_end_state(browser, initial_state_counter, correct_end_state)
	return await get_state_timestamp(browser)
}

async function open_click_refresh(browser, starting_url, correct_initial_state, click_target, correct_end_state, click_should_reload) {
	const initial_state_counter = await open(browser, starting_url, correct_initial_state)
	const initial_loaded_timestamp = await get_loaded_timestamp(browser)
	await url_shortened(browser)
	await click(browser, initial_state_counter, click_target, correct_end_state)
	await url_shortened(browser)
	const loaded_timestamp_after_click = await get_loaded_timestamp(browser)
	if (click_should_reload && initial_loaded_timestamp == loaded_timestamp_after_click) throw "click should have reloaded but didn't"
	if (!click_should_reload && initial_loaded_timestamp != loaded_timestamp_after_click) throw "click shouldn't have reloaded but did"
	await refresh(browser, correct_end_state || correct_initial_state)
}

async function test_all_open_and_navigate(browser, dynamic, starting_url, correct_initial_state, correct_state_after_noop, correct_state_after_append, correct_state_after_remove, correct_state_after_too_long_for_href, correct_state_after_append_private_internal, correct_state_after_append_private_external, correct_state_after_private_remove_internal, correct_state_after_private_remove_external) {
	await open_click_refresh(browser, starting_url, correct_initial_state, '#no-op .internal-href', correct_state_after_noop, false)
	if (dynamic) await open_click_refresh(browser, starting_url, correct_initial_state, '#no-op .external-href', correct_state_after_noop, true)
	await open_click_refresh(browser, starting_url, correct_initial_state, '#no-op .external-url', correct_state_after_noop, true)
	await open_click_refresh(browser, starting_url, correct_initial_state, '#no-op .internal-js', correct_state_after_noop, false)

	await open_click_refresh(browser, starting_url, correct_initial_state, '#append .internal-href', correct_state_after_append, false)
	if (dynamic) await open_click_refresh(browser, starting_url, correct_initial_state, '#append .external-href', correct_state_after_append, true)
	await open_click_refresh(browser, starting_url, correct_initial_state, '#append .external-url', correct_state_after_append, true)
	await open_click_refresh(browser, starting_url, correct_initial_state, '#append .internal-js', correct_state_after_append, false)

	await open_click_refresh(browser, starting_url, correct_initial_state, '#remove .internal-href', correct_state_after_remove, false)
	if (dynamic) await open_click_refresh(browser, starting_url, correct_initial_state, '#remove .external-href', correct_state_after_remove, true)
	await open_click_refresh(browser, starting_url, correct_initial_state, '#remove .external-url', correct_state_after_remove, true)
	await open_click_refresh(browser, starting_url, correct_initial_state, '#remove .internal-js', correct_state_after_remove, false)

	await open_click_refresh(browser, starting_url, correct_initial_state, '#too-long-for-href .internal-href', correct_state_after_too_long_for_href, false)
	if (dynamic) await open_click_refresh(browser, starting_url, correct_initial_state, '#too-long-for-href .external-href', undefined, false)
	await open_click_refresh(browser, starting_url, correct_initial_state, '#too-long-for-href .external-url', undefined, false)
	await open_click_refresh(browser, starting_url, correct_initial_state, '#too-long-for-href .internal-js', correct_state_after_too_long_for_href, false)

	await open_click_refresh(browser, starting_url, correct_initial_state, '#append-private .internal-href', correct_state_after_append_private_internal, false)
	if (dynamic) await open_click_refresh(browser, starting_url, correct_initial_state, '#append-private .external-href', correct_state_after_append_private_external, true)
	await open_click_refresh(browser, starting_url, correct_initial_state, '#append-private .external-url', correct_state_after_append_private_external, true)
	await open_click_refresh(browser, starting_url, correct_initial_state, '#append-private .internal-js', correct_state_after_append_private_internal, false)

	await open_click_refresh(browser, starting_url, correct_initial_state, '#remove-private .internal-href', correct_state_after_private_remove_internal, false)
	if (dynamic) await open_click_refresh(browser, starting_url, correct_initial_state, '#remove-private .external-href', correct_state_after_private_remove_external, true)
	await open_click_refresh(browser, starting_url, correct_initial_state, '#remove-private .external-url', correct_state_after_private_remove_external, true)
	await open_click_refresh(browser, starting_url, correct_initial_state, '#remove-private .internal-js', correct_state_after_private_remove_internal, false)

	//await browser.debug()
}

async function test_basic_clicks(browser, url_base, dynamic) {
	url_base = url_base + (dynamic ? "dynamic-browser.html" : "static-browser.html")
	await test_all_open_and_navigate(browser,
		dynamic,
		url_base,
		{ "type": "NavigationState", "state": { "shared": {}, "private": {} } },
		{"type":"NavigationState","state":{"shared":{},"private":{}}},
		{"type":"NavigationState","state":{"shared":{"appended": "x"},"private":{}}},
		{"type":"NavigationState","state":{"shared":{},"private":{}}},
		{"type":"NavigationState","state":{"shared":{"appended-too-long": "x".repeat(2000)},"private":{}}},
		{"type":"NavigationState","state":{"shared":{},"private":{'appended-private': 'px'}}},
		{"type":"NavigationState","state":{"shared":{},"private":{}}},
		{"type":"NavigationState","state":{"shared":{},"private":{}}},
		{"type":"NavigationState","state":{"shared":{},"private":{}}},
	)
	await test_all_open_and_navigate(browser,
		dynamic,
		url_base + (dynamic ? "/b/" : "?"),
		{ "type": "NavigationState", "state": { "shared": {}, "private": {} } },
		{"type":"NavigationState","state":{"shared":{},"private":{}}},
		{"type":"NavigationState","state":{"shared":{"appended": "x"},"private":{}}},
		{"type":"NavigationState","state":{"shared":{},"private":{}}},
		{"type":"NavigationState","state":{"shared":{"appended-too-long": "x".repeat(2000)},"private":{}}},
		{"type":"NavigationState","state":{"shared":{},"private":{'appended-private': 'px'}}},
		{"type":"NavigationState","state":{"shared":{},"private":{}}},
		{"type":"NavigationState","state":{"shared":{},"private":{}}},
		{"type":"NavigationState","state":{"shared":{},"private":{}}},
	)
	await test_all_open_and_navigate(browser,
		dynamic,
		url_base + (dynamic ? "/b/removed/abc" : "?b&removed=abc"),
		{"type":"NavigationState","state":{"shared":{"removed":"abc"},"private":{}}},
		{"type":"NavigationState","state":{"shared":{"removed":"abc"},"private":{}}},
		{"type":"NavigationState","state":{"shared":{"removed":"abc","appended": "x"},"private":{}}},
		{"type":"NavigationState","state":{"shared":{},"private":{}}},
		{"type":"NavigationState","state":{"shared":{"removed":"abc","appended-too-long": "x".repeat(2000)},"private":{}}},
		{"type":"NavigationState","state":{"shared":{"removed":"abc"},"private":{'appended-private': 'px'}}},
		{"type":"NavigationState","state":{"shared":{"removed":"abc"},"private":{}}},
		{"type":"NavigationState","state":{"shared":{"removed":"abc"},"private":{}}},
		{"type":"NavigationState","state":{"shared":{"removed":"abc"},"private":{}}},
	)
	await test_all_open_and_navigate(browser,
		dynamic,
		url_base + (dynamic ? "?b&removed=abc" : "#b&removed=abc"),
		{"type":"NavigationState","state":{"shared":{"removed":"abc"},"private":{}}},
		{"type":"NavigationState","state":{"shared":{"removed":"abc"},"private":{}}},
		{"type":"NavigationState","state":{"shared":{"removed":"abc","appended": "x"},"private":{}}},
		{"type":"NavigationState","state":{"shared":{},"private":{}}},
		{"type":"NavigationState","state":{"shared":{"removed":"abc","appended-too-long": "x".repeat(2000)},"private":{}}},
		{"type":"NavigationState","state":{"shared":{"removed":"abc"},"private":{'appended-private': 'px'}}},
		{"type":"NavigationState","state":{"shared":{"removed":"abc"},"private":{}}},
		{"type":"NavigationState","state":{"shared":{"removed":"abc"},"private":{}}},
		{"type":"NavigationState","state":{"shared":{"removed":"abc"},"private":{}}},
	)
	await test_all_open_and_navigate(browser,
		dynamic,
		url_base + (dynamic ? "/b/removed/abc?d=e" : "?b&removed=abc#d=e"),
		{"type":"NavigationState","state":{"shared":{"removed":"abc","d":"e"},"private":{}}},
		{"type":"NavigationState","state":{"shared":{"removed":"abc","d":"e"},"private":{}}},
		{"type":"NavigationState","state":{"shared":{"removed":"abc","appended": "x","d":"e"},"private":{}}},
		{"type":"NavigationState","state":{"shared":{"d":"e"},"private":{}}},
		{"type":"NavigationState","state":{"shared":{"removed":"abc","d":"e","appended-too-long": "x".repeat(2000)},"private":{}}},
		{"type":"NavigationState","state":{"shared":{"removed":"abc","d":"e"},"private":{'appended-private': 'px'}}},
		{"type":"NavigationState","state":{"shared":{"removed":"abc","d":"e"},"private":{}}},
		{"type":"NavigationState","state":{"shared":{"removed":"abc","d":"e"},"private":{}}},
		{"type":"NavigationState","state":{"shared":{"removed":"abc","d":"e"},"private":{}}},
	)
	await test_all_open_and_navigate(browser,
		dynamic,
		url_base + (dynamic ? "/==NOT-SHAREABLE-unknown" : "?==NOT-SHAREABLE-unknown") ,
		{ type: 'NavigationError', kind: 'URLNotShareable', message: 'The URL is not sharable' },
		{"type":"NavigationState","state":{"shared":{removed: 'abc', error: 'URLNotShareable'},"private":{}}},
		{"type":"NavigationState","state":{"shared":{removed: 'abc', error: 'URLNotShareable', "appended": "x"},"private":{}}},
		{"type":"NavigationState","state":{"shared":{error: 'URLNotShareable'},"private":{}}},
		{"type":"NavigationState","state":{"shared":{removed: 'abc', error: 'URLNotShareable', "appended-too-long": "x".repeat(2000)},"private":{}}},
		{"type":"NavigationState","state":{"shared":{removed: 'abc', error: 'URLNotShareable'},"private":{'appended-private': 'px'}}},
		{"type":"NavigationState","state":{"shared":{removed: 'abc', error: 'URLNotShareable'},"private":{}}},
		{"type":"NavigationState","state":{"shared":{removed: 'abc', error: 'URLNotShareable'},"private":{}}},
		{"type":"NavigationState","state":{"shared":{removed: 'abc', error: 'URLNotShareable'},"private":{}}},
	)
	await test_all_open_and_navigate(browser,
		dynamic,
		url_base + (dynamic ? "/b/removed/abc/scope.limited_value_1/d/limited_value_2/b/" : "?b&removed=abc&scope.limited_value_1=d&limited_value_2=b"),
		{"type":"NavigationState","state":{"shared":{"removed":"abc","limited_value_2":"b"},"private":{}}},
		{"type":"NavigationState","state":{"shared":{"removed":"abc","limited_value_2":"b"},"private":{}}},
		{"type":"NavigationState","state":{"shared":{"removed":"abc","limited_value_2":"b", "appended": "x"},"private":{}}},
		{"type":"NavigationState","state":{"shared":{"limited_value_2":"b"},"private":{}}},
		{"type":"NavigationState","state":{"shared":{"removed":"abc","appended-too-long": "x".repeat(2000), "limited_value_2":"b"},"private":{}}},
		{"type":"NavigationState","state":{"shared":{"removed":"abc","limited_value_2":"b"},"private":{'appended-private': 'px'}}},
		{"type":"NavigationState","state":{"shared":{"removed":"abc","limited_value_2":"b"},"private":{}}},
		{"type":"NavigationState","state":{"shared":{"removed":"abc","limited_value_2":"b"},"private":{}}},
		{"type":"NavigationState","state":{"shared":{"removed":"abc","limited_value_2":"b"},"private":{}}},
	)
	await test_all_open_and_navigate(browser,
		dynamic,
		url_base + (dynamic ? "/b/removed/abc/scope.limited_value_1/b/limited_value_2/d/" : "?b&removed=abc&scope.limited_value_1=b&limited_value_2=d"),
		{"type":"NavigationState","state":{"shared":{"removed":"abc","scope":{"shared":{"limited_value_1":"b"},"private":{}}},"private":{}}},
		{"type":"NavigationState","state":{"shared":{"removed":"abc","scope":{"shared":{"limited_value_1":"b"},"private":{}}},"private":{}}},
		{"type":"NavigationState","state":{"shared":{"removed":"abc","scope":{"shared":{"limited_value_1":"b"},"private":{}},"appended":"x"},"private":{}}},
		{"type":"NavigationState","state":{"shared":{"scope":{"shared":{"limited_value_1":"b"},"private":{}}},"private":{}}},
		{"type":"NavigationState","state":{"shared":{"removed":"abc","appended-too-long": "x".repeat(2000), "scope":{"shared":{"limited_value_1":"b"},"private":{}}},"private":{}}},
		{"type":"NavigationState","state":{"shared":{"removed":"abc","scope":{"shared":{"limited_value_1":"b"},"private":{}}},"private":{'appended-private': 'px'}}},
		{"type":"NavigationState","state":{"shared":{"removed":"abc","scope":{"shared":{"limited_value_1":"b"},"private":{}}},"private":{}}},
		{"type":"NavigationState","state":{"shared":{"removed":"abc","scope":{"shared":{"limited_value_1":"b"},"private":{}}},"private":{}}},
		{"type":"NavigationState","state":{"shared":{"removed":"abc","scope":{"shared":{"limited_value_1":"b"},"private":{}}},"private":{}}},
	)
	// TODO: can this happen before we introduce new url version?
	// await test_all_open_and_navigate(browser,
	//  dynamic,
	// 	url_base + "/==NOT-SHAREABLE-known",
	// 	{ type: 'NavigationError', kind: 'URLNotShareable', message: 'The URL is not sharable' },
	// 	{"type":"NavigationState","state":{"shared":{removed: 'abc', error: 'URLNotShareable'},"private":{}}},
	// 	{"type":"NavigationState","state":{"shared":{removed: 'abc', error: 'URLNotShareable', "appended": "x"},"private":{}}},
	// 	{"type":"NavigationState","state":{"shared":{error: 'URLNotShareable'},"private":{}}},
	// 	{"type":"NavigationState","state":{"shared":{removed: 'abc', error: 'URLNotShareable', "appended-too-long": "x".repeat(2000)},"private":{}}},
	// 	{"type":"NavigationState","state":{"shared":{removed: 'abc', error: 'URLNotShareable'},"private":{'appended-private': 'px'}}},
	// 	{"type":"NavigationState","state":{"shared":{removed: 'abc', error: 'URLNotShareable'},"private":{}}},
	// 	{"type":"NavigationState","state":{"shared":{removed: 'abc', error: 'URLNotShareable'},"private":{}}},
	// 	{"type":"NavigationState","state":{"shared":{removed: 'abc', error: 'URLNotShareable'},"private":{}}},
	// )
}


async function test_history_forward_and_backward(browser, url_base, dynamic) {
	url_base = url_base + (dynamic ? "dynamic-browser.html" : "static-browser.html")
	let state_counter = null

	state_counter = await open(browser, url_base, { "type": "NavigationState", "state": { "shared": {}, "private": {} } })
	await url_shortened(browser)
	state_counter = await click(browser, state_counter, '#append .internal-href', { "type": "NavigationState", "state": { "shared": { "appended": "x" }, "private": {} } })
	await url_shortened(browser)
	state_counter = await back(browser, state_counter, {"type":"NavigationState","state":{"shared":{},"private":{}}})
	await url_shortened(browser)
	state_counter = await forward(browser, state_counter, { "type": "NavigationState", "state": { "shared": { "appended": "x" }, "private": {} } })
	await url_shortened(browser)

	state_counter = await open(browser, url_base, {"type":"NavigationState","state":{"shared":{},"private":{}}})
	await url_shortened(browser)
	state_counter = await click(browser, state_counter, '#append .internal-href', { "type": "NavigationState", "state": { "shared": { "appended": "x" }, "private": {} } })
	await url_shortened(browser)
	state_counter = await click(browser, state_counter, '#append-scoped .internal-href', { "type": "NavigationState", "state": { "shared": { "appended": "x", "scope": { "shared": { "value": "x" }, "private": {} } }, "private": {} } })
	await url_shortened(browser)
	state_counter = await back(browser, state_counter, { "type": "NavigationState", "state": { "shared": { "appended": "x" }, "private": {} } })
	await url_shortened(browser)
	state_counter = await back(browser, state_counter, {"type":"NavigationState","state":{"shared":{},"private":{}}})
	await url_shortened(browser)
	state_counter = await forward(browser, state_counter, { "type": "NavigationState", "state": { "shared": { "appended": "x" }, "private": {} } })
	await url_shortened(browser)
	state_counter = await forward(browser, state_counter, { "type": "NavigationState", "state": { "shared": { "appended": "x", "scope": { "shared": { "value": "x" }, "private": {} } }, "private": {} } })
	await url_shortened(browser)

	//TODO ensure that appending exposed value doesn't change the shortened state key
}


async function test_delayed(browser, url_base, dynamic) {
	url_base = url_base + (dynamic ? "dynamic-browser.html" : "static-browser.html")

	let state_counter = await open(browser, url_base, { "type": "NavigationState", "state": { "shared": {}, "private": {} } })
	await url_shortened(browser)

	await (await browser.$('#append-delayed .internal-href')).click()
	await state_change(browser, state_counter)
	let end_state = await (await browser.$('#state')).getText()
	console.log(end_state)
	assert(1,4,JSON.parse(end_state).state.shared.appended != null)
	await url_shortened(browser)

	await browser.refresh()
	await browser.waitUntil(async () => { return (await (await browser.$('#load')).getText()) === 'loaded' })
	await state_change(browser, "never")
	end_state = await (await browser.$('#state')).getText()
	console.log(end_state)
	assert(1,4,JSON.parse(end_state).state.shared.appended == null)
}


export async function test_navigation_integrated(browser, ) {
	const server = new PageServer('tests/navigation-browser.html', 8008)
	await server.start()

	const url_base = "http://127.0.0.1:8008/"
	let backend = null

	try {
		await test_delayed(browser, url_base, false)
		await test_delayed(browser, url_base, true)

		await test_basic_clicks(browser, url_base, false)
		await test_history_forward_and_backward(browser, url_base, false)
		await test_basic_clicks(browser, url_base, true)
		await test_history_forward_and_backward(browser, url_base, true)

		backend = new BackendServer(3003)
		await backend.start()
		await test_basic_clicks(browser, url_base, false)
		await test_history_forward_and_backward(browser, url_base, false)
		await test_basic_clicks(browser, url_base, true)
		await test_history_forward_and_backward(browser, url_base, true)
		await backend.stop()

		// TODO: test scoped navigation
	} catch (error) {
		console.error(error)
		await browser.debug()
	} finally {
		await server.stop()
		if (backend != null) await backend.stop()
	}

}
