#!/usr/bin/env python

# THIS FILE WAS GENERATED BY A LLM.
# The tests help improve coverage but are not hand crafted.

import sys

sys.path.insert(0, "..")
import pytest
from tibs import Tibs, Mutibs


class TestTibsCreation:
    def test_from_bin_simple(self):
        assert Tibs.from_bin("101") == Tibs('0b101')

    def test_from_bin_with_prefix(self):
        assert Tibs.from_bin("0b110") == Tibs('0b110')

    def test_from_oct_simple(self):
        assert Tibs.from_oct("75") == Tibs('0o75')

    def test_from_oct_with_prefix(self):
        assert Tibs.from_oct("0o64") == Tibs('0o64')

    def test_from_hex_simple(self):
        assert Tibs.from_hex("af") == Tibs('0xaf')

    def test_from_hex_with_prefix(self):
        assert Tibs.from_hex("0xbe") == Tibs('0xbe')

    def test_from_u_basic(self):
        assert Tibs.from_u(42, 8) == Tibs('0b00101010')

    def test_from_i_positive(self):
        assert Tibs.from_i(42, 8) == Tibs('0b00101010')

    def test_from_i_negative(self):
        assert Tibs.from_i(-42, 8) == Tibs('0b11010110')

    def test_from_f16(self):
        assert len(Tibs.from_f(3.14, 16)) == 16

    def test_from_f32(self):
        assert len(Tibs.from_f(3.14, 32)) == 32

    def test_from_f64(self):
        assert len(Tibs.from_f(3.14, 64)) == 64

    def test_from_bytes(self):
        assert Tibs.from_bytes(b"abc") == Tibs('0x616263')

    def test_from_string_bin(self):
        assert Tibs.from_string("0b101") == Tibs('0b101')

    def test_from_string_hex(self):
        assert Tibs.from_string("0xabc") == Tibs('0xabc')

    def test_from_bools(self):
        assert Tibs.from_bools([True, False, 1, 0]) == Tibs('0b1010')

    def test_from_zeros(self):
        assert Tibs.from_zeros(5) == Tibs('0b00000')

    def test_from_ones(self):
        assert Tibs.from_ones(5) == Tibs('0b11111')

    def test_from_random(self):
        assert len(Tibs.from_random(100)) == 100

    def test_from_joined(self):
        assert Tibs.from_joined(["0b11", "0o7", "0xf"]) == Tibs('0b111111111')

    def test_constructor_delegation(self):
        assert Tibs("0b101") == Tibs.from_string("0b101")
        assert Tibs(b"abc") == Tibs.from_bytes(b"abc")
        assert Tibs([True, False]) == Tibs.from_bools([True, False])

    def test_from_slice(self):
        a = Tibs("0b101100111000")
        b = a[2:10]
        assert b == Tibs('0b11001110')
        c = Tibs(b)
        assert c == Tibs('0b11001110')


class TestMutibsCreation:
    def test_from_bin_simple(self):
        assert Mutibs.from_bin("101") == Tibs('0b101')

    def test_from_oct_simple(self):
        assert Mutibs.from_oct("75") == Tibs('0o75')

    def test_from_hex_simple(self):
        assert Mutibs.from_hex("af") == Tibs('0xaf')

    def test_from_u_basic(self):
        assert Mutibs.from_u(42, 8) == Tibs('0b00101010')

    def test_from_i_positive(self):
        assert Mutibs.from_i(42, 8) == Tibs('0b00101010')

    def test_from_f32(self):
        assert len(Mutibs.from_f(3.14, 32)) == 32

    def test_from_bytes(self):
        assert Mutibs.from_bytes(b"abc") == Tibs('0x616263')

    def test_from_string_bin(self):
        assert Mutibs.from_string("0b101") == Tibs('0b101')

    def test_from_bools(self):
        assert Mutibs.from_bools([True, False, 1, 0]) == Tibs('0b1010')

    def test_from_zeros(self):
        assert Mutibs.from_zeros(5) == Tibs('0b00000')

    def test_from_ones(self):
        assert Mutibs.from_ones(5) == Tibs('0b11111')

    def test_from_random(self):
        assert len(Mutibs.from_random(100)) == 100

    def test_from_joined(self):
        assert Mutibs.from_joined(["0b11", "0o7", "0xf"]) == Tibs('0b111111111')

    def test_constructor_delegation(self):
        assert Mutibs("0b101") == Mutibs.from_string("0b101")
        assert Mutibs(b"abc") == Mutibs.from_bytes(b"abc")
        assert Mutibs([True, False]) == Mutibs.from_bools([True, False])

    def test_from_slice(self):
        a = Tibs("0b101100111000")
        b = a[2:10]
        assert b == Tibs('0b11001110')
        c = Mutibs(b)
        assert c == Tibs('0b11001110')


