"""Tests for telnetlib3.mud encoding and decoding."""

# 3rd party
import pytest

# local
from telnetlib3.mud import (
    zmp_decode,
    zmp_encode,
    atcp_decode,
    gmcp_decode,
    gmcp_encode,
    msdp_decode,
    msdp_encode,
    mssp_decode,
    mssp_encode,
    aardwolf_decode,
)
from telnetlib3.telopt import (
    MSDP_VAL,
    MSDP_VAR,
    MSSP_VAL,
    MSSP_VAR,
    MSDP_ARRAY_OPEN,
    MSDP_TABLE_OPEN,
    MSDP_ARRAY_CLOSE,
    MSDP_TABLE_CLOSE,
)


def test_gmcp_roundtrip() -> None:
    """Encode and decode GMCP with nested data."""
    package = "Char.Vitals"
    data = {"hp": 100, "maxhp": 120, "mp": 50}
    encoded = gmcp_encode(package, data)
    decoded_pkg, decoded_data = gmcp_decode(encoded)
    assert decoded_pkg == package
    assert decoded_data == data


def test_gmcp_package_only() -> None:
    """Encode and decode GMCP package without data."""
    package = "Core.Hello"
    encoded = gmcp_encode(package)
    assert encoded == b"Core.Hello"
    decoded_pkg, decoded_data = gmcp_decode(encoded)
    assert decoded_pkg == package
    assert decoded_data is None


def test_gmcp_nested_json() -> None:
    """Encode and decode GMCP with nested structures."""
    package = "Room.Info"
    data = {
        "name": "The Inn",
        "exits": ["north", "south"],
        "items": [{"name": "sword", "id": 123}, {"name": "shield", "id": 456}],
    }
    encoded = gmcp_encode(package, data)
    decoded_pkg, decoded_data = gmcp_decode(encoded)
    assert decoded_pkg == package
    assert decoded_data == data


def test_gmcp_decode_encoding_param() -> None:
    """gmcp_decode uses the encoding parameter for decoding."""
    decoded_pkg, decoded_data = gmcp_decode(b"Caf\xe9", encoding="latin-1")
    assert decoded_pkg == "Caf\xe9"
    assert decoded_data is None


def test_gmcp_decode_latin1_fallback() -> None:
    """gmcp_decode falls back to latin-1 when utf-8 fails."""
    decoded_pkg, decoded_data = gmcp_decode(b"Caf\xe9")
    assert decoded_pkg == "Caf\xe9"
    assert decoded_data is None


def test_gmcp_decode_invalid_json() -> None:
    """Decode GMCP with invalid JSON raises ValueError."""
    with pytest.raises(ValueError):
        gmcp_decode(b"Package {bad json}")


def test_msdp_simple() -> None:
    """Encode and decode simple MSDP variable."""
    variables = {"FOO": "bar"}
    encoded = msdp_encode(variables)
    expected = MSDP_VAR + b"FOO" + MSDP_VAL + b"bar"
    assert encoded == expected
    decoded = msdp_decode(encoded)
    assert decoded == variables


def test_msdp_multiple() -> None:
    """Encode and decode multiple MSDP variables."""
    variables = {"A": "1", "B": "2"}
    encoded = msdp_encode(variables)
    decoded = msdp_decode(encoded)
    assert decoded == variables


def test_msdp_nested_table() -> None:
    """Encode and decode MSDP with nested table."""
    variables = {"ROOM": {"NAME": "Inn", "EXITS": "north,south"}}
    encoded = msdp_encode(variables)
    assert MSDP_TABLE_OPEN in encoded
    assert MSDP_TABLE_CLOSE in encoded
    decoded = msdp_decode(encoded)
    assert decoded == variables


def test_msdp_array() -> None:
    """Encode and decode MSDP with array."""
    variables = {"LIST": ["a", "b", "c"]}
    encoded = msdp_encode(variables)
    assert MSDP_ARRAY_OPEN in encoded
    assert MSDP_ARRAY_CLOSE in encoded
    decoded = msdp_decode(encoded)
    assert decoded == variables


def test_msdp_mixed() -> None:
    """Encode and decode MSDP with mixed value types."""
    variables = {
        "NAME": "Player",
        "STATS": {"HP": "100", "MP": "50"},
        "SKILLS": ["sword", "shield", "magic"],
    }
    encoded = msdp_encode(variables)
    decoded = msdp_decode(encoded)
    assert decoded == variables


