from __future__ import annotations

import base64

from pygitweb.static_tests.js_runtime import JsPage
from pygitweb.static_tests.pages import requested

_B64 = base64.b64encode(b"# Demo\n").decode("ascii")
_STATE = {
	"commit_href": "/commit",
	"display": "abc",
	"browse_href": "/tree",
	"log_href": "/log",
	"shortlog_href": "/shortlog",
	"tree_html": "<span class='tree'>files</span>",
	"readme_html": "<p>hi</p>",
}


def _page(*, project: str = "demo", full: bool = True) -> str:
	links = (
		'<a id="summary-ref-log-link"></a><a id="summary-ref-shortlog-link"></a>'
		'<div id="summary-tree-container"></div><a id="summary-tree-browse-link"></a>'
		if full
		else ""
	)
	return (
		'<select id="summary-ref-select"><option value="HEAD">HEAD</option>'
		'<option value="old" selected>old</option></select>'
		'<a id="summary-ref-commit-link"></a><a id="summary-ref-browse-link"></a>'
		+ links
		+ f'<div id="summary-readme-container" data-project="{project}"></div>'
		+ f'<div class="page"></div><div id="readme-container" data-readme-b64="{_B64}"></div>'
	)


def test_missing_controls_skip_startup(js: JsPage) -> None:
	js.set_html("<div></div>")
	js.load("summary-ref-switcher.js")
	assert requested(js, "summary-refs") is False


def test_missing_project_skips_startup(js: JsPage) -> None:
	js.set_html(_page(project=""))
	js.load("summary-ref-switcher.js")
	assert requested(js, "summary-refs") is False


def test_options_replace_stale_entries_and_keep_a_live_value(js: JsPage) -> None:
	js.route("summary-refs", body={"options": [{"value": "old", "kind": "branch", "label": "old"}]})
	js.route("summary-ref-state", body=_STATE)
	js.set_html(_page())
	js.load("summary-ref-switcher.js")
	assert js.count("#summary-ref-select option") == 2
	assert js.prop("#summary-ref-select", "value") == "old"
	assert js.text("#summary-ref-select option:last-child") == "branch: old"


def test_a_removed_value_falls_back_to_head(js: JsPage) -> None:
	js.route("summary-refs", body={"options": [{"value": "main", "kind": "branch", "label": "main"}]})
	js.route("summary-ref-state", body=_STATE)
	js.set_html(_page())
	js.load("summary-ref-switcher.js")
	assert js.prop("#summary-ref-select", "value") == "HEAD"
	assert js.text("#summary-ref-select option:last-child") == "branch: main"


def test_failed_option_and_state_loads_leave_the_page_alone(js: JsPage) -> None:
	js.route("summary-refs", status=500)
	js.route("summary-ref-state", status=404)
	js.set_html(_page())
	js.load("summary-ref-switcher.js")
	js.set_prop("#summary-ref-select", "value", "HEAD")
	js.dispatch("#summary-ref-select", "change")
	assert js.text("#summary-ref-commit-link") == ""
	assert js.count("#summary-ref-select option") == 2


def test_change_loads_the_selected_ref(js: JsPage) -> None:
	js.route("summary-refs", body={"options": []})
	js.route("summary-ref-state", body=_STATE)
	js.set_html(_page(project="group/demo"))
	js.load("summary-ref-switcher.js")
	js.run(
		"document.getElementById('summary-ref-select').innerHTML = '<option value=\"feature/a\">feature/a</option>';"
	)
	js.dispatch("#summary-ref-select", "change")
	assert any("ref=feature%2Fa" in str(item["url"]) for item in js.requests())
	assert any("/project/group/demo/" in str(item["url"]) for item in js.requests())


def test_optional_links_are_skipped_when_absent(js: JsPage) -> None:
	js.route("summary-refs", body={"options": []})
	js.route("summary-ref-state", body=_STATE)
	js.set_html(_page(full=False))
	js.load("summary-ref-switcher.js")
	js.dispatch("#summary-ref-select", "change")
	assert js.text("#summary-ref-commit-link") == "abc"


def test_an_update_with_text_reloads_options_and_state(js: JsPage) -> None:
	js.route("summary-refs", body={"options": [{"value": "main", "kind": "branch", "label": "main"}]})
	js.route("summary-ref-state", body=_STATE)
	js.set_html(_page())
	js.load("summary-ref-switcher.js")
	before = len(js.requests())
	js.fulfill("updates=true", status=200, text="changed")
	assert len(js.requests()) > before
	assert js.text("#summary-ref-commit-link") == "abc"


def test_an_empty_update_does_not_reload(js: JsPage) -> None:
	js.route("summary-refs", body={"options": []})
	js.set_html(_page())
	js.load("summary-ref-switcher.js")
	refs = [item for item in js.requests() if "summary-refs" in str(item["url"])]
	js.fulfill("updates=true", status=200, text="")
	refs_after = [item for item in js.requests() if "summary-refs" in str(item["url"])]
	assert len(refs_after) == len(refs)


def test_not_modified_polls_again_without_a_backoff(js: JsPage) -> None:
	js.route("summary-refs", body={"options": []})
	js.set_html(_page())
	js.load("summary-ref-switcher.js")
	js.fulfill("updates=true", status=304, text="")
	updates = [item for item in js.requests() if "updates=true" in str(item["url"])]
	assert len(updates) >= 2
	assert js.value("__timers.length") == 0


def test_a_failed_update_backs_off_then_continues(js: JsPage) -> None:
	js.route("summary-refs", body={"options": []})
	js.set_html(_page())
	js.load("summary-ref-switcher.js")
	js.fulfill("updates=true", status=500, text="nope")
	assert js.value("__timers.length") == 1
	before = len([item for item in js.requests() if "updates=true" in str(item["url"])])
	js.tick()
	after = len([item for item in js.requests() if "updates=true" in str(item["url"])])
	assert after > before


def test_beforeunload_stops_the_subscription(js: JsPage) -> None:
	js.route("summary-refs", body={"options": []})
	js.set_html(_page())
	js.load("summary-ref-switcher.js")
	js.dispatch_window("beforeunload")
	assert js.value("__alerts") == []


def test_a_rejected_update_backs_off_until_abort(js: JsPage) -> None:
	js.route("updates=true", error="offline")
	js.route("summary-refs", body={"options": []})
	js.set_html(_page())
	js.load("summary-ref-switcher.js")
	assert js.value("__timers.length") >= 1
	js.dispatch_window("beforeunload")
	js.tick()
	assert js.text("#summary-ref-commit-link") == ""