# Generated by GPT-5 + Kimi-K2

import random
import unittest
import numpy as np
from invisicode import (
	u32_to_str, str_to_u32, leb128, decode_leb128,
	l128_encode, l128_decode, encode, decode, is_invisicode,
	detect_and_decode,
	InvisicodeEncodeError, InvisicodeDecodeError
)


class TestU32Conversion(unittest.TestCase):
	"""Test u32_to_str and str_to_u32 conversion functions."""

	def test_str_to_u32_basic(self):
		"""Test basic string to u32 conversion."""
		result = str_to_u32("hello")
		expected = np.array([104, 101, 108, 108, 111], dtype=np.uint32)
		np.testing.assert_array_equal(result, expected)

	def test_str_to_u32_empty(self):
		"""Test empty string conversion."""
		result = str_to_u32("")
		expected = np.array([], dtype=np.uint32)
		np.testing.assert_array_equal(result, expected)

	def test_str_to_u32_unicode(self):
		"""Test unicode characters conversion."""
		result = str_to_u32("Hello 世界 🌍")
		self.assertIsInstance(result, np.ndarray)
		self.assertEqual(result.dtype, np.uint32)
		self.assertGreater(len(result), 0)

	def test_u32_to_str_basic(self):
		"""Test basic u32 to string conversion."""
		arr = np.array([104, 101, 108, 108, 111], dtype=np.uint32)
		result = u32_to_str(arr)
		self.assertEqual(result, "hello")

	def test_u32_to_str_empty(self):
		"""Test empty array conversion."""
		arr = np.array([], dtype=np.uint32)
		result = u32_to_str(arr)
		self.assertEqual(result, "")

	def test_roundtrip_conversion(self):
		"""Test that str_to_u32 and u32_to_str are inverses."""
		original = "Hello, World! 🎉"
		converted = u32_to_str(str_to_u32(original))
		self.assertEqual(original, converted)


class TestLeb128(unittest.TestCase):
	"""Test leb128 encoding and decoding functions."""

	def test_leb128_positive_small(self):
		"""Test LEB128 encoding of small positive numbers."""
		self.assertEqual(leb128(0), bytearray([0]))
		self.assertEqual(leb128(1), bytearray([1]))
		self.assertEqual(leb128(127), bytearray([127]))
		self.assertEqual(leb128(128), bytearray([128, 1]))

	def test_leb128_negative(self):
		"""Test LEB128 encoding of negative numbers."""
		self.assertEqual(leb128(-1), bytearray([129, 0]))
		self.assertEqual(leb128(-127), bytearray([255, 0]))
		self.assertEqual(leb128(-128), bytearray([128, 129, 0]))  # Fixed expected value

	def test_leb128_large_numbers(self):
		"""Test LEB128 encoding of large numbers."""
		result = leb128(0x123456)
		self.assertIsInstance(result, bytearray)
		self.assertGreater(len(result), 0)

	def test_decode_leb128_positive(self):
		"""Test LEB128 decoding of positive numbers."""
		value, remaining = decode_leb128(bytearray([1]))
		self.assertEqual(value, 1)
		self.assertEqual(remaining, bytearray())

		value, remaining = decode_leb128(bytearray([128, 1]))
		self.assertEqual(value, 128)
		self.assertEqual(remaining, bytearray())

	def test_decode_leb128_negative(self):
		"""Test LEB128 decoding of negative numbers."""
		value, remaining = decode_leb128(bytearray([129, 0]))
		self.assertEqual(value, -1)
		self.assertEqual(remaining, bytearray())

	def test_decode_leb128_with_remaining(self):
		"""Test LEB128 decoding with remaining data."""
		data = bytearray([1, 2, 3])
		value, remaining = decode_leb128(data)
		self.assertEqual(value, 1)
		self.assertEqual(remaining, bytearray([2, 3]))

	def test_leb128_roundtrip(self):
		"""Test that leb128 and decode_leb128 are inverses."""
		test_values = [0, 1, 127, 128, 255, 256, -1, -127, -128, 12345, -12345]
		for value in test_values:
			encoded = leb128(value)
			decoded, remaining = decode_leb128(encoded)
			self.assertEqual(decoded, value)
			self.assertEqual(len(remaining), 0)


