"""Sends packets with various length to check the server reassembles them
correctly. Also checks truncation"""

import socket

from irctest import cases
from irctest.irc_utils import message_parser
from irctest.numerics import ERR_INPUTTOOLONG
from irctest.patma import ANYSTR


def _sendWhole(self, line):
    print("sending", repr(line.encode()))
    self.clients[1].conn.sendall(line.encode())


def _sendCharPerChar(self, line):
    print("sending", repr(line.encode()))
    for char in line:
        self.clients[1].conn.sendall(char.encode())


def _sendBytePerByte(self, line):
    print("sending", repr(line.encode()))
    for byte in line.encode():
        self.clients[1].conn.sendall(bytes([byte]))


def _testNoTags(sender_function, colon):
    def f(self):
        self.connectClient("foo")
        self.connectClient("bar")

        overhead = self.get_overhead(1, 2, colon=colon)
        print(f"overhead is {overhead}")

        line = f"PRIVMSG bar {colon}"
        remaining_size = 512 - len(line) - len("\r\n")
        emoji_size = len("😃".encode())
        payloads = [
            # one byte:
            "a",
            # one multi-byte char:
            "😃",
            # full payload, will be truncated
            "a" * remaining_size,
            "a" * (remaining_size - emoji_size) + "😃",
            # full payload to recipient:
            "a" * (remaining_size - overhead),
            "a" * (remaining_size - emoji_size - overhead) + "😃",
            # full payload to recipient plus one byte:
            "a" * (remaining_size - overhead + 1),
            "a" * (remaining_size - emoji_size - overhead + 1) + "😃",
            # full payload to recipient plus two bytes:
            "a" * (remaining_size - emoji_size - overhead + 1) + "😃",
        ]
        for payload in payloads:
            sender_function(self, line + payload + "\r\n")
            messages = self.getMessages(1)
            if messages and ERR_INPUTTOOLONG in (m.command for m in messages):
                # https://defs.ircdocs.horse/defs/numerics.html#err-inputtoolong-417
                self.assertGreater(
                    len(line + payload + "\r\n"),
                    512 - overhead,
                    "Got ERR_INPUTTOOLONG for a messag that should fit "
                    "withing 512 characters.",
                )
                continue

            received_line = self._getLine(2)
            print("received", repr(received_line))
            try:
                decoded_line = received_line.decode()
            except UnicodeDecodeError:
                # server truncated a byte off the emoji at the end
                if "UTF8ONLY" in self.server_support:
                    # https://github.com/ircv3/ircv3-specifications/pull/432
                    raise self.failureException(
                        f"Server advertizes UTF8ONLY, but sent an invalid UTF8 "
                        f"message: {received_line!r}"
                    )
                payload_intact = False
            else:
                msg = message_parser.parse_message(decoded_line)
                self.assertMessageMatch(msg, command="PRIVMSG", params=["bar", ANYSTR])
                payload_intact = msg.params[1] == payload
            if not payload_intact:
                # truncated
                self.assertLessEqual(len(received_line), 512, received_line)
                self.assertTrue(
                    payload.encode().startswith(
                        received_line.split(b" ")[-1].strip().lstrip(b":")
                    ),
                    f"expected payload to be a prefix of {payload!r}, "
                    f"but got {payload!r}",
                )

    return f


class BufferingTestCase(cases.BaseServerTestCase):
    # show_io = False

    def get_overhead(self, client1, client2, colon):
        self.sendLine(client1, f"PRIVMSG bar {colon}a\r\n")
        line = self._getLine(client2)
        return len(line) - len(f"PRIVMSG bar {colon}a\r\n")

    def _getLine(self, client) -> bytes:
        line = b""
        while True:
            try:
                data = self.clients[client].conn.recv(4096)
            except socket.timeout:
                data = b""
            line += data
            if not data or data.endswith(b"\r\n"):
                return line

    testNoTagsWholeNoColon = _testNoTags(_sendWhole, colon="")
    testNoTagsCharPerCharNoColon = _testNoTags(_sendCharPerChar, colon="")
    testNoTagsBytePerByteNoColon = _testNoTags(_sendBytePerByte, colon="")
    testNoTagsWholeColon = _testNoTags(_sendWhole, colon=":")
    testNoTagsCharPerCharColon = _testNoTags(_sendCharPerChar, colon=":")
    testNoTagsBytePerByteColon = _testNoTags(_sendBytePerByte, colon=":")

@@ -303,39 +303,45 @@
 ne):%0A    print(%22
-sending
+(repr) 1 -%3E S
 %22, repr(line.enc
@@ -435,39 +435,45 @@
 ne):%0A    print(%22
-sending
+(repr) 1 -%3E S
 %22, repr(line.enc
@@ -605,15 +605,21 @@
 nt(%22
-sending
+(repr) 1 -%3E S
 %22, r
@@ -813,11 +813,13 @@
 nt(%22
-foo
+nick1
 %22)%0A 
@@ -845,19 +845,21 @@
 Client(%22
-bar
+nick2
 %22)%0A%0A    
@@ -969,35 +969,37 @@
 ine = f%22PRIVMSG 
-bar
+nick2
  %7Bcolon%7D%22%0A      
@@ -2450,24 +2450,30 @@
  print(%22
+(
 re
-ceived
+pr) S -%3E 2:
 %22, repr(
@@ -3164,16 +3164,37 @@
 geMatch(
+%0A                    
 msg, com
@@ -3218,19 +3218,21 @@
 arams=%5B%22
-bar
+nick2
 %22, ANYST
@@ -3233,16 +3233,33 @@
  ANYSTR%5D
+%0A                
 )%0A      
@@ -3940,35 +3940,37 @@
 ent1, f%22PRIVMSG 
-bar
+nick2
  %7Bcolon%7Da%5Cr%5Cn%22)%0A
@@ -4052,11 +4052,13 @@
 MSG 
-bar
+nick2
  %7Bco

improve log readability