import json
import asyncio
import os
import sqlite3
import time
from types import SimpleNamespace

import httpx
import pytest
import requests

from src import dashboard_auth, gitea_proxy, main
from src.push_notifications import (
    PushConfiguration,
    dispatch_unread_updates,
    send_web_push,
)
from src import push_subscription_store as push_store_module
from src.push_subscription_store import PushSubscriptionStore
from src.push_endpoint_policy import UnsafePushEndpoint


@pytest.fixture(autouse=True)
def resolve_test_push_endpoints_publicly(monkeypatch):
    async def accept(endpoint):
        return endpoint

    monkeypatch.setattr(main, "validate_public_push_endpoint", accept)


def test_dispatch_lease_is_exclusive_recoverable_and_owner_fenced(tmp_path):
    path = tmp_path / "push.sqlite3"
    first = PushSubscriptionStore(path)
    second = PushSubscriptionStore(path)

    assert first.acquire_dispatch_lease("worker-a", now=100, lease_seconds=30) is True
    assert second.acquire_dispatch_lease("worker-b", now=100, lease_seconds=30) is False
    assert second.acquire_dispatch_lease("worker-b", now=131, lease_seconds=30) is True

    assert first.release_dispatch_lease("worker-a") is False
    assert second.release_dispatch_lease("worker-b") is True


def test_subscription_store_uses_private_filesystem_permissions(tmp_path):
    state_dir = tmp_path / "push-state"
    previous_umask = os.umask(0)
    try:
        database = state_dir / "push.sqlite3"
        PushSubscriptionStore(database)
    finally:
        os.umask(previous_umask)

    assert state_dir.stat().st_mode & 0o777 == 0o700
    assert database.stat().st_mode & 0o777 == 0o600


def test_subscription_store_encrypts_device_credentials_and_reopens_with_same_key(tmp_path):
    database = tmp_path / "push.sqlite3"
    key = b"p" * 32
    subscription = {
        "endpoint": "https://push.example/private-device-canary",
        "keys": {"p256dh": "public-key-canary", "auth": "auth-secret-canary"},
    }

    PushSubscriptionStore(database, encryption_key=key).upsert("session-a", subscription)

    persisted = database.read_bytes()
    assert b"private-device-canary" not in persisted
    assert b"public-key-canary" not in persisted
    assert b"auth-secret-canary" not in persisted
    reopened = PushSubscriptionStore(database, encryption_key=key)
    assert reopened.claim_unseen({42: "r1"})[0].subscription == subscription


def test_subscription_store_migrates_legacy_plaintext_without_resetting_checkpoints(tmp_path):
    database = tmp_path / "push.sqlite3"
    subscription = {
        "endpoint": "https://push.example/legacy-device-canary",
        "keys": {"p256dh": "legacy-public-canary", "auth": "legacy-auth-canary"},
    }
    with sqlite3.connect(database) as connection:
        connection.executescript(
            """
            CREATE TABLE push_subscriptions (
                session_id TEXT PRIMARY KEY,
                endpoint TEXT NOT NULL UNIQUE,
                subscription_json TEXT NOT NULL
            );
            CREATE TABLE push_deliveries (
                session_id TEXT NOT NULL,
                thread_id INTEGER NOT NULL,
                revision TEXT NOT NULL DEFAULT '',
                PRIMARY KEY (session_id, thread_id)
            );
            """
        )
        connection.execute(
            "INSERT INTO push_subscriptions VALUES (?, ?, ?)",
            ("session-a", subscription["endpoint"], json.dumps(subscription)),
        )
        connection.execute(
            "INSERT INTO push_deliveries VALUES (?, ?, ?)",
            ("session-a", 42, "r1"),
        )

    store = PushSubscriptionStore(database, encryption_key=b"m" * 32)

    assert store.claim_unseen({42: "r1"}) == []
    updated = store.claim_unseen({42: "r2"})
    assert updated[0].subscription == subscription
    persisted = database.read_bytes()
    assert b"legacy-device-canary" not in persisted
    assert b"legacy-public-canary" not in persisted
    assert b"legacy-auth-canary" not in persisted


def test_push_store_factory_only_requires_its_key_when_push_is_enabled(
    tmp_path, monkeypatch
):
    monkeypatch.delenv("STACKCHAIN_PUSH_STATE_ENCRYPTION_KEY", raising=False)
    factory = getattr(push_store_module, "build_push_subscription_store", None)
    assert callable(factory), "push store factory is missing"

    disabled = factory(tmp_path / "disabled.sqlite3", push_enabled=False)
    assert disabled.is_subscribed("session-a") is False
    assert disabled.deadline_preferences("session-a")["enabled"] is False
    assert not (tmp_path / "disabled.sqlite3").exists()
    with pytest.raises(RuntimeError, match="encryption key"):
        factory(tmp_path / "enabled.sqlite3", push_enabled=True)


def test_subscription_store_fails_closed_for_wrong_key_or_tampered_payload(tmp_path):
    database = tmp_path / "push.sqlite3"
    subscription = {
        "endpoint": "https://push.example/device-a",
        "keys": {"p256dh": "public-key", "auth": "auth-secret"},
    }
    PushSubscriptionStore(database, encryption_key=b"a" * 32).upsert(
        "session-a", subscription
    )

    with pytest.raises(RuntimeError, match="could not be decrypted"):
        PushSubscriptionStore(database, encryption_key=b"b" * 32)

    with sqlite3.connect(database) as connection:
        payload = connection.execute(
            "SELECT subscription_json FROM push_subscriptions WHERE session_id = ?",
            ("session-a",),
        ).fetchone()[0]
        replacement = "A" if payload[-1] != "A" else "B"
        connection.execute(
            "UPDATE push_subscriptions SET subscription_json = ? WHERE session_id = ?",
            (payload[:-1] + replacement, "session-a"),
        )
    with pytest.raises(RuntimeError, match="could not be decrypted"):
        PushSubscriptionStore(database, encryption_key=b"a" * 32)


def test_subscription_ciphertext_cannot_be_substituted_between_sessions(tmp_path):
    database = tmp_path / "push.sqlite3"
    key = b"s" * 32
    store = PushSubscriptionStore(database, encryption_key=key)
    for session_id in ("session-a", "session-b"):
        store.upsert(
            session_id,
            {
                "endpoint": f"https://push.example/{session_id}",
                "keys": {"p256dh": f"key-{session_id}", "auth": f"auth-{session_id}"},
            },
        )
    with sqlite3.connect(database) as connection:
        payloads = connection.execute(
            "SELECT session_id, subscription_json FROM push_subscriptions ORDER BY session_id"
        ).fetchall()
        connection.executemany(
            "UPDATE push_subscriptions SET subscription_json = ? WHERE session_id = ?",
            ((payloads[1][1], payloads[0][0]), (payloads[0][1], payloads[1][0])),
        )

    with pytest.raises(RuntimeError, match="could not be decrypted"):
        PushSubscriptionStore(database, encryption_key=key)


def test_subscription_store_delivers_each_unread_thread_once_per_device(tmp_path):
    store = PushSubscriptionStore(tmp_path / "push.sqlite3")
    subscription = {
        "endpoint": "https://push.example/device-a",
        "keys": {"p256dh": "public-key", "auth": "auth-secret"},
    }

    store.upsert("session-a", subscription)

    first = store.claim_unseen({41, 42})
    store.mark_delivered("session-a", first[0].thread_ids)
    second = store.claim_unseen({41, 42})

    assert [(item.session_id, item.thread_ids) for item in first] == [
        ("session-a", (41, 42))
    ]
    assert second == []