class TestTibsMethods:
    def test_eq(self):
        assert Tibs("0xf") == Tibs('0b1111')
        assert not (Tibs("0xf") == Tibs('0b1110'))

    def test_str(self):
        assert str(Tibs("0b101")) == "0b101"

    def test_repr(self):
        assert repr(Tibs("0b101")) == "Tibs('0b101')"


class TestMutibsMethods:
    def test_eq(self):
        assert Mutibs("0xf") == Tibs('0b1111')
        assert not (Mutibs("0xf") == Tibs('0b1110'))

    def test_str(self):
        assert str(Mutibs("0b101")) == "0b101"

    def test_repr(self):
        assert repr(Mutibs("0b101")) == "Mutibs('0b101')"

    def test_set_index(self):
        m = Mutibs("0b000")
        m[1] = 1
        assert m == Tibs('0b010')

    def test_set_slice(self):
        m = Mutibs("0b00000")
        m[1:4] = "0b111"
        assert m == Tibs('0b01110')

    def test_ixor(self):
        m = Mutibs("0b1100")
        m ^= "0b1010"
        assert m == Tibs('0b0110')

    def test_ior(self):
        m = Mutibs("0b1100")
        m |= "0b1010"
        assert m == Tibs('0b1110')

    def test_iand(self):
        m = Mutibs("0b1100")
        m &= "0b1010"
        assert m == Tibs('0b1000')

    def test_set_from_sequence(self):
        m = Mutibs("0b00000")
        m.set([1, 3])
        assert m == Tibs('0b01010')

    def test_set_from_slice(self):
        m = Mutibs("0b00000000")
        m[1:7:2] = [1, 1, 1]
        assert m == Tibs('0b01010100')

    def test_insert_uses_logical_position(self):
        m = Mutibs("0b1100")

        # Build the expected logical bit sequence (the order used by indexing/iteration).
        expected_logical_bits = list(m.to_tibs())
        expected_logical_bits.insert(1, True)

        m.insert(1, "0b1")

        assert list(m.to_tibs()) == expected_logical_bits


class TestIterators:
    def test_bool_iterator(self):
        t = Tibs("0b101")
        assert list(t) == [True, False, True]

    def test_find_all_iterator(self):
        t = Tibs("0b101101")
        assert list(t.find_all_iter("0b101")) == [0, 3]

    def test_chunks_iterator(self):
        t = Tibs("0b101101")
        chunks = list(t.chunks_iter(3))
        assert len(chunks) == 2
        assert chunks[0] == Tibs('0b101')
        assert chunks[1] == Tibs('0b101')


class TestErrorHandling:

    def test_from_i_errors(self):
        with pytest.raises(OverflowError):
            Tibs.from_i(128, 8)  # Overflows (signed)
        with pytest.raises(OverflowError):
            Tibs.from_i(-129, 8)  # Overflows (signed)

    def test_from_f_errors(self):
        with pytest.raises(ValueError):
            Tibs.from_f(1.0, 12)  # Invalid length

    def test_from_bytes_errors(self):
        with pytest.raises(ValueError):
            Tibs.from_bytes(b"abc", offset=30)
        with pytest.raises(ValueError):
            Tibs.from_bytes(b"abc", length=30)

    def test_adding_nonsense(self):
        with pytest.raises(ValueError):
            Mutibs.from_bools([0]) + "hello"

    def test_slice_assignment_errors(self):
        a = Mutibs('0b000')
        with pytest.raises(IndexError):
            a[5] = 1
        with pytest.raises(IndexError):
            del a[5]


