"""A desktop client that cannot answer the ``tour`` server request must not cost a full
bridge timeout per call.

The renderer's handler ships in the desktop bundle; the tool is offered by the
backend. An app build older than the tour tool has no branch for the event, so
nothing ever answers the request and the agent blocks for the whole deadline
— once per action the model tries. See tui_gateway.server._tour_request.
"""

import json

import pytest

import tui_gateway.server as server


@pytest.fixture
def session(monkeypatch):
    record = {}
    monkeypatch.setitem(server._sessions, "s1", record)
    return record


@pytest.fixture
def bridge(monkeypatch):
    """Record every ``_ask`` (server request) call and serve canned answers."""
    calls = []

    def fake_block(event, sid, payload, timeout=None, **_kw):
        answer = fake_block.answers.pop(0) if fake_block.answers else ""
        calls.append({"event": event, "sid": sid, "payload": payload, "timeout": timeout})
        return answer

    fake_block.answers = []
    fake_block.calls = calls
    monkeypatch.setattr(server, "_ask", fake_block)
    return fake_block


def test_first_action_is_probed_on_a_short_deadline(session, bridge):
    bridge.answers = [json.dumps({"success": True})]
    server._tour_request("s1", {"action": "targets"})

    assert bridge.calls[0]["event"] == "tour"
    assert bridge.calls[0]["timeout"] == server._TOUR_PROBE_TIMEOUT_S
    assert server._TOUR_PROBE_TIMEOUT_S < server._TOUR_TIMEOUT_S


def test_a_client_that_answers_gets_the_full_deadline_back(session, bridge):
    """The generous deadline exists for a preview tour injecting into a live
    page; only an unproven client is held to the probe."""
    bridge.answers = [json.dumps({"success": True}), json.dumps({"success": True})]
    server._tour_request("s1", {"action": "targets"})
    server._tour_request("s1", {"action": "show", "selector": "#composer"})

    assert bridge.calls[1]["timeout"] == server._TOUR_TIMEOUT_S




def test_later_actions_short_circuit_instead_of_stalling_again(session, bridge):
    """The regression: a "give me a tour" turn stacks one timeout per action."""
    server._tour_request("s1", {"action": "targets"})
    for action in ("show", "start", "next", "stop"):
        assert json.loads(server._tour_request("s1", {"action": action}))["success"] is False

    assert len(bridge.calls) == 1


def test_a_proven_client_is_not_condemned_by_one_slow_action(session, bridge):
    """A transient miss on a live renderer must not disable the tour."""
    bridge.answers = [json.dumps({"success": True})]
    server._tour_request("s1", {"action": "targets"})
    server._tour_request("s1", {"action": "show", "selector": "#composer"})

    bridge.answers = [json.dumps({"success": True})]
    assert json.loads(server._tour_request("s1", {"action": "next"}))["success"] is True
    assert len(bridge.calls) == 3


def test_a_new_session_reprobes(bridge, monkeypatch):
    """The verdict lives on the session record, so it dies with the session."""
    monkeypatch.setitem(server._sessions, "dead", {})
    monkeypatch.setitem(server._sessions, "fresh", {})

    server._tour_request("dead", {"action": "targets"})
    bridge.answers = [json.dumps({"success": True})]

    assert json.loads(server._tour_request("fresh", {"action": "targets"}))["success"] is True


def test_a_session_with_no_record_still_bridges(bridge):
    """Detached callers have no session dict; they keep the plain bridge."""
    bridge.answers = [json.dumps({"success": True})]
    assert json.loads(server._tour_request("gone", {"action": "targets"}))["success"] is True
