use core::num::NonZeroUsize;

use qubit_codec::Codec;
use qubit_codec_binary::{
    Leb128DecodeErrorKind,
    NonStrict,
    Strict,
    ZigZagCodec,
};

use super::assertions_tests::assert_decoded_eq;

fn nonzero(value: usize) -> NonZeroUsize {
    NonZeroUsize::new(value).expect("test count must be non-zero")
}

/// Checks the exact ZigZag LEB128 bytes for an `i16` value.
fn assert_i16_zig_zag_bytes(value: i16, expected: &[u8]) {
    let mut output = [0u8; ZigZagCodec::<i16, NonStrict>::MAX_UNITS_PER_VALUE];

    let len = unsafe {
        ZigZagCodec::<i16, NonStrict>::encode_unchecked(value, &mut output, 0)
    };
    assert_eq!(expected.len(), len);
    assert_eq!(expected, &output[..len]);

    let decoded =
        unsafe { ZigZagCodec::<i16, Strict>::decode_unchecked(&output, 0) }
            .expect("canonical ZigZag boundary value should decode");
    assert_decoded_eq((value, len), decoded);
}

#[test]
fn test_zig_zag_codec_exposes_unit_bounds() {
    assert_eq!(1, ZigZagCodec::<i8, NonStrict>::MIN_UNITS_PER_VALUE);
    assert_eq!(2, ZigZagCodec::<i8, NonStrict>::MAX_UNITS_PER_VALUE);
    assert_eq!(3, ZigZagCodec::<i16, NonStrict>::MAX_UNITS_PER_VALUE);
    assert_eq!(5, ZigZagCodec::<i32, NonStrict>::MAX_UNITS_PER_VALUE);
    assert_eq!(10, ZigZagCodec::<i64, NonStrict>::MAX_UNITS_PER_VALUE);
    assert_eq!(19, ZigZagCodec::<i128, NonStrict>::MAX_UNITS_PER_VALUE);
    assert_eq!(
        (isize::BITS as usize).div_ceil(7),
        ZigZagCodec::<isize, Strict>::MAX_UNITS_PER_VALUE
    );
}

#[test]
fn test_zig_zag_codec_encodes_7_bit_boundaries() {
    let cases: &[(i16, &[u8])] = &[
        (0, &[0x00]),
        (-1, &[0x01]),
        (1, &[0x02]),
        (63, &[0x7e]),
        (-64, &[0x7f]),
        (64, &[0x80, 0x01]),
        (-65, &[0x81, 0x01]),
        (i16::MAX, &[0xfe, 0xff, 0x03]),
        (i16::MIN, &[0xff, 0xff, 0x03]),
    ];

    for &(value, expected) in cases {
        assert_i16_zig_zag_bytes(value, expected);
    }
}

#[test]
fn test_zig_zag_codec_reads_and_writes_values_unchecked() {
    let mut output =
        [0u8; ZigZagCodec::<i16, NonStrict>::MAX_UNITS_PER_VALUE + 2];
    let len = unsafe {
        ZigZagCodec::<i16, NonStrict>::encode_unchecked(-300, &mut output, 1)
    };

    assert_eq!(2, len);
    assert_eq!([0x00, 0xd7, 0x04, 0x00, 0x00], output);

    let decoded =
        unsafe { ZigZagCodec::<i16, NonStrict>::decode_unchecked(&output, 1) }
            .expect("valid i16 should decode");
    assert_decoded_eq((-300, 2), decoded);
}

#[test]
fn test_zig_zag_codec_encodes_and_decodes_through_codec_trait() {
    let codec = ZigZagCodec::<i16, NonStrict>::default();
    let mut output =
        [0u8; ZigZagCodec::<i16, NonStrict>::MAX_UNITS_PER_VALUE + 2];

    assert_eq!(
        ZigZagCodec::<i16, NonStrict>::MIN_UNITS_PER_VALUE,
        codec.min_units_per_value().get()
    );
    assert_eq!(
        ZigZagCodec::<i16, NonStrict>::MAX_UNITS_PER_VALUE,
        codec.max_units_per_value().get()
    );

    let written =
        unsafe { Codec::encode_unchecked(&codec, &-300, &mut output, 1) }
            .expect("ZigZag encoding should be infallible");
    assert_eq!(2, written);
    assert_eq!([0x00, 0xd7, 0x04, 0x00, 0x00], output);

    let decoded = unsafe { Codec::decode_unchecked(&codec, &output, 1) }
        .expect("valid ZigZag value should decode");
    assert_decoded_eq((-300, 2), decoded);
}

#[test]
fn test_zig_zag_codec_trait_decodes_single_byte_value() {
    let codec = ZigZagCodec::<i64, NonStrict>::default();
    let input = [0x01u8];

    let decoded = unsafe { Codec::decode_unchecked(&codec, &input, 0) }
        .expect("single-byte ZigZag value should decode");

    assert_decoded_eq((-1, 1), decoded);
}

#[test]
fn test_zig_zag_codec_handles_signed_extremes() {
    let mut output = [0u8; ZigZagCodec::<i128, NonStrict>::MAX_UNITS_PER_VALUE];
    let len = unsafe {
        ZigZagCodec::<i128, NonStrict>::encode_unchecked(
            i128::MIN,
            &mut output,
            0,
        )
    };

    let decoded =
        unsafe { ZigZagCodec::<i128, NonStrict>::decode_unchecked(&output, 0) }
            .expect("valid i128 should decode");
    assert_decoded_eq((i128::MIN, len), decoded);
}

#[test]
fn test_zig_zag_codec_reports_incomplete_values_unchecked() {
    let input = [0x00, 0xd7, 0x04, 0xff];

    let pending = unsafe {
        ZigZagCodec::<i16, NonStrict>::decode_unchecked(&input[..2], 1)
    }
    .expect_err("partial ZigZag value should report incomplete input");
    assert_eq!(Leb128DecodeErrorKind::Incomplete, pending.kind());
    assert_eq!(1, pending.start_index());
    assert_eq!(2, pending.error_index());
    assert_eq!(Some(nonzero(2)), pending.required());
    assert_eq!(Some(1), pending.available());
    assert_eq!(Some(nonzero(1)), pending.additional());

    let decoded =
        unsafe { ZigZagCodec::<i16, NonStrict>::decode_unchecked(&input, 1) }
            .expect("complete ZigZag value should decode");
    assert_decoded_eq((-300, 2), decoded);

    let error = unsafe {
        ZigZagCodec::<i16, Strict>::decode_unchecked(&[0x80, 0x00], 0)
    }
    .expect_err("non-canonical ZigZag value should fail");
    assert_eq!(Leb128DecodeErrorKind::NonCanonical, error.kind());
    assert_eq!(0, error.start_index());
    assert_eq!(1, error.error_index());
    assert_eq!(Some(nonzero(2)), error.consumed());
}

#[test]
fn test_zig_zag_codec_rejects_noncanonical_strict_values() {
    let error = unsafe {
        ZigZagCodec::<i16, Strict>::decode_unchecked(&[0x80, 0x00, 0x00], 0)
    }
    .expect_err("non-canonical value should fail");

    assert_eq!(Leb128DecodeErrorKind::NonCanonical, error.kind());
    assert_eq!(0, error.start_index());
    assert_eq!(1, error.error_index());
}