def test_delivery_health_is_bounded_per_channel_and_recovers_after_success(tmp_path):
    store = PushSubscriptionStore(tmp_path / "push.sqlite3")
    store.upsert("session-a", {
        "endpoint": "https://push.example/device-a",
        "keys": {"p256dh": "public-key", "auth": "auth-secret"},
    })

    store.mark_delivery_failed("session-a", "unread", "timeout", now=100)
    store.mark_delivery_failed("session-a", "unread", "provider", now=110)
    store.mark_delivery_failed("session-a", "deadline", "provider", now=120)

    assert store.delivery_health("session-a") == {
        "unread": {
            "state": "degraded",
            "consecutive_failures": 2,
            "last_attempted_at": 110,
            "last_succeeded_at": None,
            "reason": "provider",
        },
        "deadline": {
            "state": "degraded",
            "consecutive_failures": 1,
            "last_attempted_at": 120,
            "last_succeeded_at": None,
            "reason": "provider",
        },
    }

    store.mark_delivery_succeeded("session-a", "unread", now=130)

    assert store.delivery_health("session-a")["unread"] == {
        "state": "healthy",
        "consecutive_failures": 0,
        "last_attempted_at": 130,
        "last_succeeded_at": 130,
        "reason": None,
    }
    assert store.delivery_health("session-a")["deadline"]["state"] == "degraded"


def test_subscription_store_reopens_a_delivered_thread_when_its_revision_changes(tmp_path):
    store = PushSubscriptionStore(tmp_path / "push.sqlite3")
    store.upsert("session-a", {
        "endpoint": "https://push.example/device-a",
        "keys": {"p256dh": "public-key", "auth": "auth-secret"},
    })

    first = store.claim_unseen({42: "2026-08-11T12:00:00Z"})
    store.mark_delivered("session-a", first[0].thread_revisions)
    unchanged = store.claim_unseen({42: "2026-08-11T12:00:00Z"})
    updated = store.claim_unseen({42: "2026-08-11T12:05:00Z"})

    assert unchanged == []
    assert updated[0].thread_revisions == ((42, "2026-08-11T12:05:00Z"),)


def test_subscription_store_does_not_realert_for_an_older_snapshot_revision(tmp_path):
    store = PushSubscriptionStore(tmp_path / "push.sqlite3")
    store.upsert("session-a", {
        "endpoint": "https://push.example/device-a",
        "keys": {"p256dh": "public-key", "auth": "auth-secret"},
    })
    store.mark_delivered("session-a", {42: "2026-08-11T12:05:00Z"})

    assert store.claim_unseen({42: "2026-08-11T12:00:00Z"}) == []


def test_complete_unread_snapshot_reconciles_delivery_and_digest_checkpoints(tmp_path):
    store = PushSubscriptionStore(tmp_path / "push.sqlite3")
    store.upsert("session-a", {
        "endpoint": "https://push.example/device-a",
        "keys": {"p256dh": "public-key", "auth": "auth-secret"},
    })
    revisions = {1: "r1", 2: "r2", 3: "r3"}
    store.mark_delivered("session-a", revisions)
    store.mark_digest_pending("session-a", {3: "r3"})

    store.reconcile_unread({2})
    delivery = store.claim_unseen(revisions)[0]

    assert delivery.thread_ids == (1, 3)
    assert delivery.digest_ids == ()


@pytest.mark.anyio
async def test_dispatch_reconciles_stale_checkpoints_after_a_complete_snapshot(tmp_path):
    store = PushSubscriptionStore(tmp_path / "push.sqlite3")
    store.upsert("session-a", {
        "endpoint": "https://push.example/device-a",
        "keys": {"p256dh": "public-key", "auth": "auth-secret"},
    })
    store.mark_delivered("session-a", {1: "r1"})

    async def unread():
        return {"items": [{"id": 2, "updated_at": "r2"}], "complete": True}

    async def send(_subscription, _payload):
        return None

    config = PushConfiguration("public", "private", "mailto:ops@example.com")
    assert await dispatch_unread_updates(store, config, unread, send) == 1

    remaining = store.claim_unseen({1: "r1", 2: "r2"})
    assert remaining[0].thread_ids == (1,)


@pytest.mark.anyio
async def test_dispatch_sends_nothing_and_preserves_checkpoints_for_an_incomplete_snapshot(tmp_path):
    store = PushSubscriptionStore(tmp_path / "push.sqlite3")
    store.upsert("session-a", {
        "endpoint": "https://push.example/device-a",
        "keys": {"p256dh": "public-key", "auth": "auth-secret"},
    })
    store.mark_delivered("session-a", {1: "r1"})
    sent = []

    async def unread():
        return {"items": [{"id": 2, "updated_at": "r2"}], "complete": False}

    async def send(_subscription, payload):
        sent.append(payload)

    config = PushConfiguration("public", "private", "mailto:ops@example.com")
    assert await dispatch_unread_updates(store, config, unread, send) == 0
    assert sent == []
    assert store.claim_unseen({1: "r1"}) == []


def test_subscription_store_migrates_legacy_delivery_without_replaying_it(tmp_path):
    path = tmp_path / "push.sqlite3"
    subscription = {
        "endpoint": "https://push.example/device-a",
        "keys": {"p256dh": "public-key", "auth": "auth-secret"},
    }
    with sqlite3.connect(path) as connection:
        connection.executescript("""
            CREATE TABLE push_subscriptions (
                session_id TEXT PRIMARY KEY,
                endpoint TEXT NOT NULL UNIQUE,
                subscription_json TEXT NOT NULL
            );
            CREATE TABLE push_deliveries (
                session_id TEXT NOT NULL,
                thread_id INTEGER NOT NULL,
                PRIMARY KEY (session_id, thread_id)
            );
        """)
        connection.execute(
            "INSERT INTO push_subscriptions VALUES (?, ?, ?)",
            ("session-a", subscription["endpoint"], json.dumps(subscription)),
        )
        connection.execute("INSERT INTO push_deliveries VALUES (?, ?)", ("session-a", 42))

    store = PushSubscriptionStore(path)

    assert store.claim_unseen({42: "2026-08-11T12:00:00Z"}) == []
    updated = store.claim_unseen({42: "2026-08-11T12:05:00Z"})
    assert updated[0].thread_revisions == ((42, "2026-08-11T12:05:00Z"),)


@pytest.mark.anyio
async def test_managed_session_active_applies_configured_idle_deadline(monkeypatch):
    calls = []

    class Store:
        def managed_status(self, management_id, *, idle_timeout_seconds):
            calls.append((management_id, idle_timeout_seconds))
            return "active" if management_id == "active-device" else "idle"

    monkeypatch.setenv("STACKCHAIN_DASHBOARD_IDLE_TIMEOUT_SECONDS", "321")
    monkeypatch.setattr(dashboard_auth, "_session_store", lambda: Store())

    assert await dashboard_auth.managed_session_active("active-device") is True
    assert await dashboard_auth.managed_session_active("idle-device") is False
    assert calls == [("active-device", 321), ("idle-device", 321)]