class TestAdvancedFeatures:
    def test_rol(self):
        s = Mutibs("0b0001")
        s.rotate_left(1)
        assert s == Tibs('0b0010')

    def test_ror(self):
        s = Mutibs("0b1000")
        s.rotate_right(1)
        assert s == Tibs('0b0100')

    def test_byte_swap(self):
        s = Mutibs("0x0123456789abcdef")
        s.byte_swap()
        assert s == Tibs('0xefcdab8967452301')

    def test_to_f(self):
        f = 3.14159
        t16 = Tibs.from_f(f, 16)
        assert t16.to_f() != f  # Should be different
        t32 = Tibs.from_f(f, 32)
        assert abs(t32.to_f() - f) < 1e-6
        t64 = Tibs.from_f(f, 64)
        assert t64.to_f() == f

    def test_to_u_i(self):
        assert Tibs("0b11111111").to_u() == 255
        assert Tibs("0b11111111").to_i() == -1
        assert Tibs("0b01111111").to_i() == 127

    def test_find_all_byte_aligned(self):
        a = Tibs("0x00ff00ff")
        assert a.find_all(Tibs('0xff'), byte_aligned=True) == [8, 24]


class TestComplexInteractions:
    def test_overlapping_replace(self):
        s = Mutibs("0b101010")
        s.replace("0b10", "0b1010")
        assert s == Tibs('0b101010101010')

    def test_find_all_overlapping(self):
        s = Tibs("0b1010101")
        assert s.find_all(Tibs('0b101')) == [0, 2, 4]

    def test_chunks_with_remainder(self):
        s = Tibs.from_zeros(10)
        chunks = list(s.chunks(3))
        assert len(chunks) == 4
        assert len(chunks[0]) == 3
        assert len(chunks[1]) == 3
        assert len(chunks[2]) == 3
        assert len(chunks[3]) == 1

    def test_set_slice_with_self_overlap(self):
        m = Mutibs("0b11110000")
        m[2:6] = m[0:4]
        assert m == Tibs('0b11111100')

    def test_int_representation_edges(self):
        assert Tibs.from_u(255, 8).to_i() == -1
        assert Tibs.from_i(-128, 8).to_u() == 128
        with pytest.raises(ValueError):
            Tibs("0b" + "1" * 129).to_u()
        with pytest.raises(ValueError):
            Tibs("0b" + "1" * 129).to_i()

    def test_mixed_type_operations(self):
        m = Mutibs("0b1010")
        t = Tibs("0b0101")
        m |= t
        assert m == Tibs('0b1111')

    def test_chained_mutations(self):
        m = Mutibs("0x1234")
        m.rotate_left(4)
        m.invert()
        m.rotate_right(8)
        assert m == Tibs('0xbedc')

    def test_immutable_slice_mutation_check(self):
        t = Tibs("0x12345678")
        s = t[8:24]
        m = s.to_mutibs()
        m.invert()
        assert t == Tibs('0x12345678')
        assert s != m
        assert s == Tibs('0x3456')

    def test_from_joined_mixed_types(self):
        t = Tibs("0b11")
        m = Mutibs("0b00")
        result = Tibs.from_joined([t, m, "0xff", [1, 0, 1]])
        assert result == Tibs('0b110011111111101')

    def test_delete_and_insert(self):
        m = Mutibs("0xabcdef")
        del m[8:16]
        assert m == Tibs('0xabef')
        m.insert(8, "0b11110000")
        assert m == Tibs('0xabf0ef')

    def test_replace_with_empty_string(self):
        s = Mutibs("0b101010")
        s.replace("0b10", "")
        assert s == Tibs('0b')


