"""ET-002 — тесты чекбокса показа/скрытия POI в попапе рельефа.

Изменение ET-002 — исключительно фронтендовое (`src/web/index.html`,
`src/web/app.js`). В CI исполняется только `pytest tests/`, поэтому файл
покрывает фичу двумя способами:

1. Статические проверки структуры `index.html` и `app.js` — выполняются
   всегда, без внешних зависимостей.
2. Поведенческие JS unit-тесты (TP-01..TP-04 из `04-test-plan.yaml`) —
   запускаются через встроенный тест-раннер Node (`node --test`). Если
   `node` в системе отсутствует — эта часть помечается `skip` (по аналогии
   с `tests/integration/test_routing_barriers.py::test_lua_syntax` и
   `luac`).

Браузерные e2e-сценарии (TP-05..TP-09) требуют Playwright-инфраструктуры,
которой в репозитории нет; добавление новых npm-пакетов запрещено
`07-infra-requirements.md`. Их поведенческая суть покрыта JS unit-тестами
и статическими проверками ниже.
"""

from __future__ import annotations

import subprocess
from pathlib import Path
from shutil import which

import pytest

REPO_ROOT = Path(__file__).resolve().parents[2]
INDEX_HTML = REPO_ROOT / "src" / "web" / "index.html"
APP_JS = REPO_ROOT / "src" / "web" / "app.js"
JS_TEST = REPO_ROOT / "tests" / "unit" / "poi_toggle.test.js"


def _index_html() -> str:
    assert INDEX_HTML.is_file(), f"не найден {INDEX_HTML}"
    return INDEX_HTML.read_text(encoding="utf-8")


def _app_js() -> str:
    assert APP_JS.is_file(), f"не найден {APP_JS}"
    return APP_JS.read_text(encoding="utf-8")


# ──────────────────────────────────────────────────────────────────────────────
# Статические проверки index.html (REQ-F-01, UI-спецификация)
# ──────────────────────────────────────────────────────────────────────────────

def test_poi_checkbox_present_in_html():
    """REQ-F-01: чекбокс POI присутствует в попапе с корректными атрибутами."""
    html = _index_html()
    assert 'id="poi-visible-cb"' in html, "нет чекбокса poi-visible-cb"
    assert 'onchange="onPoiCheckbox()"' in html, "чекбокс не привязан к onPoiCheckbox()"
    assert "<span>POI</span>" in html, "нет подписи «POI»"


def test_poi_checkbox_checked_by_default():
    """REQ-F-02: чекбокс POI отрисован как checked (POI видны по умолчанию)."""
    html = _index_html()
    # Атрибут checked должен стоять именно на инпуте poi-visible-cb.
    start = html.index('id="poi-visible-cb"')
    tag_end = html.index(">", start)
    assert "checked" in html[start:tag_end], "у чекбокса POI нет атрибута checked"


def test_poi_checkbox_placed_after_trails_separated_by_hr():
    """REQ-F-01: чекбокс POI стоит после «Тропы» и отделён <hr>."""
    html = _index_html()
    trails_pos = html.index('id="trails-path-cb"')
    poi_pos = html.index('id="poi-visible-cb"')
    assert poi_pos > trails_pos, "POI должен идти после чекбокса «Тропы»"
    between = html[trails_pos:poi_pos]
    assert "<hr" in between, "POI не отделён горизонтальной линией <hr>"


def test_poi_checkbox_uses_shared_style_class():
    """UI-спецификация: чекбокс использует общий класс terrain-checkbox."""
    html = _index_html()
    start = html.index('id="poi-visible-cb"')
    label_start = html.rfind("<label", 0, start)
    label_open_end = html.index(">", label_start)
    assert 'class="terrain-checkbox"' in html[label_start:label_open_end], (
        "чекбокс POI должен быть в label с классом terrain-checkbox"
    )


# ──────────────────────────────────────────────────────────────────────────────
# Статические проверки app.js (ADR-0001)
# ──────────────────────────────────────────────────────────────────────────────

def test_poi_functions_defined():
    """ADR-0001: определены хелпер и обработчики POI."""
    js = _app_js()
    for fn in ("applyPoiVisibility", "onPoiCheckbox", "restorePoiState"):
        assert f"function {fn}(" in js, f"не определена функция {fn}()"


def test_poi_logic_uses_localstorage_key():
    """REQ-F-05: персистентность через localStorage ключ poi-visible."""
    js = _app_js()
    assert "localStorage.setItem('poi-visible'" in js, "состояние POI не сохраняется"
    assert "localStorage.getItem('poi-visible')" in js, "состояние POI не читается"


def test_poi_logic_reuses_layer_state_and_groups():
    """ADR-0001 п.3-4: источник истины — layerState.poi, группа слоёв не дублируется."""
    js = _app_js()
    assert "layerState.poi" in js, "POI-логика не синхронизирует layerState.poi"
    assert "layerGroups.poi" in js, "POI-логика должна переиспользовать layerGroups.poi"
    # poi-группа объявлена ровно один раз — в общей карте layerGroups.
    assert js.count("poi:     ['poi-circles', 'poi-labels']") == 1, (
        "карта групп слоёв POI задублирована"
    )


def test_restore_poi_state_wired_into_init():
    """REQ-F-06: restorePoiState() вызывается при инициализации/смене стиля."""
    js = _app_js()
    # Один def + минимум 3 вызова (rebuildMapOverlays + 2 ветки initTerrain).
    assert js.count("restorePoiState()") >= 4, (
        "restorePoiState() не подключён ко всем точкам восстановления"
    )


def test_poi_visibility_toggled_via_set_layout_property():
    """ADR-0001 п.1: видимость переключается через setLayoutProperty."""
    js = _app_js()
    marker = "// >>> ET-002 POI visibility block"
    end = "// <<< ET-002 POI visibility block"
    assert marker in js and end in js, "маркеры POI-блока отсутствуют"
    block = js[js.index(marker):js.index(end)]
    assert "setLayoutProperty" in block, "POI-блок не использует setLayoutProperty"
    assert "removeLayer" not in block, (
        "POI-блок не должен удалять слои (ADR-0001 отвергает вариант C)"
    )


# ──────────────────────────────────────────────────────────────────────────────
# Поведенческие JS unit-тесты через Node (TP-01..TP-04)
# ──────────────────────────────────────────────────────────────────────────────

node_required = pytest.mark.skipif(
    which("node") is None,
    reason="node не установлен — поведенческие JS unit-тесты пропущены",
)


@node_required
def test_js_unit_tests_pass():
    """TP-01..TP-04: запускает behavioral JS-тесты POI через `node --test`."""
    assert JS_TEST.is_file(), f"не найден JS-тест {JS_TEST}"
    node = which("node")
    result = subprocess.run(
        [node, "--test", str(JS_TEST)],
        capture_output=True,
        text=True,
        cwd=str(REPO_ROOT),
    )
    assert result.returncode == 0, (
        f"JS unit-тесты POI упали (код {result.returncode}):\n"
        f"{result.stdout}\n{result.stderr}"
    )