@pytest.mark.anyio
async def test_managed_session_statuses_apply_configured_idle_deadline_in_one_call(monkeypatch):
    calls = []

    class Store:
        def managed_statuses(self, management_ids, *, idle_timeout_seconds):
            calls.append((list(management_ids), idle_timeout_seconds))
            return {management_id: "active" for management_id in set(management_ids)}

    monkeypatch.setenv("STACKCHAIN_DASHBOARD_IDLE_TIMEOUT_SECONDS", "321")
    monkeypatch.setattr(dashboard_auth, "_session_store", lambda: Store())

    assert await dashboard_auth.managed_session_statuses(
        ["device-b", "device-a", "device-b"]
    ) == {"device-a": "active", "device-b": "active"}
    assert calls == [(["device-b", "device-a", "device-b"], 321)]


@pytest.mark.anyio
async def test_push_poll_authorizes_delivery_against_managed_session(monkeypatch):
    captured = {}
    hold_other_channels = asyncio.Event()

    async def no_wait(_seconds):
        return None

    async def stop_after_capture(*args, **kwargs):
        captured["unread"] = args[2]
        captured.update(kwargs)
        raise asyncio.CancelledError

    async def hold_dispatch(*_args, **_kwargs):
        await hold_other_channels.wait()

    monkeypatch.setenv("STACKCHAIN_PUSH_MAX_CONCURRENCY", "3")
    monkeypatch.setenv("STACKCHAIN_PUSH_MAX_INDIVIDUAL_NOTIFICATIONS", "4")
    monkeypatch.setattr(main.asyncio, "sleep", no_wait)
    monkeypatch.setattr(main, "dispatch_unread_updates", stop_after_capture)
    monkeypatch.setattr(main, "dispatch_deadline_reminders", hold_dispatch)
    monkeypatch.setattr(main, "dispatch_start_day_reminders", hold_dispatch)

    with pytest.raises(asyncio.CancelledError):
        await main._push_poll_loop()

    assert captured["unread"] is main.gitea_proxy.unread_notification_snapshot
    assert captured["session_statuses"] is dashboard_auth.managed_session_statuses
    assert captured["max_concurrency"] == 3
    assert captured["max_individual_notifications"] == 4


@pytest.mark.anyio
async def test_push_poll_still_dispatches_deadlines_when_unread_dispatch_fails(monkeypatch):
    calls = []

    sleeps = 0
    first_tick = asyncio.Event()

    async def no_wait(_seconds):
        nonlocal sleeps
        sleeps += 1
        if sleeps <= 3:
            if sleeps == 3:
                first_tick.set()
            await first_tick.wait()
        else:
            raise asyncio.CancelledError

    async def fail_unread(*_args, **_kwargs):
        calls.append("unread")
        raise RuntimeError("unread unavailable")

    async def dispatch_deadlines(*_args, **_kwargs):
        calls.append("deadline")

    async def dispatch_start_day(*_args, **_kwargs):
        calls.append("start-day")

    monkeypatch.setattr(main.asyncio, "sleep", no_wait)
    monkeypatch.setattr(main, "dispatch_unread_updates", fail_unread)
    monkeypatch.setattr(main, "dispatch_deadline_reminders", dispatch_deadlines)
    monkeypatch.setattr(main, "dispatch_start_day_reminders", dispatch_start_day)

    with pytest.raises(asyncio.CancelledError):
        await main._push_poll_loop()

    assert sorted(calls) == ["deadline", "start-day", "unread"]


@pytest.mark.anyio
async def test_push_poll_deadlines_continue_while_unread_dispatch_is_blocked(monkeypatch):
    unread_started = asyncio.Event()
    release_unread = asyncio.Event()
    deadline_started = asyncio.Event()

    async def no_wait(_seconds):
        return None

    async def blocked_unread(*_args, **_kwargs):
        unread_started.set()
        await release_unread.wait()

    async def dispatch_deadlines(*_args, **_kwargs):
        deadline_started.set()
        await release_unread.wait()

    async def dispatch_start_day(*_args, **_kwargs):
        await release_unread.wait()

    monkeypatch.setattr(main.asyncio, "sleep", no_wait)
    monkeypatch.setattr(main, "dispatch_unread_updates", blocked_unread)
    monkeypatch.setattr(main, "dispatch_deadline_reminders", dispatch_deadlines)
    monkeypatch.setattr(main, "dispatch_start_day_reminders", dispatch_start_day)

    poll = asyncio.create_task(main._push_poll_loop())
    await asyncio.wait_for(unread_started.wait(), timeout=0.5)
    try:
        await asyncio.wait_for(deadline_started.wait(), timeout=0.5)
    finally:
        poll.cancel()
        with pytest.raises(asyncio.CancelledError):
            await poll


@pytest.mark.anyio
async def test_push_poll_uses_a_lower_independent_deadline_cadence(monkeypatch):
    intervals = []

    async def capture_channel(_dispatch, *, interval):
        intervals.append(interval)

    monkeypatch.setenv("STACKCHAIN_PUSH_POLL_SECONDS", "30")
    monkeypatch.setenv("STACKCHAIN_DEADLINE_POLL_SECONDS", "600")
    monkeypatch.setattr(main, "_push_channel_loop", capture_channel)

    await main._push_poll_loop()

    assert sorted(intervals) == [30.0, 600.0, 600.0]


@pytest.mark.anyio
async def test_push_channel_loop_skips_missed_ticks_without_overlapping(monkeypatch):
    starts = []
    now = 0.0

    class Loop:
        def time(self):
            return now

    async def record_dispatch():
        nonlocal now
        starts.append("dispatch")
        now = 16.0 if len(starts) == 1 else now
        if len(starts) == 2:
            raise asyncio.CancelledError

    sleeps = []

    async def capture_sleep(seconds):
        nonlocal now
        sleeps.append(seconds)
        now += seconds

    monkeypatch.setattr(main.asyncio, "get_running_loop", lambda: Loop())
    monkeypatch.setattr(main.asyncio, "sleep", capture_sleep)

    with pytest.raises(asyncio.CancelledError):
        await main._push_channel_loop(record_dispatch, interval=5.0)

    assert starts == ["dispatch", "dispatch"]
    assert sleeps == [5.0, 4.0]


def test_dispatch_leases_are_isolated_by_channel(tmp_path):
    path = tmp_path / "push.sqlite3"
    first = PushSubscriptionStore(path)
    second = PushSubscriptionStore(path)

    assert first.acquire_dispatch_lease(
        "unread-owner", channel="unread", now=100, lease_seconds=30
    ) is True
    assert second.acquire_dispatch_lease(
        "deadline-owner", channel="deadline", now=100, lease_seconds=30
    ) is True
    assert second.acquire_dispatch_lease(
        "other-unread-owner", channel="unread", now=100, lease_seconds=30
    ) is False


def test_replacing_subscription_resets_delivery_cursor_and_revocation_removes_device(tmp_path):
    store = PushSubscriptionStore(tmp_path / "push.sqlite3")
    original = {
        "endpoint": "https://push.example/original",
        "keys": {"p256dh": "old-key", "auth": "old-auth"},
    }
    replacement = {
        "endpoint": "https://push.example/replacement",
        "keys": {"p256dh": "new-key", "auth": "new-auth"},
    }
    store.upsert("session-a", original)
    store.claim_unseen({9})

    store.upsert("session-a", replacement)
    delivery = store.claim_unseen({9})
    store.delete_session("session-a")

    assert delivery[0].subscription == replacement
    assert delivery[0].thread_ids == (9,)
    assert store.claim_unseen({10}) == []


