from api.edge.outbox import OutboxStore, PayloadRejected
from api.services.write_idempotency import route_class_for


def test_route_classifier_matches_safe_writes():
    assert route_class_for("POST", "/progress/") == "append"
    assert route_class_for("PATCH", "/tasks/abc") == "replace"
    assert route_class_for("DELETE", "/tasks/abc") is None


def test_outbox_scrubs_secret_fields_and_tracks_status(tmp_path):
    store = OutboxStore(tmp_path / "outbox.sqlite3")
    envelope = store.enqueue(
        method="POST",
        path="/progress/",
        body={"summary": "offline", "password": "secret", "tokens_in": 12},
        source_agent="pytest",
        source_host="host-a",
    )

    assert envelope.status == "queued"
    assert envelope.route_class == "append"
    assert envelope.body["password"] == "[redacted]"
    assert envelope.body["tokens_in"] == 12
    assert store.summary()["pending_count"] == 1

    store.mark_acked(envelope.id, response_status=201, response_body={"id": "central"})
    acked = store.get(envelope.id)
    assert acked.status == "acked"
    assert acked.response_body == {"id": "central"}
    assert store.summary()["pending_count"] == 0


def test_outbox_rejects_non_queueable_routes(tmp_path):
    store = OutboxStore(tmp_path / "outbox.sqlite3")
    try:
        store.enqueue(method="DELETE", path="/tasks/abc", body={})
    except PayloadRejected as exc:
        assert "not queueable" in str(exc)
    else:
        raise AssertionError("DELETE should not be queueable")


def test_replace_writes_coalesce_superseded_queued_envelopes(tmp_path):
    store = OutboxStore(tmp_path / "outbox.sqlite3")
    first = store.enqueue(method="PATCH", path="/tasks/task-1", body={"status": "progress"})
    second = store.enqueue(method="PATCH", path="/tasks/task-1", body={"status": "done"})

    assert store.get(first.id).status == "cancelled"
    assert store.get(second.id).status == "queued"
    assert len(store.due()) == 1