class TestL128StringEncoding(unittest.TestCase):
	"""Test l128_encode and l128_decode functions."""

	def test_l128_encode_basic(self):
		"""Test basic string encoding with LEB128."""
		result = l128_encode("hello")
		self.assertIsInstance(result, memoryview)
		self.assertGreater(len(result), 0)

	def test_l128_encode_empty(self):
		"""Test empty string encoding."""
		result = l128_encode("")
		self.assertEqual(result, b"")

	def test_l128_encode_unicode(self):
		"""Test unicode string encoding."""
		result = l128_encode("Hello 世界 🌍")
		self.assertIsInstance(result, memoryview)
		self.assertGreater(len(result), 0)

	def test_l128_decode_basic(self):
		"""Test basic string decoding."""
		encoded = l128_encode("hello")
		decoded = l128_decode(encoded)
		self.assertEqual(decoded, "hello")

	def test_l128_decode_empty(self):
		"""Test empty bytes decoding."""
		result = l128_decode(b"")
		self.assertEqual(result, "")

	def test_l128_decode_invalid_unicode(self):
		"""Test decoding with invalid unicode values."""
		# Create encoded data with a value beyond valid unicode range
		invalid_data = leb128(0x110001)  # Beyond the valid unicode range
		with self.assertRaises(UnicodeDecodeError):
			l128_decode(bytes(invalid_data))

	def test_l128_roundtrip(self):
		"""Test that l128_encode and l128_decode are inverses."""
		test_strings = [
			"",
			"hello",
			"Hello, World!",
			"Unicode: 世界 🌍",
			"Special chars: \n\t\r",
			"Mixed: ABC世界123🎉",
		]
		for s in test_strings:
			encoded = l128_encode(s)
			decoded = l128_decode(encoded)
			self.assertEqual(decoded, s)

	def test_l128_encode_matches_reference(self):
		"""Ensure vectorised encoder matches reference leb128 implementation."""
		base_points = [
			0,
			1,
			0x7F,
			0x80,
			0x81,
			0x3FFF,
			0x4000,
			0x4001,
			0xFFFF,
			0x10000,
			0x10FFFF,
		]
		test_string = "".join(chr(cp) for cp in base_points) * 4
		reference = b"".join(leb128(ord(ch)) for ch in test_string)
		self.assertEqual(l128_encode(test_string), reference)

	def test_l128_decode_matches_reference(self):
		"""Ensure vectorised decoder matches reference decode_leb128 implementation."""
		rng = random.Random(1337)
		points = []
		while len(points) < 1000:
			cp = rng.randrange(0x110000)
			if 0xD800 <= cp <= 0xDFFF:
				continue
			points.append(cp)
		test_string = "".join(chr(cp) for cp in points)
		encoded = l128_encode(test_string)

		decoded_points = []
		remaining = memoryview(encoded)
		while remaining:
			value, remaining = decode_leb128(remaining)
			decoded_points.append(value)

		expected = u32_to_str(np.array(decoded_points, dtype=np.uint32))
		self.assertEqual(l128_decode(encoded), expected)