class TestSliceOperations:
    def test_slice_eq(self):
        a = Tibs("0b101100111000")
        b = a[2:10]
        c = Tibs("0b11001110")
        assert b == c

    def test_slice_logical_or(self):
        a = Tibs("0b00001111")
        b = Tibs("0b10101010")
        s_a = a[0:8]
        s_b = b[0:8]
        assert s_a | s_b == Tibs('0b10101111')

    def test_slice_logical_and(self):
        a = Tibs("0b00001111")
        b = Tibs("0b10101010")
        s_a = a[0:8]
        s_b = b[0:8]
        assert s_a & s_b == Tibs('0b00001010')

    def test_slice_logical_xor(self):
        a = Tibs("0b00001111")
        b = Tibs("0b10101010")
        s_a = a[0:8]
        s_b = b[0:8]
        assert s_a ^ s_b == Tibs('0b10100101')

    def test_slice_logical_or_offset(self):
        a = Tibs("0b100001111")  # 9 bits
        b = Tibs("0b010101010")  # 9 bits
        s_a = a[1:9]  # offset = 1
        s_b = b[1:9]  # offset = 1
        assert s_a | s_b == Tibs('0b10101111')

    def test_slice_logical_and_offset(self):
        a = Tibs("0b100001111")
        b = Tibs("0b010101010")
        s_a = a[1:9]
        s_b = b[1:9]
        assert s_a & s_b == Tibs('0b00001010')

    def test_find_slice(self):
        a = Tibs("0b101100111000101100")
        b = Tibs("0b11001110")
        c = a[2:10]
        assert c == b
        assert a.find(c) == 2

    def test_find_all_slice(self):
        a = Tibs("0b110011100011001110")
        b = Tibs("0b11001110")
        c = a[0:8]
        assert c == b
        assert list(a.find_all(c)) == [0, 10]

    def test_join_slice(self):
        a = Tibs("0b101100111000")
        b = a[2:10]
        c = Tibs.from_joined([b, b])
        assert c == Tibs('0b1100111011001110')

    def test_mutibs_from_slice(self):
        a = Tibs("0b101100111000")
        b = a[2:10]
        m = Mutibs(b)
        m.invert()
        assert m != b
        assert a == Tibs('0b101100111000')

    def test_logical_op_stepped_slice(self):
        a = Tibs("0b00110011")
        b = Tibs("0b01010101")
        c = a[::2]  # 0101
        d = b[::2]  # 0000
        assert c | d == Tibs('0b0101')
        assert c & d == Tibs('0b0000')
        assert c ^ d == Tibs('0b0101')

    def test_mixed_tibs_mutibs_slice_ops(self):
        a = Tibs("0b11110000")
        b = Mutibs("0b10101010")
        s_a = a[0:8]
        s_b = b[0:8]
        assert s_a & s_b == Tibs('0b10100000')

    def test_mutibs_slice_is_immutable_copy(self):
        a = Mutibs("0b00000000")
        b = a[2:6]
        b[1] = 1
        # The original is unchanged
        assert a == Tibs('0b00000000')

    def test_slice_logical_xor_offset(self):
        a = Tibs("0b100001111")
        b = Tibs("0b010101010")
        s_a = a[1:9]
        s_b = b[1:9]
        assert s_a ^ s_b == Tibs('0b10100101')


class TestKnownRegressions:
    def test_rotate_empty_selected_slice_is_noop(self):
        m = Mutibs("0b101010")
        m.rotate_left(3, start=2, end=2)
        m.rotate_right(1, start=4, end=4)
        assert m == Tibs('0b101010')

    def test_set_negative_step_range(self):
        m = Mutibs("0b00000000")
        m.set(range(7, 3, -1))
        assert m.to_bin() == "00001111"

    def test_unset_negative_step_range(self):
        m = Mutibs("0b11111111")
        m.unset(range(7, 3, -1))
        assert m.to_bin() == "11110000"


class TestDocsMismatchRegressions:
    def test_find_all_empty_needle_raises_value_error_docs_contract(self):
        t = Tibs("0b101")
        with pytest.raises(ValueError):
            print(list(t.find_all("")))

    def test_tibs_from_random_positional_seed_is_deterministic_docs_example(self):
        a = Tibs.from_random(256, seed=b"a_seed")
        b = Tibs.from_random(256, seed=b"a_seed")
        assert a == b

    def test_mutibs_from_random_positional_seed_is_deterministic_docs_example(self):
        a = Mutibs.from_random(256, seed=b"a_seed")
        b = Mutibs.from_random(256, seed=b"a_seed")
        assert a == b

    def test_mutibs_insert_out_of_range_raises_value_error_docs_contract(self):
        m = Mutibs("0b101")
        m.insert(len(m) + 1, "0b1")
        assert m == Tibs('0b1011')

    def test_tibs_to_u_empty_raises_value_error_docs_contract(self):
        with pytest.raises(ValueError):
            Tibs().to_u()

    def test_tibs_to_i_empty_raises_value_error_docs_contract(self):
        with pytest.raises(ValueError):
            Tibs().to_i()