def test_msdp_empty_value() -> None:
    """Encode and decode MSDP with empty string value."""
    variables = {"KEY": ""}
    encoded = msdp_encode(variables)
    decoded = msdp_decode(encoded)
    assert decoded == variables


def test_msdp_decode_encoding_param() -> None:
    """msdp_decode uses the encoding parameter for decoding."""
    encoded = MSDP_VAR + b"KEY" + MSDP_VAL + b"Caf\xe9"
    decoded = msdp_decode(encoded, encoding="latin-1")
    assert decoded == {"KEY": "Caf\xe9"}


def test_msdp_decode_latin1_fallback() -> None:
    """msdp_decode falls back to latin-1 when utf-8 fails."""
    encoded = MSDP_VAR + b"KEY" + MSDP_VAL + b"Caf\xe9"
    decoded = msdp_decode(encoded)
    assert decoded == {"KEY": "Caf\xe9"}


def test_mssp_single_value() -> None:
    """Encode and decode MSSP with single values."""
    variables = {"NAME": "TestMUD", "UPTIME": "12345"}
    encoded = mssp_encode(variables)
    decoded = mssp_decode(encoded)
    assert decoded == variables


def test_mssp_multi_value() -> None:
    """Encode and decode MSSP with multi-value field."""
    variables = {"PORT": ["6023", "6024", "6025"]}
    encoded = mssp_encode(variables)
    decoded = mssp_decode(encoded)
    assert decoded == variables


def test_mssp_roundtrip() -> None:
    """Full roundtrip with mixed single and multi values."""
    variables = {
        "NAME": "TestMUD",
        "PORT": ["6023", "6024"],
        "CODEBASE": "Custom",
        "CONTACT": "admin@test.mud",
    }
    encoded = mssp_encode(variables)
    decoded = mssp_decode(encoded)
    assert decoded == variables


def test_mssp_decode_multi_returns_list() -> None:
    """Verify single value returns str, multiple values return list."""
    encoded = (
        MSSP_VAR
        + b"SINGLE"
        + MSSP_VAL
        + b"one"
        + MSSP_VAR
        + b"MULTI"
        + MSSP_VAL
        + b"first"
        + MSSP_VAL
        + b"second"
    )
    decoded = mssp_decode(encoded)
    assert decoded["SINGLE"] == "one"
    assert decoded["MULTI"] == ["first", "second"]


def test_mssp_decode_encoding_param() -> None:
    """mssp_decode uses the encoding parameter for decoding."""
    encoded = MSSP_VAR + b"NAME" + MSSP_VAL + b"\xc9toile"
    decoded = mssp_decode(encoded, encoding="latin-1")
    assert decoded == {"NAME": "\xc9toile"}


def test_mssp_decode_latin1_fallback() -> None:
    """mssp_decode falls back to latin-1 when utf-8 fails."""
    encoded = MSSP_VAR + b"NAME" + MSSP_VAL + b"\xc9toile"
    decoded = mssp_decode(encoded)
    assert decoded == {"NAME": "\xc9toile"}


def test_zmp_decode_ident() -> None:
    result = zmp_decode(b"zmp.ident\x00MudName\x001.0\x00A test MUD\x00")
    assert result == ["zmp.ident", "MudName", "1.0", "A test MUD"]


def test_zmp_decode_single_command() -> None:
    result = zmp_decode(b"zmp.ping\x00")
    assert result == ["zmp.ping"]


def test_zmp_decode_no_trailing_nul() -> None:
    result = zmp_decode(b"zmp.check\x00zmp.ping")
    assert result == ["zmp.check", "zmp.ping"]


def test_zmp_decode_empty() -> None:
    assert zmp_decode(b"") == []


def test_zmp_decode_latin1_fallback() -> None:
    result = zmp_decode(b"zmp.ident\x00Caf\xe9\x00")
    assert result == ["zmp.ident", "Caf\xe9"]


def test_atcp_decode_with_value() -> None:
    package, value = atcp_decode(b"Room.Exits ne,sw,nw")
    assert package == "Room.Exits"
    assert value == "ne,sw,nw"