class TestInvisicodeEncodeDecode(unittest.TestCase):
	"""Test the main encode and decode functions."""

	def test_encode_bytes_basic(self):
		"""Test basic bytes encoding."""
		data = b"hello world"
		result = encode(data)
		self.assertIsInstance(result, str)
		self.assertTrue(len(result) > 0)
		self.assertTrue(all(0xe0000 <= ord(c) < 0xe1000 for c in result))

	def test_encode_string_basic(self):
		"""Test basic string encoding."""
		data = "hello world"
		result = encode(data)
		self.assertIsInstance(result, str)
		self.assertTrue(result.startswith(chr(0x1d17a)))  # STRINGPREFIX
		self.assertTrue(len(result) > 1)

	def test_encode_empty_bytes(self):
		"""Test empty bytes encoding."""
		result = encode(b"")
		self.assertEqual(result, "")

	def test_encode_empty_string(self):
		"""Test empty string encoding."""
		result = encode("")
		self.assertEqual(result, chr(0x1d17a))  # Just the prefix

	def test_encode_numpy_array(self):
		"""Test encoding numpy array."""
		arr = np.array([1, 2, 3, 4, 5], dtype=np.uint8)
		result = encode(arr)
		self.assertIsInstance(result, str)
		self.assertTrue(len(result) > 0)

	def test_encode_bytearray(self):
		"""Test encoding bytearray."""
		data = bytearray([1, 2, 3, 4, 5])
		result = encode(data)
		self.assertIsInstance(result, str)
		self.assertTrue(len(result) > 0)

	def test_encode_memoryview(self):
		"""Test encoding memoryview."""
		data = memoryview(b"test data")
		result = encode(data)
		self.assertIsInstance(result, str)
		self.assertTrue(len(result) > 0)

	def test_decode_bytes_basic(self):
		"""Test basic bytes decoding."""
		original = b"hello world"
		encoded = encode(original)
		decoded = decode(encoded)
		self.assertEqual(decoded, original)

	def test_decode_string_basic(self):
		"""Test basic string decoding."""
		original = "hello world"
		encoded = encode(original)
		decoded = decode(encoded)
		self.assertEqual(decoded, original)

	def test_decode_with_expect_bytes(self):
		"""Test decoding with expect=bytes parameter."""
		original = b"test data"
		encoded = encode(original)
		decoded = decode(encoded, expect=bytes)
		self.assertEqual(decoded, original)
		self.assertIsInstance(decoded, bytes)

	def test_decode_with_expect_str(self):
		"""Test decoding with expect=str parameter."""
		original = "test string"
		encoded = encode(original)
		decoded = decode(encoded, expect=str)
		self.assertEqual(decoded, original)
		self.assertIsInstance(decoded, str)

	def test_decode_mismatch_expect_bytes(self):
		"""Test decoding with mismatched expect=bytes."""
		original = "test string"
		encoded = encode(original)
		with self.assertRaises(InvisicodeDecodeError):
			decode(encoded, expect=bytes)

	def test_decode_mismatch_expect_str(self):
		"""Test decoding with mismatched expect=str."""
		original = b"test data"
		encoded = encode(original)
		with self.assertRaises(InvisicodeDecodeError):
			decode(encoded, expect=str)

	def test_decode_invalid_character(self):
		"""Test decoding with invalid character."""
		# Create valid encoded string and insert invalid character in the middle
		valid_encoded = encode(b"test")
		# Insert an invalid character after the first valid character
		invalid_encoded = valid_encoded[0] + chr(0xd0000) + valid_encoded[1:]
		with self.assertRaises(InvisicodeDecodeError):
			decode(invalid_encoded)

	def test_decode_allow_invalid(self):
		"""Test decoding with invalid character but strict=False."""
		# Create valid encoded string and insert invalid character in the middle
		original = b"test"
		valid_encoded = encode(original)
		# Insert an invalid character after the first valid character
		invalid_encoded = valid_encoded[0] + chr(0xd0000) + valid_encoded[1:]
		valid_decoded = decode(invalid_encoded, strict=False)
		self.assertEqual(valid_decoded, original)

	def test_decode_multi(self):
		"""Test detecting and decoding multiple encoded substrings, making sure each one maintains its original type."""
		originals = ["test1", b"test2", "test3", b"test4"]
		invalids = ["a", "bc", "def", "ghij", "klmno"]
		s = ""
		for x, y in zip(invalids, originals):
			s += x
			if y:
				s += encode(y)
		self.assertEqual(detect_and_decode(s), originals)

	def test_roundtrip_bytes_various_lengths(self):
		"""Test roundtrip encoding/decoding of bytes with various lengths."""
		test_cases = [
			b"",  # Empty
			b"a",  # 1 byte
			b"ab",  # 2 bytes
			b"abc",  # 3 bytes
			b"abcd",  # 4 bytes
			b"a" * 10,  # 10 bytes
			b"a" * 100,  # 100 bytes
			b"a" * 10_000,  # 10 thousand bytes
			bytes(range(256)),  # All possible byte values
			random.randbytes(10**8),  # Stress test
		]
		for data in test_cases:
			with self.subTest(data_length=len(data)):
				encoded = encode(data)
				decoded = decode(encoded)
				self.assertEqual(decoded, data)

	def test_roundtrip_string_various_content(self):
		"""Test roundtrip encoding/decoding of strings with various content."""
		test_cases = [
			"",
			"a",
			"hello",
			"Hello, World!",
			"Unicode: 世界 🌍",
			"Special chars: \n\t\r\0",
			"Mixed: ABC世界123🎉",
			"abcde" * 1000,
			"x" * 10**8,  # Stress test
		]
		for s in test_cases:
			with self.subTest(string=s[:20]):
				encoded = encode(s)
				decoded = decode(encoded)
				self.assertEqual(decoded, s)


