from __future__ import annotations

import pytest

from pygitweb.static_tests.js_runtime import JsError, JsPage

_FORM = (
	'<input id="search-patterns">'
	'<input id="search-paths">'
	'<input id="search-globs">'
	'<select id="search-sort"><option value=""></option><option value="path">path</option></select>'
	'<input id="search-max-count">'
	'<input id="search-heading" type="checkbox">'
	'<input id="search-multiline" type="checkbox">'
	'<div id="search-status"></div>'
	'<div id="search-results"></div>'
	'<details id="search-advanced"></details>'
)


def _ready(js: JsPage, href: str = "http://127.0.0.1/search") -> None:
	js.run("window.SEARCH_API_URL = '/api/search';")
	js.set_location(href)
	js.set_html(_FORM)


def test_load_throws_when_a_control_is_missing(js: JsPage) -> None:
	js.run("window.SEARCH_API_URL = '/api/search';")
	js.set_html("<div></div>")
	with pytest.raises(JsError):
		js.load("search-page.js")


def test_read_url_fills_the_form_and_opens_advanced_filters(js: JsPage) -> None:
	js.route("/api/search", body={"results": []})
	_ready(
		js,
		"http://127.0.0.1/search?patterns=foo&paths=src&globs=*.py&sort=path&max_count=5&heading=true&multiline=true",
	)
	js.load("search-page.js")
	assert js.prop("#search-patterns", "value") == "foo"
	assert js.prop("#search-paths", "value") == "src"
	assert js.prop("#search-globs", "value") == "*.py"
	assert js.prop("#search-sort", "value") == "path"
	assert js.prop("#search-max-count", "value") == "5"
	assert js.prop("#search-heading", "checked") is True
	assert js.prop("#search-multiline", "checked") is True
	assert js.prop("#search-advanced", "open") is True


def test_empty_pattern_clears_results_and_the_query_string(js: JsPage) -> None:
	_ready(js, "http://127.0.0.1/search?patterns=old")
	js.load("search-page.js")
	js.set_prop("#search-patterns", "value", "  ")
	js.dispatch("#search-patterns", "keydown", key="Enter")
	assert js.text("#search-status") == ""
	assert js.prop("#search-results", "innerHTML") == ""
	assert "patterns" not in js.value("location.search")


def test_results_are_escaped_and_pluralized(js: JsPage) -> None:
	js.route("/api/search", body={"results": ["<script>", "ok"]})
	_ready(js, "http://127.0.0.1/search?patterns=foo")
	js.load("search-page.js")
	assert js.text("#search-status") == "2 matches"
	assert "&lt;script&gt;" in js.prop("#search-results", "innerHTML")
	assert "<script>" not in js.prop("#search-results", "innerHTML")
	assert "is-loading" not in js.classes("#search-results")


def test_a_single_match_uses_the_singular_label(js: JsPage) -> None:
	js.route("/api/search", body={"results": ["one"]})
	_ready(js, "http://127.0.0.1/search?patterns=foo&heading=false")
	js.set_html(_FORM)
	js.load("search-page.js")
	assert js.text("#search-status") == "1 match"
	url = next(str(item["url"]) for item in js.requests() if "/api/search" in str(item["url"]))
	assert "heading=false" in url
	assert "patterns=foo" in url


def test_missing_results_count_as_zero(js: JsPage) -> None:
	js.route("/api/search", body={})
	_ready(js, "http://127.0.0.1/search?patterns=foo")
	js.load("search-page.js")
	assert js.text("#search-status") == "0 matches"
	assert js.prop("#search-results", "innerHTML") == ""


def test_error_detail_is_shown(js: JsPage) -> None:
	js.route("/api/search", status=400, body={"detail": "bad pattern"}, status_text="Bad")
	_ready(js, "http://127.0.0.1/search?patterns=foo")
	js.load("search-page.js")
	assert js.text("#search-status") == "Error 400: bad pattern"


def test_error_without_json_uses_the_status_text(js: JsPage) -> None:
	js.route("/api/search", status=502, json_error=True, status_text="Bad Gateway")
	_ready(js, "http://127.0.0.1/search?patterns=foo")
	js.load("search-page.js")
	assert js.text("#search-status") == "Error 502: Bad Gateway"


def test_network_error_is_shown(js: JsPage) -> None:
	js.route("/api/search", error="offline")
	_ready(js, "http://127.0.0.1/search?patterns=foo")
	js.load("search-page.js")
	assert js.text("#search-status") == "Error: offline"
	assert "is-loading" not in js.classes("#search-results")


def test_a_newer_search_aborts_the_previous_one(js: JsPage) -> None:
	js.route("/api/search", hang=True)
	_ready(js, "http://127.0.0.1/search?patterns=foo")
	js.load("search-page.js")
	assert js.text("#search-status") == "Searching…"
	js.set_prop("#search-patterns", "value", "bar")
	js.dispatch("#search-patterns", "keydown", key="Enter")
	assert js.text("#search-status") == "Searching…"
	js.fulfill("/api/search", body={"results": ["bar"]})
	assert js.text("#search-status") == "1 match"
	assert "Error" not in js.text("#search-status")


def test_input_debounces_and_other_controls_search_too(js: JsPage) -> None:
	js.route("/api/search", body={"results": ["x"]})
	_ready(js)
	js.load("search-page.js")
	js.set_prop("#search-patterns", "value", "foo")
	js.set_prop("#search-paths", "value", "src")
	js.set_prop("#search-globs", "value", "*.py")
	js.set_prop("#search-max-count", "value", "")
	js.set_prop("#search-sort", "value", "path")
	js.set_prop("#search-heading", "checked", True)
	js.set_prop("#search-multiline", "checked", True)
	js.dispatch("#search-patterns", "input")
	assert js.text("#search-status") == ""
	js.tick()
	url = next(str(item["url"]) for item in js.requests() if "patterns=foo" in str(item["url"]))
	assert "paths=src" in url
	assert "globs=*.py" in url
	assert "sort=path" in url
	assert "max_count" not in url
	assert "heading=true" in url
	assert "multiline=true" in url
	assert js.value("location.search").find("patterns=foo") >= 0


def test_other_keys_do_not_run_a_search_immediately(js: JsPage) -> None:
	_ready(js)
	js.load("search-page.js")
	js.set_prop("#search-patterns", "value", "foo")
	js.dispatch("#search-patterns", "keydown", key="a")
	assert js.text("#search-status") == ""