def test_atcp_decode_no_value() -> None:
    package, value = atcp_decode(b"Conn.MXP")
    assert package == "Conn.MXP"
    assert not value


def test_atcp_decode_value_with_spaces() -> None:
    package, value = atcp_decode(b"Room.Name The Grand Hall")
    assert package == "Room.Name"
    assert value == "The Grand Hall"


def test_atcp_decode_empty() -> None:
    package, value = atcp_decode(b"")
    assert not package
    assert not value


def test_atcp_decode_latin1_fallback() -> None:
    package, value = atcp_decode(b"Room.Name Caf\xe9")
    assert package == "Room.Name"
    assert value == "Caf\xe9"


def test_aardwolf_decode_status() -> None:
    result = aardwolf_decode(bytes([100, 3]))
    assert result["channel"] == "status"
    assert result["channel_byte"] == 100
    assert result["data_byte"] == 3


def test_aardwolf_decode_tick() -> None:
    result = aardwolf_decode(bytes([101, 1]))
    assert result["channel"] == "tick"
    assert result["data_byte"] == 1


def test_aardwolf_decode_unknown_channel() -> None:
    result = aardwolf_decode(bytes([200, 5]))
    assert result["channel"] == "0xc8"
    assert result["channel_byte"] == 200
    assert result["data_byte"] == 5


def test_aardwolf_decode_empty() -> None:
    result = aardwolf_decode(b"")
    assert result["channel"] == "unknown"
    assert result["channel_byte"] == 0


def test_aardwolf_decode_single_byte() -> None:
    result = aardwolf_decode(bytes([102]))
    assert result["channel"] == "affect"
    assert "data_byte" not in result


def test_aardwolf_decode_long_payload() -> None:
    result = aardwolf_decode(bytes([100, 3, 4, 5]))
    assert result["channel"] == "status"
    assert result["data_bytes"] == bytes([3, 4, 5])


def test_gmcp_decode_empty_json_text():
    pkg, data = gmcp_decode(b"Char.Vitals ")
    assert pkg == "Char.Vitals"
    assert data is None


def test_msdp_decode_skips_garbage_bytes():
    buf = b"\x42" + MSDP_VAR + b"KEY" + MSDP_VAL + b"val"
    result = msdp_decode(buf)
    assert result == {"KEY": "val"}


def test_mssp_decode_duplicate_var_to_list():
    buf = MSSP_VAR + b"PORT" + MSSP_VAL + b"6023" + MSSP_VAR + b"PORT" + MSSP_VAL + b"6024"
    result = mssp_decode(buf)
    assert result["PORT"] == ["6023", "6024"]


def test_mssp_decode_triple_value_appends():
    buf = (
        MSSP_VAR
        + b"PORT"
        + MSSP_VAL
        + b"6023"
        + MSSP_VAR
        + b"PORT"
        + MSSP_VAL
        + b"6024"
        + MSSP_VAR
        + b"PORT"
        + MSSP_VAL
        + b"6025"
    )
    result = mssp_decode(buf)
    assert result["PORT"] == ["6023", "6024", "6025"]


def test_mssp_decode_skips_garbage_bytes():
    buf = b"\x42" + MSSP_VAR + b"NAME" + MSSP_VAL + b"TestMUD"
    result = mssp_decode(buf)
    assert result == {"NAME": "TestMUD"}


def test_zmp_encode_decode_roundtrip():
    encoded = zmp_encode("zmp.ident", "telnetlib3", "1.0")
    assert encoded == b"zmp.ident\x00telnetlib3\x001.0\x00"
    decoded = zmp_decode(encoded)
    assert decoded == ["zmp.ident", "telnetlib3", "1.0"]


def test_zmp_encode_no_args():
    encoded = zmp_encode("zmp.ping")
    assert encoded == b"zmp.ping\x00"
    assert zmp_decode(encoded) == ["zmp.ping"]


def test_zmp_encode_multiple_args():
    encoded = zmp_encode("zmp.check", "char.vitals")
    assert encoded == b"zmp.check\x00char.vitals\x00"
    assert zmp_decode(encoded) == ["zmp.check", "char.vitals"]


def test_zmp_decode_latin1_fallback():
    buf = b"echo\x00caf\xe9\x00"
    result = zmp_decode(buf)
    assert result == ["echo", "caf\xe9"]