class TestIsInvisicode(unittest.TestCase):
	"""Test the is_invisicode validation function."""

	def test_is_invisicode_valid_bytes(self):
		"""Test valid invisicode bytes encoding."""
		encoded = encode(b"test data")
		self.assertTrue(is_invisicode(encoded))

	def test_is_invisicode_valid_string(self):
		"""Test valid invisicode string encoding."""
		encoded = encode("test string")
		self.assertTrue(is_invisicode(encoded))

	def test_is_invisicode_empty_strict(self):
		"""Test empty string with strict=True."""
		self.assertFalse(is_invisicode("", strict=True))

	def test_is_invisicode_empty_not_strict(self):
		"""Test empty string with strict=False."""
		self.assertTrue(is_invisicode("", strict=False))

	def test_is_invisicode_invalid_character(self):
		"""Test string with invalid character."""
		self.assertFalse(is_invisicode("hello"))
		self.assertFalse(is_invisicode(chr(0xd0000)))

	def test_is_invisicode_mixed_valid_invalid(self):
		"""Test string with mix of valid and invalid characters."""
		valid_part = encode(b"test")[1:]  # Remove prefix
		mixed = valid_part + "a"  # Add invalid ASCII
		self.assertFalse(is_invisicode(mixed))

	def test_is_invisicode_strict_vs_non_strict(self):
		"""Test difference between strict and non-strict modes."""
		# This test explores the behavior difference
		# In strict mode, all characters must be in range
		# In non-strict mode, trailing characters may exist and be discarded
		test_string = encode(b"test") + "test"
		self.assertFalse(is_invisicode(test_string, strict=True))
		self.assertTrue(is_invisicode(test_string, strict=False))


class TestErrorClasses(unittest.TestCase):
	"""Test custom error classes."""

	def test_invisicode_encode_error(self):
		"""Test InvisicodeEncodeError is a ValueError."""
		self.assertTrue(issubclass(InvisicodeEncodeError, ValueError))
		error = InvisicodeEncodeError("Test error")
		self.assertIsInstance(error, ValueError)
		self.assertEqual(str(error), "Test error")

	def test_invisicode_decode_error(self):
		"""Test InvisicodeDecodeError is a ValueError."""
		self.assertTrue(issubclass(InvisicodeDecodeError, ValueError))
		error = InvisicodeDecodeError("Test error")
		self.assertIsInstance(error, ValueError)
		self.assertEqual(str(error), "Test error")


if __name__ == "__main__":
	unittest.main()