@pytest.mark.anyio
async def test_dispatch_sends_one_privacy_safe_deep_link_per_new_thread(tmp_path):
    store = PushSubscriptionStore(tmp_path / "push.sqlite3")
    store.upsert("session-a", {
        "endpoint": "https://push.example/device-a",
        "keys": {"p256dh": "public-key", "auth": "auth-secret"},
    })
    sent = []

    async def unread():
        return {"items": [
            {"id": 42, "repository": "private/repo", "title": "Secret title"},
        ]}

    async def send(subscription, payload):
        sent.append((subscription, json.loads(payload)))

    config = PushConfiguration("public-vapid", "private-vapid", "mailto:ops@example.com")
    assert await dispatch_unread_updates(store, config, unread, send) == 1
    assert await dispatch_unread_updates(store, config, unread, send) == 0
    assert sent[0][1] == {
        "title": "New work update",
        "body": "Tap to review it in Stackchain.",
        "route": "#/my-work/update/42",
        "tag": "stackchain-update-42",
        "notification_id": 42,
    }
    assert "private/repo" not in json.dumps(sent)
    assert "Secret title" not in json.dumps(sent)


@pytest.mark.anyio
async def test_dispatch_realerts_when_the_same_thread_has_a_newer_revision(tmp_path):
    store = PushSubscriptionStore(tmp_path / "push.sqlite3")
    store.upsert("session-a", {
        "endpoint": "https://push.example/device-a",
        "keys": {"p256dh": "public-key", "auth": "auth-secret"},
    })
    revision = "2026-08-11T12:00:00Z"
    sent = []

    async def unread():
        return {"items": [{
            "id": 42,
            "updated_at": revision,
            "repository": "private/repo",
            "title": "Secret follow-up",
        }]}

    async def send(_subscription, payload):
        sent.append(json.loads(payload))

    config = PushConfiguration("public", "private", "mailto:ops@example.com")
    assert await dispatch_unread_updates(store, config, unread, send) == 1
    assert await dispatch_unread_updates(store, config, unread, send) == 0
    revision = "2026-08-11T12:05:00Z"
    assert await dispatch_unread_updates(store, config, unread, send) == 1
    assert [payload["notification_id"] for payload in sent] == [42, 42]
    assert "updated_at" not in json.dumps(sent)
    assert "Secret follow-up" not in json.dumps(sent)


@pytest.mark.anyio
async def test_update_burst_sends_bounded_individual_pushes_and_one_private_digest(tmp_path):
    store = PushSubscriptionStore(tmp_path / "push.sqlite3")
    store.upsert("session-a", {
        "endpoint": "https://push.example/device-a",
        "keys": {"p256dh": "public-key", "auth": "auth-secret"},
    })
    sent = []

    async def unread():
        return {"items": [
            {
                "id": notification_id,
                "repository": "private/repo",
                "title": f"Secret update {notification_id}",
            }
            for notification_id in range(41, 46)
        ]}

    async def send(_subscription, payload):
        sent.append(json.loads(payload))

    config = PushConfiguration("public", "private", "mailto:ops@example.com")
    delivered = await dispatch_unread_updates(
        store,
        config,
        unread,
        send,
        max_individual_notifications=2,
    )

    assert delivered == 3
    assert [payload["tag"] for payload in sent] == [
        "stackchain-update-41",
        "stackchain-update-42",
        "stackchain-update-digest",
    ]
    assert sent[-1] == {
        "title": "3 new work updates",
        "body": "Tap to review them in Stackchain.",
        "route": "#/my-work/updates",
        "tag": "stackchain-update-digest",
        "update_count": 3,
    }
    assert "private/repo" not in json.dumps(sent)
    assert "Secret update" not in json.dumps(sent)
    assert await dispatch_unread_updates(
        store,
        config,
        unread,
        send,
        max_individual_notifications=2,
    ) == 0


@pytest.mark.anyio
async def test_failed_digest_retries_only_overflow_after_individual_checkpoints(tmp_path):
    store = PushSubscriptionStore(tmp_path / "push.sqlite3")
    store.upsert("session-a", {
        "endpoint": "https://push.example/device-a",
        "keys": {"p256dh": "public-key", "auth": "auth-secret"},
    })
    attempts = []

    async def unread():
        return {"items": [{"id": notification_id} for notification_id in range(51, 56)]}

    async def fail_digest(_subscription, payload):
        decoded = json.loads(payload)
        attempts.append(decoded["tag"])
        if decoded["tag"] == "stackchain-update-digest":
            raise RuntimeError("push provider unavailable")

    config = PushConfiguration("public", "private", "mailto:ops@example.com")
    assert await dispatch_unread_updates(
        store,
        config,
        unread,
        fail_digest,
        max_individual_notifications=2,
    ) == 2
    assert store.claim_unseen(range(51, 56))[0].thread_ids == (53, 54, 55)

    async def succeed(_subscription, payload):
        attempts.append(json.loads(payload)["tag"])

    assert await dispatch_unread_updates(
        store,
        config,
        unread,
        succeed,
        max_individual_notifications=2,
    ) == 1
    assert attempts == [
        "stackchain-update-51",
        "stackchain-update-52",
        "stackchain-update-digest",
        "stackchain-update-digest",
    ]
    assert store.claim_unseen(range(51, 56)) == []


@pytest.mark.anyio
async def test_production_notification_page_dispatches_one_unread_push(tmp_path):
    def upstream(_request):
        return httpx.Response(200, json=[{
            "id": 42,
            "unread": True,
            "repository": {"full_name": "private/repo"},
            "subject": {
                "title": "Secret title",
                "type": "Issue",
                "html_url": "https://forge.example/private/repo/issues/7",
            },
        }])

    gitea_proxy.start_client(transport=httpx.MockTransport(upstream))
    try:
        page = await gitea_proxy.notification_page(1)
    finally:
        await gitea_proxy.stop_client()

    store = PushSubscriptionStore(tmp_path / "push.sqlite3")
    store.upsert("session-a", {
        "endpoint": "https://push.example/device-a",
        "keys": {"p256dh": "public-key", "auth": "auth-secret"},
    })
    sent = []

    async def unread():
        return page

    async def send(_subscription, payload):
        sent.append(json.loads(payload))

    config = PushConfiguration("public", "private", "mailto:ops@example.com")
    assert await dispatch_unread_updates(store, config, unread, send) == 1
    assert sent == [{
        "title": "New work update",
        "body": "Tap to review it in Stackchain.",
        "route": "#/my-work/update/42",
        "tag": "stackchain-update-42",
        "notification_id": 42,
    }]
    assert "private/repo" not in json.dumps(sent)
    assert "Secret title" not in json.dumps(sent)


