"""
This module provides common fixtures and utilities used across all test modules.
"""

import pytest
import random
import radiate as rd

try:
    import numpy as np

    HAS_NUMPY = True
except ImportError:
    HAS_NUMPY = False
    np = None


@pytest.fixture(scope="session")
def random_seed():
    """Set a consistent random seed for reproducible tests."""
    seed = 42
    random.seed(seed)
    if HAS_NUMPY:
        np.random.seed(seed)  # type: ignore
    rd.random.seed(seed)
    return seed


"""
Data sets
"""


@pytest.fixture
def xor_dataset():
    """Create XOR dataset for testing."""
    inputs = [[1.0, 1.0], [1.0, 0.0], [0.0, 1.0], [0.0, 0.0]]
    outputs = [[0.0], [1.0], [1.0], [0.0]]
    return inputs, outputs


@pytest.fixture
def simple_regression_dataset():
    """Create a simple regression dataset for testing."""
    inputs = [[0.0], [1.0], [2.0], [3.0], [4.0]]
    outputs = [[0.0], [2.0], [4.0], [6.0], [8.0]]
    return inputs, outputs


@pytest.fixture
def complex_regression_dataset():
    """Create a more complex regression dataset for testing."""
    inputs = [[i] for i in range(-10, 11)]
    outputs = [[3 * i**2 + 2 * i + 1] for i in range(-10, 11)]
    return inputs, outputs


@pytest.fixture
def memory_dataset():
    """Create a dataset for testing memory-based fitness functions."""
    inputs = [
        [0.0],
        [0.0],
        [0.0],
        [1.0],
        [0.0],
        [0.0],
        [0.0],
    ]
    outputs = [
        [0.0],
        [0.0],
        [1.0],
        [0.0],
        [0.0],
        [0.0],
        [1.0],
    ]

    return inputs, outputs


@pytest.fixture
def example_1x1_regression_dataset():
    def compute(x: float) -> float:
        return 4.0 * x**3 - 3.0 * x**2 + x

    inputs = []
    answers = []

    x = -1.0
    for _ in range(20):
        x += 0.1
        inputs.append([x])
        answers.append([compute(x)])

    return inputs, answers


"""
Graph Fixtures
"""


@pytest.fixture
def graph_simple_1x1():
    """Create a simple graph codec for testing."""
    codec = rd.GraphCodec.directed(
        shape=(1, 1),
        vertex=[rd.Op.add(), rd.Op.mul(), rd.Op.linear()],
        edge=rd.Op.weight(),
        output=rd.Op.linear(),
    )

    return codec.decode(codec.encode())


@pytest.fixture
def graph_codec_simple_2x1():
    """Create a simple graph codec for testing."""
    return rd.GraphCodec.directed(
        shape=(2, 1),
        vertex=[rd.Op.add(), rd.Op.mul(), rd.Op.linear()],
        edge=rd.Op.weight(),
        output=rd.Op.linear(),
    )


@pytest.fixture
def graph_simple_2x1():
    """Create a simple graph structure for testing."""
    codec = rd.GraphCodec.directed(
        shape=(2, 1),
        vertex=[rd.Op.add(), rd.Op.mul(), rd.Op.linear()],
        edge=rd.Op.weight(),
        output=rd.Op.linear(),
    )
    return codec.decode(codec.encode())


"""
Tree Fixtures
"""


@pytest.fixture
def tree_codec_simple_2x1():
    """Create a simple tree codec for testing."""
    return rd.TreeCodec(
        shape=(2, 1),
        vertex=[rd.Op.add(), rd.Op.mul(), rd.Op.sub()],
        leaf=[rd.Op.var(0), rd.Op.var(1)],
        root=rd.Op.linear(),
    )


@pytest.fixture
def tree_simple_2x1():
    """Create a simple tree structure for testing."""
    codec = rd.TreeCodec(
        shape=(2, 1),
        vertex=[rd.Op.add(), rd.Op.mul(), rd.Op.sub()],
        leaf=[rd.Op.var(0), rd.Op.var(1)],
        root=rd.Op.linear(),
    )
    return codec.decode(codec.encode())


"""
Engine Fixtures
"""


@pytest.fixture
def graph_1x1_engine():
    """Create a simple 1x1 graph engine for testing."""
    return rd.Engine.graph(
        shape=(1, 1),
        vertex=[rd.Op.add(), rd.Op.mul(), rd.Op.linear()],
        edge=rd.Op.weight(),
        output=rd.Op.linear(),
    ).alters(
        rd.Cross.graph(0.05, 0.5),
        rd.Mutate.op(0.07, 0.05),
        rd.Mutate.graph(0.1, 0.1, False),
    )


@pytest.fixture
def simple_float_engine():
    """Create a simple float codec engine for testing."""
    return (
        rd.Engine.float(10, init_range=(-1.0, 1.0))
        .fitness(lambda x: sum(xi**2 for xi in x))
        .minimizing()
        .size(100)
        .alters(rd.Cross.uniform(0.5), rd.Mutate.arithmetic(0.1))
    )


@pytest.fixture
def simple_multi_objective_engine():
    """Create a simple multi-objective float codec engine for testing."""
    return (
        rd.Engine.float(10, init_range=(-1.0, 1.0))
        .fitness(
            lambda x: [
                sum(xi**2 for xi in x),
                sum((xi - 0.5) ** 2 for xi in x),
            ]
        )
        .objective(rd.MIN, rd.MIN)
        .size(100)
        .select(rd.Select.tournament(3), rd.Select.nsga2())
        .alters(rd.Cross.uniform(0.5), rd.Mutate.arithmetic(0.1))
    )


@pytest.fixture
def simple_bit_20_bit_engine():
    """Create a simple bit codec engine for testing."""
    return (
        rd.Engine.bit(20)
        .size(150)
        .alters(rd.Cross.uniform(0.5), rd.Mutate.uniform(0.1))
    )


"""
Performance Benchmarking Utilities
"""


class PerformanceBenchmark:
    """Utility class for performance testing."""

    @staticmethod
    def time_function(func, *args, **kwargs):
        """Time a function execution."""
        import time

        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        return result, end - start

    @staticmethod
    def memory_usage():
        """Get current memory usage (if psutil is available)."""
        try:
            import psutil  # type: ignore

            process = psutil.Process()
            return process.memory_info().rss / 1024 / 1024  # MB
        except ImportError:
            return None


@pytest.fixture
def performance_benchmark():
    """Provide performance benchmarking utilities."""
    return PerformanceBenchmark()


# Test markers for different test types
def pytest_configure(config):
    """Configure custom pytest markers."""
    config.addinivalue_line("markers", "unit: mark test as a unit test")
    config.addinivalue_line("markers", "integration: mark test as an integration test")
    config.addinivalue_line("markers", "performance: mark test as a performance test")
    config.addinivalue_line("markers", "slow: mark test as slow running")
    config.addinivalue_line("markers", "regression: mark test as a regression test")
    config.addinivalue_line("markers", "smoke: mark test as a smoke test")
    config.addinivalue_line("markers", "skipif: mark test to skip if condition is true")
    config.addinivalue_line("markers", "bench: mark test as a benchmark test")