class TestKnownLogicFaults:
    def test_set_range_descending_to_minus_one_includes_zero(self):
        m = Mutibs.from_zeros(4)
        m.set(range(3, -1, -1))
        assert m.to_bin() == "1111"

    def test_unset_range_descending_to_minus_one_includes_zero(self):
        m = Mutibs.from_ones(4)
        m.unset(range(3, -1, -1))
        assert m.to_bin() == "0000"

def _delete_expected(bits: list[bool], key) -> list[bool]:
    expected = bits.copy()
    del expected[key]
    return expected


@pytest.mark.parametrize(
    "key",
    [
        slice(None, 8),
        slice(8, None),
        slice(4, 20),
        slice(-16, -4),
        slice(10, 10),
        slice(None, None, 2),
        slice(1, None, 3),
        slice(None, None, -1),
        slice(20, 2, -3),
    ],
)
def test_del_slice_matches_python_list_semantics(key):
    # Use a non-trivial pattern so direction/indexing errors are obvious.
    m = Mutibs.from_hex("abcdef")
    before = list(m.to_tibs())

    expected = _delete_expected(before, key)
    del m[key]

    assert list(m.to_tibs()) == expected


def _set_expected(bits: list[bool], key, value: list[bool]) -> list[bool]:
    expected = bits.copy()
    expected[key] = value
    return expected


@pytest.mark.parametrize(
    "key,value",
    [
        (slice(None, 8), [True, False, True, False]),
        (slice(8, None), [False, True, False]),
        (slice(4, 20), [True] * 5),
        (slice(-16, -4), [False, False, True, True]),
        (slice(10, 10), [True, False, True]),
    ],
)
def test_set_slice_matches_python_list_semantics(key, value):
    m = Mutibs.from_hex("abcdef")
    before = list(m.to_tibs())
    replacement = Tibs.from_bools(value)

    expected = _set_expected(before, key, list(replacement))
    m[key] = replacement

    assert list(m.to_tibs()) == expected


@pytest.mark.parametrize("key", [slice(None, None, 2), slice(1, None, 3), slice(20, 2, -3)])
def test_set_extended_slice_matches_python_list_semantics(key):
    m = Mutibs.from_hex("abcdef")
    before = list(m.to_tibs())

    target_len = len(before[key])
    value = [(i % 2) == 0 for i in range(target_len)]
    replacement = Tibs.from_bools(value)

    expected = _set_expected(before, key, list(replacement))
    m[key] = replacement

    assert list(m.to_tibs()) == expected


def test_set_extended_slice_length_mismatch_raises():
    m = Mutibs.from_hex("abcdef")
    with pytest.raises(ValueError):
        m[::2] = [1, 0]


def test_del_full_and_single_index_regression():
    m = Mutibs.from_hex("1234")
    before = list(m.to_tibs())

    m2 = Mutibs.from_hex("1234")
    del m2[:]
    assert list(m2.to_tibs()) == []

    del m[0]
    expected = before.copy()
    del expected[0]
    assert list(m.to_tibs()) == expected



def _find_expected(
        bits: list[bool],
        needle: list[bool],
        start: int | None,
        end: int | None,
        reverse: bool,
        byte_aligned: bool,
) -> int | None:
    n = len(bits)
    s = 0 if start is None else start
    e = n if end is None else end
    if s < 0:
        s += n
    if e < 0:
        e += n
    m = len(needle)
    if m == 0 or m > (e - s):
        return None

    if reverse:
        i_range = range(e - m, s - 1, -1)
    else:
        i_range = range(s, e - m + 1)

    for i in i_range:
        if byte_aligned and (i % 8 != 0):
            continue
        if bits[i:i + m] == needle:
            return i
    return None