@pytest.mark.anyio
async def test_concurrent_workers_do_not_dispatch_the_same_update(tmp_path):
    path = tmp_path / "push.sqlite3"
    first = PushSubscriptionStore(path)
    second = PushSubscriptionStore(path)
    first.upsert("session-a", {
        "endpoint": "https://push.example/device-a",
        "keys": {"p256dh": "public-key", "auth": "auth-secret"},
    })
    sending = asyncio.Event()
    finish = asyncio.Event()
    sent = []

    async def unread():
        return {"items": [{"id": 42}]}

    async def send(_subscription, payload):
        sent.append(json.loads(payload)["tag"])
        sending.set()
        await finish.wait()

    config = PushConfiguration("public", "private", "mailto:ops@example.com")
    active = asyncio.create_task(dispatch_unread_updates(first, config, unread, send))
    await sending.wait()
    competing = await asyncio.wait_for(
        dispatch_unread_updates(second, config, unread, send), timeout=0.1
    )
    finish.set()

    assert competing == 0
    assert await active == 1
    assert sent == ["stackchain-update-42"]


@pytest.mark.anyio
async def test_dispatch_removes_an_expired_push_endpoint(tmp_path):
    store = PushSubscriptionStore(tmp_path / "push.sqlite3")
    store.upsert("session-a", {
        "endpoint": "https://push.example/expired",
        "keys": {"p256dh": "public-key", "auth": "auth-secret"},
    })

    class Expired(Exception):
        response = SimpleNamespace(status_code=410)

    async def unread():
        return {"items": [{"id": 8}]}

    async def send(_subscription, _payload):
        raise Expired()

    config = PushConfiguration("public", "private", "mailto:ops@example.com")
    assert await dispatch_unread_updates(store, config, unread, send) == 0
    assert store.is_subscribed("session-a") is False


@pytest.mark.anyio
async def test_dispatch_removes_an_endpoint_that_rebinds_private_without_contacting_it(tmp_path):
    store = PushSubscriptionStore(tmp_path / "push.sqlite3")
    store.upsert("unsafe-device", {
        "endpoint": "https://push.example/rebound",
        "keys": {"p256dh": "public-key", "auth": "auth-secret"},
    })
    contacted = []

    async def unread():
        return {"items": [{"id": 8}]}

    async def reject_rebound(_endpoint):
        raise UnsafePushEndpoint("Endpoint must resolve to a public Web Push service")

    async def send(subscription, _payload):
        contacted.append(subscription["endpoint"])

    config = PushConfiguration("public", "private", "mailto:ops@example.com")
    assert await dispatch_unread_updates(
        store, config, unread, send, endpoint_validator=reject_rebound
    ) == 0
    assert contacted == []
    assert store.is_subscribed("unsafe-device") is False


@pytest.mark.anyio
async def test_web_push_transport_dials_the_address_validated_for_the_original_tls_host():
    endpoint = "https://push.example/device-a"
    resolved = SimpleNamespace(
        endpoint=endpoint,
        hostname="push.example",
        port=443,
        addresses=("93.184.216.34",),
    )
    resolutions = []
    connection = {}

    async def resolve_once(value):
        resolutions.append(value)
        return resolved

    def inspect_transport(**kwargs):
        session = kwargs["requests_session"]
        assert session.trust_env is False
        request = session.prepare_request(requests.Request("POST", endpoint))
        adapter = session.get_adapter(endpoint)
        adapter.add_headers(request)
        host, tls = adapter.build_connection_pool_key_attributes(request, True)
        connection.update(host=host, tls=tls, headers=dict(request.headers))

    await send_web_push(
        {"endpoint": endpoint, "keys": {"p256dh": "key", "auth": "secret"}},
        "{}",
        PushConfiguration("public", "private", "mailto:ops@example.com"),
        endpoint_resolver=resolve_once,
        webpush_sender=inspect_transport,
    )

    assert resolutions == [endpoint]
    assert connection["host"] == {
        "scheme": "https",
        "host": "93.184.216.34",
        "port": 443,
    }
    assert connection["tls"]["assert_hostname"] == "push.example"
    assert connection["tls"]["server_hostname"] == "push.example"
    assert connection["headers"]["Host"] == "push.example"


@pytest.mark.anyio
async def test_connect_time_rebinding_rejection_removes_only_the_unsafe_device(tmp_path):
    store = PushSubscriptionStore(tmp_path / "push.sqlite3")
    for session_id in ("unsafe-device", "healthy-device"):
        store.upsert(session_id, {
            "endpoint": f"https://push.example/{session_id}",
            "keys": {"p256dh": "public-key", "auth": "auth-secret"},
        })
    sent = []

    async def unread():
        return {"items": [{"id": 8}]}

    async def send(subscription, _payload):
        if subscription["endpoint"].endswith("unsafe-device"):
            raise UnsafePushEndpoint("Endpoint rebound before connection")
        sent.append(subscription["endpoint"])

    config = PushConfiguration("public", "private", "mailto:ops@example.com")
    assert await dispatch_unread_updates(store, config, unread, send) == 1
    assert store.is_subscribed("unsafe-device") is False
    assert store.is_subscribed("healthy-device") is True
    assert sent == ["https://push.example/healthy-device"]


@pytest.mark.anyio
async def test_transient_endpoint_failure_does_not_block_healthy_devices(tmp_path):
    store = PushSubscriptionStore(tmp_path / "push.sqlite3")
    for session_id in ("session-a", "session-b"):
        store.upsert(session_id, {
            "endpoint": f"https://push.example/{session_id}",
            "keys": {"p256dh": "public-key", "auth": "auth-secret"},
        })
    sent = []

    async def unread():
        return {"items": [{"id": 8}]}

    async def send(subscription, _payload):
        if subscription["endpoint"].endswith("session-a"):
            raise RuntimeError("provider unavailable")
        sent.append(subscription["endpoint"])

    config = PushConfiguration("public", "private", "mailto:ops@example.com")
    assert await dispatch_unread_updates(store, config, unread, send) == 1
    assert sent == ["https://push.example/session-b"]
    assert store.claim_unseen({8})[0].session_id == "session-a"


@pytest.mark.anyio
async def test_unread_dispatch_records_degraded_health_then_recovers(tmp_path):
    store = PushSubscriptionStore(tmp_path / "push.sqlite3")
    store.upsert("session-a", {
        "endpoint": "https://push.example/device-a",
        "keys": {"p256dh": "public-key", "auth": "auth-secret"},
    })
    revision = 0

    async def unread():
        nonlocal revision
        revision += 1
        return {"items": [{"id": 42, "updated_at": f"r{revision}"}], "complete": True}

    async def timeout_send(_subscription, _payload):
        raise asyncio.TimeoutError

    config = PushConfiguration("public", "private", "mailto:ops@example.com")
    for _ in range(3):
        assert await dispatch_unread_updates(store, config, unread, timeout_send) == 0

    health = store.delivery_health("session-a")["unread"]
    assert health["state"] == "degraded"
    assert health["consecutive_failures"] == 3
    assert health["reason"] == "timeout"

    async def succeed(_subscription, _payload):
        return None

    assert await dispatch_unread_updates(store, config, unread, succeed) == 1
    recovered = store.delivery_health("session-a")["unread"]
    assert recovered["state"] == "healthy"
    assert recovered["consecutive_failures"] == 0
    assert recovered["last_succeeded_at"] is not None


