import json
import pytest
from r_bridge.protocol import make_request, parse_response, SENTINEL
from r_bridge.exceptions import RProtocolError


def test_sentinel_prefix():
    line = make_request("ping", {})
    assert line.startswith(SENTINEL)


def test_newline_terminated():
    line = make_request("ping", {})
    assert line.endswith("\n")


def test_valid_envelope():
    line = make_request("set_var", {"name": "x", "value": 1})
    body = json.loads(line[len(SENTINEL):].strip())
    assert body["op"] == "set_var"
    assert "id" in body
    assert body["payload"] == {"name": "x", "value": 1}


def test_unique_ids():
    ids = {json.loads(make_request("ping", {})[len(SENTINEL):].strip())["id"] for _ in range(100)}
    assert len(ids) == 100


def test_parse_ok_response():
    raw = json.dumps({"id": "abc", "status": "ok", "payload": {"pong": True}, "warnings": []})
    resp = parse_response(SENTINEL + raw + "\n")
    assert resp["id"] == "abc"
    assert resp["status"] == "ok"
    assert resp["payload"] == {"pong": True}
    assert resp["warnings"] == []


def test_parse_missing_sentinel():
    raw = json.dumps({"id": "abc", "status": "ok", "payload": {}, "warnings": []})
    with pytest.raises(RProtocolError):
        parse_response(raw + "\n")


def test_parse_malformed_json():
    with pytest.raises(RProtocolError):
        parse_response(SENTINEL + "not json\n")


def test_parse_missing_fields():
    raw = json.dumps({"id": "abc"})  # missing status, payload, warnings
    with pytest.raises(RProtocolError):
        parse_response(SENTINEL + raw + "\n")


def test_non_protocol_line_ignored():
    # Lines without sentinel should return None (stray R output)
    from r_bridge.protocol import strip_sentinel
    assert strip_sentinel("some random R output\n") is None
    assert strip_sentinel(SENTINEL + '{"a":1}\n') == '{"a":1}'
