"""Unit tests for envelope schemas."""
from __future__ import annotations

import json

import pytest

from ana.envelope import (
    Ack,
    Discovery,
    EnvelopeError,
    Query,
    Reply,
    parse_envelope,
)


def test_query_roundtrip() -> None:
    q = Query(**{"from": "alice"}, to="bob", query="status?")
    assert q.from_ == "alice"
    assert q.to == "bob"
    assert q.type == "query"

    decoded = parse_envelope(q.to_bytes())
    assert isinstance(decoded, Query)
    assert decoded.from_ == "alice"
    assert decoded.to == "bob"
    assert decoded.query == "status?"


def test_reply_roundtrip() -> None:
    r = Reply(
        **{"from": "bob"},
        reply_for="cc.fleet.bob.query.status",
        data={"alive": True, "uptime_s": 42},
        note="ok",
    )
    body = r.to_json()
    parsed = json.loads(body)
    assert parsed["from"] == "bob"
    assert parsed["data"]["alive"] is True

    decoded = parse_envelope(body)
    assert isinstance(decoded, Reply)
    assert decoded.data == {"alive": True, "uptime_s": 42}
    assert decoded.note == "ok"


def test_ack_roundtrip() -> None:
    a = Ack(
        **{"from": "carol"},
        ack_for="cc.fleet.carol.query.status",
        received_subjects=["cc.fleet.alice.>", "cc.fleet.bob.>"],
    )
    decoded = parse_envelope(a.to_bytes())
    assert isinstance(decoded, Ack)
    assert decoded.alive is True
    assert decoded.received_subjects == ["cc.fleet.alice.>", "cc.fleet.bob.>"]


def test_discovery_roundtrip() -> None:
    d = Discovery(
        **{"from": "dave"},
        role="worker",
        cluster="e580",
        subjects_owned=["cc.fleet.dave.>"],
        capabilities={"gpu": "A100", "ram_gb": 80},
    )
    decoded = parse_envelope(d.to_bytes())
    assert isinstance(decoded, Discovery)
    assert decoded.capabilities["gpu"] == "A100"


def test_parse_envelope_rejects_unknown_type() -> None:
    payload = json.dumps({"type": "scream", "from": "x"}).encode()
    with pytest.raises(EnvelopeError):
        parse_envelope(payload)


def test_parse_envelope_rejects_bad_json() -> None:
    with pytest.raises(EnvelopeError):
        parse_envelope(b"not-json")


def test_parse_envelope_rejects_missing_type() -> None:
    with pytest.raises(EnvelopeError):
        parse_envelope(json.dumps({"from": "x"}).encode())


def test_parse_envelope_rejects_non_object() -> None:
    with pytest.raises(EnvelopeError):
        parse_envelope(b'["not", "object"]')


def test_extra_fields_preserved_via_extra_allow() -> None:
    """Forward-compat: unknown fields don't break parsing."""
    payload = json.dumps(
        {
            "type": "query",
            "from": "alice",
            "to": "bob",
            "query": "status?",
            "future_field": "ok",
        }
    ).encode()
    decoded = parse_envelope(payload)
    assert isinstance(decoded, Query)
    # extra=allow means it's accessible via __dict__
    assert getattr(decoded, "future_field", None) == "ok"


def test_to_json_excludes_none() -> None:
    q = Query(**{"from": "alice"}, to="bob", query="status?")
    body = q.to_json()
    parsed = json.loads(body)
    # fields/reply_to/request_id are all None and should NOT appear
    assert "fields" not in parsed
    assert "reply_to" not in parsed
    assert "request_id" not in parsed


def test_alias_from_keyword() -> None:
    """``from`` is a Python keyword; we accept it as alias and expose .from_."""
    q = Query.model_validate({"from": "alice", "to": "bob", "query": "ping"})
    assert q.from_ == "alice"
    # round-trip back to alias-form
    assert q.model_dump(by_alias=True)["from"] == "alice"