@pytest.mark.anyio
async def test_unexpected_device_failure_waits_for_siblings_and_retries_only_that_device(tmp_path):
    path = tmp_path / "push.sqlite3"
    store = PushSubscriptionStore(path)
    competitor = PushSubscriptionStore(path)
    for session_id in ("failing-device", "healthy-device"):
        store.upsert(session_id, {
            "endpoint": f"https://push.example/{session_id}",
            "keys": {"p256dh": "public-key", "auth": "auth-secret"},
        })
    healthy_started = asyncio.Event()
    release_healthy = asyncio.Event()
    sent = []

    async def unread():
        return {"items": [{"id": 8, "updated_at": "r1"}], "complete": True}

    async def validate(endpoint):
        if endpoint.endswith("failing-device"):
            raise TimeoutError("endpoint validation unavailable")
        return endpoint

    async def send(subscription, _payload):
        sent.append(subscription["endpoint"])
        healthy_started.set()
        await release_healthy.wait()

    config = PushConfiguration("public", "private", "mailto:ops@example.com")
    dispatch = asyncio.create_task(dispatch_unread_updates(
        store,
        config,
        unread,
        send,
        endpoint_validator=validate,
    ))
    await asyncio.wait_for(healthy_started.wait(), timeout=0.2)
    await asyncio.sleep(0)

    assert dispatch.done() is False
    assert competitor.acquire_dispatch_lease(
        "competing-worker", now=time.time(), lease_seconds=60
    ) is False

    release_healthy.set()
    assert await dispatch == 1
    remaining = store.claim_unseen({8: "r1"})
    assert [(item.session_id, item.thread_ids) for item in remaining] == [
        ("failing-device", (8,))
    ]
    assert store.delivery_health("failing-device")["unread"]["reason"] == "timeout"

    retried = []

    async def validate_recovered(endpoint):
        return endpoint

    async def send_retry(subscription, _payload):
        retried.append(subscription["endpoint"])

    assert await dispatch_unread_updates(
        store,
        config,
        unread,
        send_retry,
        endpoint_validator=validate_recovered,
    ) == 1
    assert retried == ["https://push.example/failing-device"]
    assert sent == ["https://push.example/healthy-device"]


@pytest.mark.anyio
async def test_transient_failure_stops_that_device_until_the_next_poll(tmp_path):
    store = PushSubscriptionStore(tmp_path / "push.sqlite3")
    for session_id in ("failing-device", "healthy-device"):
        store.upsert(session_id, {
            "endpoint": f"https://push.example/{session_id}",
            "keys": {"p256dh": "public-key", "auth": "auth-secret"},
        })
    attempts = []

    async def unread():
        return {"items": [
            {"id": 8},
            {"id": 9},
        ]}

    async def send(subscription, payload):
        endpoint = subscription["endpoint"]
        thread_id = int(json.loads(payload)["route"].rsplit("/", 1)[1])
        attempts.append((endpoint, thread_id))
        if endpoint.endswith("failing-device"):
            raise RuntimeError("provider unavailable")

    config = PushConfiguration("public", "private", "mailto:ops@example.com")
    assert await dispatch_unread_updates(store, config, unread, send) == 2
    assert [item for item in attempts if item[0].endswith("failing-device")] == [
        ("https://push.example/failing-device", 8)
    ]
    remaining = {item.session_id: item.thread_ids for item in store.claim_unseen({8, 9})}
    assert remaining == {"failing-device": (8, 9)}


@pytest.mark.anyio
async def test_timed_out_endpoint_does_not_stall_push_fanout(tmp_path):
    store = PushSubscriptionStore(tmp_path / "push.sqlite3")
    for session_id in ("session-a", "session-b"):
        store.upsert(session_id, {
            "endpoint": f"https://push.example/{session_id}",
            "keys": {"p256dh": "public-key", "auth": "auth-secret"},
        })
    sent = []

    async def unread():
        return {"items": [{"id": 13}]}

    async def send(subscription, _payload):
        if subscription["endpoint"].endswith("session-a"):
            await asyncio.Event().wait()
        sent.append(subscription["endpoint"])

    config = PushConfiguration("public", "private", "mailto:ops@example.com")
    delivered = await asyncio.wait_for(
        dispatch_unread_updates(
            store, config, unread, send, send_timeout_seconds=0.01
        ),
        timeout=0.2,
    )

    assert delivered == 1
    assert sent == ["https://push.example/session-b"]


@pytest.mark.anyio
async def test_dispatches_devices_concurrently_with_a_strict_bound(tmp_path):
    store = PushSubscriptionStore(tmp_path / "push.sqlite3")
    for index in range(4):
        store.upsert(f"session-{index}", {
            "endpoint": f"https://push.example/session-{index}",
            "keys": {"p256dh": "public-key", "auth": "auth-secret"},
        })
    active = 0
    peak = 0
    bound_reached = asyncio.Event()
    release = asyncio.Event()

    async def unread():
        return {"items": [{"id": 13}]}

    async def send(_subscription, _payload):
        nonlocal active, peak
        active += 1
        peak = max(peak, active)
        if active == 2:
            bound_reached.set()
        await release.wait()
        active -= 1

    config = PushConfiguration("public", "private", "mailto:ops@example.com")
    dispatch = asyncio.create_task(dispatch_unread_updates(
        store, config, unread, send, max_concurrency=2
    ))
    await asyncio.wait_for(bound_reached.wait(), timeout=0.2)

    assert peak == 2
    assert dispatch.done() is False

    release.set()
    assert await dispatch == 4
    assert peak == 2


@pytest.mark.anyio
async def test_dispatch_removes_inactive_sessions_without_blocking_active_devices(tmp_path):
    store = PushSubscriptionStore(tmp_path / "push.sqlite3")
    for session_id in ("expired-device", "active-device"):
        store.upsert(session_id, {
            "endpoint": f"https://push.example/{session_id}",
            "keys": {"p256dh": "public-key", "auth": "auth-secret"},
        })
    sent = []

    async def unread():
        return {"items": [{"id": 21}]}

    authorization_calls = []

    async def session_statuses(management_ids):
        authorization_calls.append(list(management_ids))
        return {
            management_id: "active" if management_id == "active-device" else "revoked"
            for management_id in management_ids
        }

    async def send(subscription, _payload):
        sent.append(subscription["endpoint"])

    config = PushConfiguration("public", "private", "mailto:ops@example.com")
    assert await dispatch_unread_updates(
        store, config, unread, send, session_statuses=session_statuses
    ) == 1
    assert store.is_subscribed("expired-device") is False
    assert store.is_subscribed("active-device") is True
    assert sent == ["https://push.example/active-device"]
    assert authorization_calls == [["active-device", "expired-device"]]


@pytest.mark.anyio
async def test_dispatch_fails_closed_and_retains_subscriptions_when_session_registry_fails(tmp_path):
    store = PushSubscriptionStore(tmp_path / "push.sqlite3")
    store.upsert("active-device", {
        "endpoint": "https://push.example/active-device",
        "keys": {"p256dh": "public-key", "auth": "auth-secret"},
    })
    sent = []

    async def unread():
        return {"items": [{"id": 22}]}

    async def registry_unavailable(_management_ids):
        raise RuntimeError("session registry unavailable")

    async def send(subscription, _payload):
        sent.append(subscription)

    config = PushConfiguration("public", "private", "mailto:ops@example.com")
    assert await dispatch_unread_updates(
        store, config, unread, send, session_statuses=registry_unavailable
    ) == 0
    assert store.is_subscribed("active-device") is True
    assert sent == []


