import pytest
import numpy as np

from radiate import FloatCodec, Float32


@pytest.mark.unit
def test_float_codec_vector_creation():
    """Test creating a float codec for vectors."""
    codec = FloatCodec(shape=4, init_range=(-1.0, 1.0))
    genotype = codec.encode()

    assert len(genotype) == 1
    assert len(genotype[0]) == 4
    assert all(-1.0 <= gene.allele() <= 1.0 for gene in genotype[0])

    codec = FloatCodec(shape=10, init_range=(-5.0, 5.0))
    genotype = codec.encode()

    assert len(genotype) == 1
    assert len(genotype[0]) == 10
    assert all(-5.0 <= gene.allele() <= 5.0 for gene in genotype[0])


@pytest.mark.unit
def test_float_codec_matrix_creation():
    """Test creating a float codec for matrices."""
    codec = FloatCodec((2, 3), init_range=(-10.0, 10.0))
    genotype = codec.encode()

    assert len(genotype) == 2
    assert all(len(row) == 3 for row in genotype)
    assert all(-10.0 <= gene.allele() <= 10.0 for row in genotype for gene in row)

    codec = FloatCodec(shape=(4, 5), init_range=(-2.0, 2.0))
    genotype = codec.encode()

    assert len(genotype) == 4
    assert all(len(row) == 5 for row in genotype)
    assert all(-2.0 <= gene.allele() <= 2.0 for row in genotype for gene in row)


@pytest.mark.unit
def test_float_codec_decode():
    """Test decoding float genotypes."""
    codec = FloatCodec(shape=3, init_range=(0.0, 1.0), use_numpy=True)
    genotype = codec.encode()
    decoded = codec.decode(genotype)

    assert len(decoded) == 3
    assert all(isinstance(x, np.float64) for x in decoded)
    assert all(0.0 <= x <= 1.0 for x in decoded)

    codec = FloatCodec(shape=3, init_range=(-1.0, 1.0), use_numpy=True)
    genotype = codec.encode()
    decoded = codec.decode(genotype)

    assert len(decoded) == 3
    assert all(isinstance(x, np.float64) for x in decoded)
    assert all(-1.0 <= x <= 1.0 for x in decoded)


@pytest.mark.unit
def test_float_codec_with_numpy():
    """Test float codec with numpy arrays."""
    codec = FloatCodec(shape=3, init_range=(-1.0, 1.0), use_numpy=True)
    genotype = codec.encode()
    decoded = codec.decode(genotype)

    assert isinstance(decoded, np.ndarray)
    assert decoded.shape == (3,)
    assert all(-1.0 <= x <= 1.0 for x in decoded)

    codec = FloatCodec(shape=10, init_range=(-5.0, 5.0), use_numpy=True)
    genotype = codec.encode()
    decoded = codec.decode(genotype)

    assert isinstance(decoded, np.ndarray)
    assert decoded.shape == (10,)
    assert all(-5.0 <= x <= 5.0 for x in decoded)

    codec = FloatCodec(
        shape=[5, 5, 5, 5], init_range=(-10.0, 10.0), use_numpy=True, dtype=Float32
    )

    genotype = codec.encode()
    decoded = codec.decode(genotype)

    assert isinstance(decoded, list)
    assert len(decoded) == 4

    for row in decoded:
        assert isinstance(row, np.ndarray)
        assert row.shape == (5,)
        assert all(-10.0 <= x <= 10.0 for x in row)
        assert row.dtype == np.float32  # type: ignore


@pytest.mark.unit
def test_float_codec_matrix_invalid_shape():
    """Test FloatCodec matrix with invalid shape."""
    with pytest.raises(ValueError, match="Shape must be a tuple of \\(rows, cols\\)"):
        FloatCodec(shape=(2,))


@pytest.mark.unit
def test_float_codec_matrix_invalid_value_range_order():
    """Test FloatCodec matrix with invalid value range order"""
    with pytest.raises(
        ValueError, match="Minimum value must be less than maximum value"
    ):
        FloatCodec(shape=(2, 3), init_range=(10.0, 5.0))
    with pytest.raises(
        ValueError, match="Minimum value must be less than maximum value"
    ):
        FloatCodec(shape=(2, 3), init_range=(10.0, 5.0))


@pytest.mark.unit
def test_float_codec_matrix_invalid_bound_range_order():
    """Test FloatCodec matrix with invalid bound range order."""
    with pytest.raises(
        ValueError, match="Minimum bound must be less than maximum bound"
    ):
        FloatCodec(shape=(2, 3), bounds=(10.0, 5.0))
    with pytest.raises(
        ValueError, match="Minimum bound must be less than maximum bound"
    ):
        FloatCodec(shape=(2, 3), bounds=(10.0, 5.0))


@pytest.mark.unit
def test_float_codec_vector_invalid_length():
    """Test FloatCodec vector with invalid length."""
    with pytest.raises(ValueError, match="Length must be a positive integer"):
        FloatCodec(shape=0)
    with pytest.raises(ValueError, match="Length must be a positive integer"):
        FloatCodec(-5)


@pytest.mark.unit
def test_negative_length_codec():
    """Test codecs handle negative length gracefully."""
    with pytest.raises(ValueError):
        FloatCodec(shape=-1, init_range=(-1.0, 1.0))
