"""Tests for the binary protocol layer (proto.py)."""

from __future__ import annotations

import socket

import pytest

from routeros.proto import Reader, Writer, decode_length, encode_length

# ---------------------------------------------------------------------------
# encode_length / decode_length round-trips
# ---------------------------------------------------------------------------


class TestEncodeLength:
    """Verify the five byte-width ranges."""

    def test_1byte_zero(self):
        assert encode_length(0) == bytes([0x00])

    def test_1byte_max(self):
        assert encode_length(0x7F) == bytes([0x7F])

    def test_2byte_min(self):
        # 128 = 0x0080 → high nibble 0x80 | 0x00 = 0x80, low = 0x80
        result = encode_length(0x80)
        assert len(result) == 2
        assert result[0] & 0xC0 == 0x80

    def test_2byte_max(self):
        result = encode_length(0x3FFF)
        assert len(result) == 2

    def test_3byte_min(self):
        result = encode_length(0x4000)
        assert len(result) == 3

    def test_3byte_max(self):
        result = encode_length(0x1FFFFF)
        assert len(result) == 3

    def test_4byte_min(self):
        result = encode_length(0x200000)
        assert len(result) == 4

    def test_4byte_max(self):
        result = encode_length(0x0FFFFFFF)
        assert len(result) == 4

    def test_5byte_min(self):
        result = encode_length(0x10000000)
        assert len(result) == 5
        assert result[0] == 0xF0


class TestDecodeLength:
    """Decode from a real socketpair."""

    def _send_and_decode(self, data: bytes) -> int:
        r, w = socket.socketpair()
        try:
            w.sendall(data)
            return decode_length(r)
        finally:
            r.close()
            w.close()

    def test_1byte(self):
        assert self._send_and_decode(encode_length(0)) == 0
        assert self._send_and_decode(encode_length(1)) == 1
        assert self._send_and_decode(encode_length(0x7F)) == 0x7F

    def test_2byte(self):
        assert self._send_and_decode(encode_length(0x80)) == 0x80
        assert self._send_and_decode(encode_length(0x3FFF)) == 0x3FFF

    def test_3byte(self):
        assert self._send_and_decode(encode_length(0x4000)) == 0x4000
        assert self._send_and_decode(encode_length(0x1FFFFF)) == 0x1FFFFF

    def test_4byte(self):
        assert self._send_and_decode(encode_length(0x200000)) == 0x200000
        assert self._send_and_decode(encode_length(0x0FFFFFFF)) == 0x0FFFFFFF

    def test_5byte(self):
        assert self._send_and_decode(encode_length(0x10000000)) == 0x10000000

    def test_roundtrip_sample_values(self):
        for n in [0, 1, 127, 128, 255, 1000, 16383, 16384, 2097151, 2097152, 268435455, 268435456]:
            r, w = socket.socketpair()
            try:
                w.sendall(encode_length(n))
                assert decode_length(r) == n
            finally:
                r.close()
                w.close()


# ---------------------------------------------------------------------------
# Reader / Writer round-trip
# ---------------------------------------------------------------------------


class TestReaderWriter:
    def _make_pair(self):
        r_sock, w_sock = socket.socketpair()
        return Reader(r_sock), Writer(w_sock), r_sock, w_sock

    def test_empty_sentence(self):
        reader, writer, rs, ws = self._make_pair()
        try:
            writer.write_sentence([])
            sen = reader.read_sentence()
            assert sen.word == ""
            assert sen.pairs == []
            assert sen.map == {}
        finally:
            rs.close(); ws.close()

    def test_command_word(self):
        reader, writer, rs, ws = self._make_pair()
        try:
            writer.write_sentence(["/ip/address/print"])
            sen = reader.read_sentence()
            assert sen.word == "/ip/address/print"
        finally:
            rs.close(); ws.close()

    def test_response_sentence(self):
        reader, writer, rs, ws = self._make_pair()
        try:
            writer.write_sentence(["!re", "=address=192.168.1.1/24", "=interface=ether1"])
            sen = reader.read_sentence()
            assert sen.word == "!re"
            assert sen.map["address"] == "192.168.1.1/24"
            assert sen.map["interface"] == "ether1"
        finally:
            rs.close(); ws.close()

    def test_tag_word(self):
        reader, writer, rs, ws = self._make_pair()
        try:
            writer.write_sentence(["!re", ".tag=r7", "=key=val"])
            sen = reader.read_sentence()
            assert sen.tag == "r7"
            assert sen.map["key"] == "val"
        finally:
            rs.close(); ws.close()

    def test_multiple_sentences(self):
        reader, writer, rs, ws = self._make_pair()
        try:
            writer.write_sentence(["!re", "=a=1"])
            writer.write_sentence(["!done"])
            s1 = reader.read_sentence()
            s2 = reader.read_sentence()
            assert s1.word == "!re"
            assert s2.word == "!done"
        finally:
            rs.close(); ws.close()

    def test_key_without_value(self):
        """=key (no =value part) → value is empty string."""
        reader, writer, rs, ws = self._make_pair()
        try:
            writer.write_sentence(["!re", "=disabled"])
            sen = reader.read_sentence()
            assert sen.map["disabled"] == ""
        finally:
            rs.close(); ws.close()

    def test_unicode_value(self):
        reader, writer, rs, ws = self._make_pair()
        try:
            writer.write_sentence(["!re", "=name=тест"])
            sen = reader.read_sentence()
            assert sen.map["name"] == "тест"
        finally:
            rs.close(); ws.close()

    def test_eof_raises(self):
        reader, writer, rs, ws = self._make_pair()
        ws.close()
        with pytest.raises(EOFError):
            reader.read_sentence()
        rs.close()