@pytest.mark.anyio
async def test_authenticated_device_can_subscribe_report_status_and_unsubscribe(tmp_path, monkeypatch):
    store = PushSubscriptionStore(tmp_path / "push.sqlite3")
    monkeypatch.setattr(main, "_push_subscription_store", store)
    monkeypatch.setattr(
        main,
        "_push_configuration",
        lambda: PushConfiguration("public-vapid", "private-vapid", "mailto:ops@example.com"),
    )
    async def management_id(_session):
        return "session-a"

    monkeypatch.setattr(main.dashboard_auth, "session_management_id", management_id)

    async def no_unread(**_kwargs):
        return {"items": [], "complete": True}

    monkeypatch.setattr(main.gitea_proxy, "unread_notification_snapshot", no_unread)
    request = SimpleNamespace(state=SimpleNamespace(
        dashboard_session=SimpleNamespace(session_id="session-a")
    ))
    payload = main.PushSubscriptionPayload(
        endpoint="https://push.example/device-a",
        keys={"p256dh": "public-key", "auth": "auth-secret"},
    )

    assert await main.push_status(request) == {
        "available": True,
        "subscribed": False,
        "public_key": "public-vapid",
        "deadline_enabled": False,
        "timezone": "UTC",
        "reminder_hour": 9,
        "reminder_days": 2,
        "snoozed_until": None,
        "start_day_enabled": False,
        "start_day_timezone": "UTC",
        "start_day_reminder_hour": 9,
        "delivery_health": {},
    }
    assert await main.subscribe_push(payload, request) == {"subscribed": True}
    assert (await main.push_status(request))["subscribed"] is True
    assert await main.unsubscribe_push(request) == {"subscribed": False}
    assert (await main.push_status(request))["subscribed"] is False


@pytest.mark.anyio
async def test_authenticated_device_can_send_a_real_test_notification(tmp_path, monkeypatch):
    store = PushSubscriptionStore(tmp_path / "push.sqlite3")
    subscription = {
        "endpoint": "https://push.example/device-a",
        "keys": {"p256dh": "public-key", "auth": "auth-secret"},
    }
    store.upsert("session-a", subscription)
    monkeypatch.setattr(main, "_push_subscription_store", store)
    configuration = PushConfiguration("public", "private", "mailto:ops@example.com")
    monkeypatch.setattr(main, "_push_configuration", lambda: configuration)

    async def management_id(_session):
        return "session-a"

    sent = []

    async def send(delivery, payload, config):
        sent.append((delivery, json.loads(payload), config))

    monkeypatch.setattr(main.dashboard_auth, "session_management_id", management_id)
    monkeypatch.setattr(main, "send_web_push", send, raising=False)
    request = SimpleNamespace(state=SimpleNamespace(dashboard_session=object()))

    assert await main.test_push_notification(request) == {
        "delivery_state": "healthy",
        "delivered": True,
    }
    assert sent == [(
        subscription,
        {
            "title": "Stackchain notifications are working",
            "body": "This device can receive private work alerts.",
            "route": "#/device-setup",
            "tag": "stackchain-push-test",
        },
        configuration,
    )]
    assert store.delivery_health("session-a")["unread"]["state"] == "healthy"


@pytest.mark.anyio
async def test_test_notification_reports_provider_failure_as_degraded(tmp_path, monkeypatch):
    store = PushSubscriptionStore(tmp_path / "push.sqlite3")
    store.upsert("session-a", {
        "endpoint": "https://push.example/device-a",
        "keys": {"p256dh": "public-key", "auth": "auth-secret"},
    })
    monkeypatch.setattr(main, "_push_subscription_store", store)
    monkeypatch.setattr(
        main, "_push_configuration",
        lambda: PushConfiguration("public", "private", "mailto:ops@example.com"),
    )

    async def management_id(_session):
        return "session-a"

    async def fail(*_args):
        raise asyncio.TimeoutError

    monkeypatch.setattr(main.dashboard_auth, "session_management_id", management_id)
    monkeypatch.setattr(main, "send_web_push", fail)
    request = SimpleNamespace(state=SimpleNamespace(dashboard_session=object()))

    with pytest.raises(main.HTTPException) as raised:
        await main.test_push_notification(request)

    assert raised.value.status_code == 503
    assert raised.value.detail == "Test notification delivery failed"
    assert store.is_subscribed("session-a") is True
    assert store.delivery_health("session-a")["unread"]["reason"] == "timeout"


@pytest.mark.anyio
async def test_authenticated_device_can_enable_deadline_reminders(tmp_path, monkeypatch):
    store = PushSubscriptionStore(tmp_path / "push.sqlite3")
    store.upsert("session-a", {
        "endpoint": "https://push.example/device-a",
        "keys": {"p256dh": "public-key", "auth": "auth-secret"},
    })
    monkeypatch.setattr(main, "_push_subscription_store", store)

    async def management_id(_session):
        return "session-a"

    monkeypatch.setattr(main.dashboard_auth, "session_management_id", management_id)
    request = SimpleNamespace(state=SimpleNamespace(dashboard_session=object()))
    payload = main.DeadlineReminderPayload(
        enabled=True, timezone="America/New_York", reminder_hour=9, reminder_days=7
    )

    assert await main.update_deadline_reminders(payload, request) == {
        "deadline_enabled": True,
        "timezone": "America/New_York",
        "reminder_hour": 9,
        "reminder_days": 7,
    }
    assert store.deadline_preferences("session-a")["enabled"] is True
    assert store.deadline_preferences("session-a")["reminder_days"] == 7


@pytest.mark.anyio
async def test_authenticated_device_can_snooze_its_enabled_deadline_reminder(tmp_path, monkeypatch):
    store = PushSubscriptionStore(tmp_path / "push.sqlite3")
    for session_id in ("session-a", "session-b"):
        store.upsert(session_id, {
            "endpoint": f"https://push.example/{session_id}",
            "keys": {"p256dh": "public-key", "auth": "auth-secret"},
        })
        store.set_deadline_preferences(
            session_id, enabled=True, timezone="UTC", reminder_hour=9
        )
    monkeypatch.setattr(main, "_push_subscription_store", store)
    monkeypatch.setattr(main.time, "time", lambda: 1_765_000_000)

    async def management_id(_session):
        return "session-a"

    monkeypatch.setattr(main.dashboard_auth, "session_management_id", management_id)
    request = SimpleNamespace(state=SimpleNamespace(dashboard_session=object()))

    assert await main.snooze_deadline_reminder(request) == {
        "snoozed": True,
        "snoozed_until": 1_765_003_600,
    }
    devices = {device.session_id: device for device in store.deadline_reminder_devices()}
    assert devices["session-a"].snoozed_until == 1_765_003_600
    assert devices["session-b"].snoozed_until is None