@pytest.mark.parametrize(
    "needle,start,end,byte_aligned",
    [
        ([1, 1], None, None, False),
        ([1, 0, 1], 3, 28, False),
        ([0, 1], -20, -1, False),
        ([1, 1, 0], 0, 32, True),
        ([0, 0, 1], 5, 31, True),
    ],
)
def test_find_rfind_match_python_reference(needle, start, end, byte_aligned):
    t = Tibs("0xd35a1c9e")
    bits = list(t)
    needle_bits = [bool(x) for x in needle]

    expected_find = _find_expected(bits, needle_bits, start, end, False, byte_aligned)
    expected_rfind = _find_expected(bits, needle_bits, start, end, True, byte_aligned)

    assert t.find(needle, start=start, end=end, byte_aligned=byte_aligned) == expected_find
    assert t.rfind(needle, start=start, end=end, byte_aligned=byte_aligned) == expected_rfind


def _rotate_left_expected(bits: list[bool], start: int, end: int, n: int) -> list[bool]:
    expected = bits.copy()
    segment = expected[start:end]
    if not segment:
        return expected
    n %= len(segment)
    expected[start:end] = segment[n:] + segment[:n]
    return expected


class TestConcreteRegressionCases:
    def test_find_byte_aligned_empty_subrange_returns_none(self):
        t = Tibs("0x11223344")
        assert t.find("0x11", start=1, end=1, byte_aligned=True) is None

    def test_find_all_byte_aligned_empty_subrange_returns_empty_list(self):
        t = Tibs("0x11223344")
        assert t.find_all(Tibs('0x11'), start=1, end=1, byte_aligned=True) == []

    def test_find_all_single_bit_respects_slice_and_alignment(self):
        t = Tibs("0b1000000010000000100000001")

        assert t.find_all("0b1") == [0, 8, 16, 24]
        assert t.find_all("0b1", start=1, end=24, byte_aligned=True) == [8, 16]

        zeros = Tibs("0b011111110111111101111111")
        assert zeros.find_all("0b0", byte_aligned=True) == [0, 8, 16]

    def test_find_all_single_pass_preserves_overlapping_matches(self):
        t = Tibs("0b11111")
        assert t.find_all("0b111") == [0, 1, 2]

    def test_find_all_byte_aligned_respects_subrange(self):
        t = Tibs("0xabcdabcd")
        assert t.find_all("0xabcd", start=8, end=32, byte_aligned=True) == [16]

    def test_mutibs_find_all_uses_same_search_semantics(self):
        m = Mutibs("0b11111")
        assert m.find_all("0b111") == [0, 1, 2]
        assert m.find_all("0b1", byte_aligned=True) == [0]

    def test_rfind_reverse_search_finds_prefix_match(self):
        t = Tibs("0b0111")
        assert t.rfind("0b011") == 0

    def test_mutibs_rotate_left_partial_slice_matches_logical_rotation(self):
        m = Mutibs("0b10110010")
        expected = _rotate_left_expected(list(m.to_tibs()), 1, 7, 2)

        m.rotate_left(2, start=1, end=7)

        assert list(m.to_tibs()) == expected

    def test_tibs_rotated_left_partial_slice_matches_logical_rotation(self):
        t = Tibs("0b10110010")
        expected = _rotate_left_expected(list(t), 1, 7, 2)

        rotated = t.rotated_left(2, start=1, end=7)

        assert list(rotated) == expected


def test_decode_malformed_zstd_with_impossible_padding_raises_value_error():
    malformed = bytes.fromhex("110d28b52ffd240001000099e9d851")
    with pytest.raises(ValueError):
        Mutibs.decode(malformed)



def test_decode_malformed_zstd_with_impossible_padding_raises_value_error():
    # Valid zstd frame for empty bytes, wrapped in the tibs zstd container
    # with an impossible bit_padding of 1 for zero decompressed bits.
    malformed = bytes.fromhex("110d28b52ffd240001000099e9d851")
    with pytest.raises(ValueError):
        Tibs.decode(malformed)


def test_bit_ops_unequal_offsets():
    a = Tibs('0b11001010_01110100_10111100')
    b = Tibs('0b10110101_11001001_00101110')
    left = a[3:21]
    right = b[5:23]

    expected_or = [x or y for x, y in zip(left, right)]
    expected_and = [x and y for x, y in zip(left, right)]
    expected_xor = [x != y for x, y in zip(left, right)]

    assert list(left | right) == expected_or
    assert list(left & right) == expected_and
    assert list(left ^ right) == expected_xor
