"""Proactive phone events handled in main.Session: new-location debounce +
prompt, geofence→place routines, and SMS watch-list gating. No network — the
phone store is stubbed and _deliver_proactive is captured."""
import main
import phone_store


class FakeWS:
    def __init__(self):
        self.sent = []

    async def send_json(self, frame):
        self.sent.append(frame)

    async def send_bytes(self, data):
        pass


def _session(monkeypatch):
    sess = main.Session(ws=FakeWS())
    sess.mode = "chat"
    delivered = []

    async def rec(injected):
        delivered.append(injected)
    monkeypatch.setattr(sess, "_deliver_proactive", rec)
    return sess, delivered


def _wire_store(monkeypatch, mapping):
    async def fake_list(kind):
        return list(mapping.get(kind, []))
    monkeypatch.setattr(main.phone_store, "list_items", fake_list)


# --- debounce (pure) -------------------------------------------------------
def test_should_prompt_location_debounce():
    sess = main.Session(ws=FakeWS())
    assert sess._should_prompt_location(26.30, 50.20) is True
    sess._last_loc_prompt = (26.30, 50.20, __import__("time").monotonic())
    # a metre away, just now -> wobble, suppressed
    assert sess._should_prompt_location(26.30001, 50.20001) is False
    # far away -> a genuinely new place, allowed even within the window
    assert sess._should_prompt_location(26.50, 50.50) is True


# --- new location ----------------------------------------------------------
async def test_new_location_unknown_prompts(monkeypatch):
    sess, delivered = _session(monkeypatch)
    _wire_store(monkeypatch, {})               # no saved places
    await sess._handle_event({"type": "event", "kind": "new_location",
                              "coords": {"lat": 26.30, "lng": 50.20}})
    assert len(delivered) == 1
    assert "26.30" in delivered[0] and "save_place" in delivered[0]


async def test_new_location_debounced_second_time(monkeypatch):
    sess, delivered = _session(monkeypatch)
    _wire_store(monkeypatch, {})
    evt = {"type": "event", "kind": "new_location",
           "coords": {"lat": 26.30, "lng": 50.20}}
    await sess._handle_event(evt)
    await sess._handle_event(evt)              # same spot, immediately after
    assert len(delivered) == 1                 # second one suppressed


async def test_new_location_near_known_place_skips(monkeypatch):
    sess, delivered = _session(monkeypatch)
    _wire_store(monkeypatch, {phone_store.KIND_PLACE: [
        {"label": "home", "lat": 26.3000, "lng": 50.2000}]})
    await sess._handle_event({"type": "event", "kind": "new_location",
                              "coords": {"lat": 26.30005, "lng": 50.20005}})
    assert delivered == []                      # it's a known place


async def test_geofence_place_fires_routines(monkeypatch):
    sess, _delivered = _session(monkeypatch)
    fired = []

    async def fake_fire(routine):
        fired.append(routine["name"])
    monkeypatch.setattr(main, "fire_routine", fake_fire)
    _wire_store(monkeypatch, {phone_store.KIND_ROUTINE: [
        {"name": "at office", "trigger": {"type": "place", "place": "office"},
         "action": {"type": "speak_brief", "brief": "tasks"}},
        {"name": "unrelated", "trigger": {"type": "time", "at": "07:00"},
         "action": {"type": "say_text", "text": "x"}},
    ]})
    await sess._handle_event({"type": "event", "kind": "new_location",
                              "coords": {"lat": 26.3, "lng": 50.2},
                              "place": "office"})
    assert fired == ["at office"]              # only the matching place routine


# --- SMS -------------------------------------------------------------------
async def test_sms_watched_sender_delivers(monkeypatch):
    sess, delivered = _session(monkeypatch)
    _wire_store(monkeypatch, {phone_store.KIND_SMS_WATCH: [{"sender": "SNB"}]})
    await sess._handle_sms({"type": "sms_in", "sender": "SNB-AlAhli",
                            "body": "Your OTP is 123456", "ts": 1})
    assert len(delivered) == 1
    assert "SNB-AlAhli" in delivered[0] and "123456" in delivered[0]


async def test_sms_unwatched_sender_ignored(monkeypatch):
    sess, delivered = _session(monkeypatch)
    _wire_store(monkeypatch, {phone_store.KIND_SMS_WATCH: [{"sender": "Barq"}]})
    await sess._handle_sms({"type": "sms_in", "sender": "SomeBank",
                            "body": "hello", "ts": 1})
    assert delivered == []


async def test_sms_wildcard_delivers(monkeypatch):
    sess, delivered = _session(monkeypatch)
    _wire_store(monkeypatch, {phone_store.KIND_SMS_WATCH: [{"sender": "*"}]})
    await sess._handle_sms({"type": "sms_in", "sender": "Anyone",
                            "body": "hi", "ts": 1})
    assert len(delivered) == 1


def test_resolve_action_unknown_id_is_safe():
    sess = main.Session(ws=FakeWS())
    sess._resolve_action({"id": "nope", "ok": True})   # must not raise