@pytest.mark.anyio
async def test_push_status_exposes_only_the_current_devices_active_deadline_snooze(tmp_path, monkeypatch):
    store = PushSubscriptionStore(tmp_path / "push.sqlite3")
    for session_id in ("session-a", "session-b"):
        store.upsert(session_id, {
            "endpoint": f"https://push.example/{session_id}",
            "keys": {"p256dh": "public-key", "auth": "auth-secret"},
        })
        store.set_deadline_preferences(
            session_id, enabled=True, timezone="UTC", reminder_hour=9
        )
    store.snooze_deadline_reminder("session-a", now=1_765_000_000)
    store.snooze_deadline_reminder("session-b", now=1_765_001_000)
    monkeypatch.setattr(main, "_push_subscription_store", store)
    monkeypatch.setattr(main.time, "time", lambda: 1_765_000_100)

    async def management_id(_session):
        return "session-a"

    monkeypatch.setattr(main.dashboard_auth, "session_management_id", management_id)
    request = SimpleNamespace(state=SimpleNamespace(dashboard_session=object()))

    status = await main.push_status(request)

    assert status["snoozed_until"] == 1_765_003_600
    assert store.deadline_preferences("session-a", now=1_765_004_000)["snoozed_until"] is None


@pytest.mark.anyio
async def test_authenticated_device_can_resume_only_its_own_deadline_reminders(tmp_path, monkeypatch):
    store = PushSubscriptionStore(tmp_path / "push.sqlite3")
    for session_id in ("session-a", "session-b"):
        store.upsert(session_id, {
            "endpoint": f"https://push.example/{session_id}",
            "keys": {"p256dh": "public-key", "auth": "auth-secret"},
        })
        store.set_deadline_preferences(
            session_id, enabled=True, timezone="UTC", reminder_hour=9
        )
        store.snooze_deadline_reminder(session_id, now=1_765_000_000)
    monkeypatch.setattr(main, "_push_subscription_store", store)

    async def management_id(_session):
        return "session-a"

    monkeypatch.setattr(main.dashboard_auth, "session_management_id", management_id)
    request = SimpleNamespace(state=SimpleNamespace(dashboard_session=object()))

    assert await main.resume_deadline_reminders(request) == {"snoozed": False}
    devices = {device.session_id: device for device in store.deadline_reminder_devices()}
    assert devices["session-a"].snoozed_until is None
    assert devices["session-b"].snoozed_until == 1_765_003_600


@pytest.mark.anyio
async def test_subscription_rejects_an_unsafe_endpoint_before_persistence(tmp_path, monkeypatch):
    store = PushSubscriptionStore(tmp_path / "push.sqlite3")
    monkeypatch.setattr(main, "_push_subscription_store", store)
    monkeypatch.setattr(
        main, "_push_configuration",
        lambda: PushConfiguration("public", "private", "mailto:ops@example.com"),
    )

    async def management_id(_session):
        return "unsafe-device"

    async def reject_private(_endpoint):
        raise UnsafePushEndpoint("Endpoint must resolve to a public Web Push service")

    monkeypatch.setattr(main.dashboard_auth, "session_management_id", management_id)
    monkeypatch.setattr(main, "validate_public_push_endpoint", reject_private)
    request = SimpleNamespace(state=SimpleNamespace(dashboard_session=object()))
    payload = main.PushSubscriptionPayload(
        endpoint="https://push.example/device-a",
        keys={"p256dh": "public-key", "auth": "auth-secret"},
    )

    with pytest.raises(main.HTTPException) as rejected:
        await main.subscribe_push(payload, request)

    assert rejected.value.status_code == 422
    assert rejected.value.detail == "Endpoint must resolve to a public Web Push service"
    assert store.is_subscribed("unsafe-device") is False


@pytest.mark.anyio
async def test_subscription_baselines_every_revision_from_the_complete_snapshot(tmp_path, monkeypatch):
    store = PushSubscriptionStore(tmp_path / "push.sqlite3")
    monkeypatch.setattr(main, "_push_subscription_store", store)
    monkeypatch.setattr(
        main,
        "_push_configuration",
        lambda: PushConfiguration("public", "private", "mailto:ops@example.com"),
    )

    async def management_id(_session):
        return "device-a"

    revisions = {
        thread_id: f"2026-08-11T12:{thread_id % 60:02d}:00Z"
        for thread_id in range(1, 126)
    }

    async def complete_snapshot(**_kwargs):
        return {
            "items": [
                {"id": thread_id, "updated_at": revision}
                for thread_id, revision in revisions.items()
            ],
            "total": 125,
            "complete": True,
        }

    async def first_page_must_not_be_used():
        raise AssertionError("subscription used the first-page inbox endpoint")

    monkeypatch.setattr(main.dashboard_auth, "session_management_id", management_id)
    monkeypatch.setattr(main.gitea_proxy, "unread_notification_snapshot", complete_snapshot)
    monkeypatch.setattr(main, "notifications", first_page_must_not_be_used)
    request = SimpleNamespace(state=SimpleNamespace(dashboard_session=object()))
    payload = main.PushSubscriptionPayload(
        endpoint="https://push.example/device-a",
        keys={"p256dh": "public-key", "auth": "auth-secret"},
    )

    assert await main.subscribe_push(payload, request) == {"subscribed": True}
    assert store.claim_unseen(revisions) == []


@pytest.mark.anyio
async def test_subscription_baselines_production_notification_ids(tmp_path, monkeypatch):
    store = PushSubscriptionStore(tmp_path / "push.sqlite3")
    monkeypatch.setattr(main, "_push_subscription_store", store)
    monkeypatch.setattr(
        main,
        "_push_configuration",
        lambda: PushConfiguration("public", "private", "mailto:ops@example.com"),
    )

    async def management_id(_session):
        return "device-a"

    async def current_unread(**_kwargs):
        return {"items": [{"id": 42}], "complete": True}

    monkeypatch.setattr(main.dashboard_auth, "session_management_id", management_id)
    monkeypatch.setattr(main.gitea_proxy, "unread_notification_snapshot", current_unread)
    request = SimpleNamespace(state=SimpleNamespace(dashboard_session=object()))
    payload = main.PushSubscriptionPayload(
        endpoint="https://push.example/device-a",
        keys={"p256dh": "public-key", "auth": "auth-secret"},
    )

    assert await main.subscribe_push(payload, request) == {"subscribed": True}
    assert store.claim_unseen({42}) == []
    assert store.claim_unseen({42, 43})[0].thread_ids == (43,)


@pytest.mark.anyio
async def test_subscription_fails_closed_when_existing_unread_baseline_is_unavailable(tmp_path, monkeypatch):
    store = PushSubscriptionStore(tmp_path / "push.sqlite3")
    monkeypatch.setattr(main, "_push_subscription_store", store)
    monkeypatch.setattr(
        main, "_push_configuration",
        lambda: PushConfiguration("public", "private", "mailto:ops@example.com"),
    )

    async def management_id(_session):
        return "device-a"

    async def unavailable(**_kwargs):
        raise RuntimeError("Gitea unavailable")

    monkeypatch.setattr(main.dashboard_auth, "session_management_id", management_id)
    monkeypatch.setattr(main.gitea_proxy, "unread_notification_snapshot", unavailable)
    request = SimpleNamespace(state=SimpleNamespace(dashboard_session=object()))
    payload = main.PushSubscriptionPayload(
        endpoint="https://push.example/device-a",
        keys={"p256dh": "public-key", "auth": "auth-secret"},
    )

    with pytest.raises(main.HTTPException) as raised:
        await main.subscribe_push(payload, request)

    assert raised.value.status_code == 503
    assert store.is_subscribed("device-a") is False
