"""
Parametrized tests for binary operators across different data types.

Tests scalar and array operations with various operator combinations,
using pytest parametrization for comprehensive coverage.
"""

import pytest

from aaiclick import create_object_from_value

THRESHOLD = 1e-5


# =============================================================================
# Helper Function for Operator Application
# =============================================================================


def apply_operator(a, b, operator: str):
    """Apply a binary operator to two objects, returning a LazyOperator."""
    match operator:
        case "+":
            return a + b
        case "-":
            return a - b
        case "*":
            return a * b
        case "/":
            return a / b
        case "//":
            return a // b
        case "%":
            return a % b
        case "**":
            return a**b
        case _:
            raise ValueError(f"Unsupported operator: {operator}")


# =============================================================================
# Integer Scalar Tests
# =============================================================================


@pytest.mark.parametrize(
    "data_a,data_b,operator,expected_result",
    [
        # Addition tests
        pytest.param(100, 50, "+", 150, id="int-add-basic"),
        pytest.param(0, 0, "+", 0, id="int-add-zeros"),
        pytest.param(-10, 5, "+", -5, id="int-add-negative"),
        pytest.param(1000, 2000, "+", 3000, id="int-add-large"),
        # Subtraction tests
        pytest.param(100, 30, "-", 70, id="int-sub-basic"),
        pytest.param(0, 0, "-", 0, id="int-sub-zeros"),
        pytest.param(50, 100, "-", -50, id="int-sub-negative-result"),
        pytest.param(1000, 1, "-", 999, id="int-sub-large"),
    ],
)
async def test_int_scalar_operators(ctx, data_a, data_b, operator, expected_result):
    """Test binary operators on integer scalars with various inputs."""
    obj_a = await create_object_from_value(data_a, aai_id=True)
    obj_b = await create_object_from_value(data_b, aai_id=True)

    result = apply_operator(obj_a, obj_b, operator)
    result_data = await result.data()

    assert result_data == expected_result


# =============================================================================
# Integer Array Tests
# =============================================================================


@pytest.mark.parametrize(
    "data_a,data_b,operator,expected_result",
    [
        # Addition tests
        pytest.param([1, 2, 3], [10, 20, 30], "+", [11, 22, 33], id="int-add-basic"),
        pytest.param([0, 0, 0], [1, 2, 3], "+", [1, 2, 3], id="int-add-zeros"),
        pytest.param([-5, -10, -15], [5, 10, 15], "+", [0, 0, 0], id="int-add-canceling"),
        pytest.param([100, 200], [50, 75], "+", [150, 275], id="int-add-large"),
        # Subtraction tests
        pytest.param([100, 200, 300], [10, 20, 30], "-", [90, 180, 270], id="int-sub-basic"),
        pytest.param([10, 20, 30], [10, 20, 30], "-", [0, 0, 0], id="int-sub-zeros"),
        pytest.param([5, 10, 15], [10, 20, 30], "-", [-5, -10, -15], id="int-sub-negative"),
        pytest.param([1000, 2000], [1, 2], "-", [999, 1998], id="int-sub-large"),
    ],
)
async def test_int_array_operators(ctx, data_a, data_b, operator, expected_result):
    """Test binary operators on integer arrays with various inputs."""
    obj_a = await create_object_from_value(data_a, aai_id=True)
    obj_b = await create_object_from_value(data_b, aai_id=True)

    result = apply_operator(obj_a, obj_b, operator)
    result_data = await result.data()

    assert result_data == expected_result


# =============================================================================
# Float Scalar Tests
# =============================================================================


@pytest.mark.parametrize(
    "data_a,data_b,operator,expected_result",
    [
        # Addition tests
        pytest.param(100.5, 50.25, "+", 150.75, id="float-add-basic"),
        pytest.param(0.0, 0.0, "+", 0.0, id="float-add-zeros"),
        pytest.param(-10.5, 5.25, "+", -5.25, id="float-add-negative"),
        pytest.param(3.14159, 2.71828, "+", 5.85987, id="float-add-pi-e"),
        # Subtraction tests
        pytest.param(100.5, 30.25, "-", 70.25, id="float-sub-basic"),
        pytest.param(0.0, 0.0, "-", 0.0, id="float-sub-zeros"),
        pytest.param(50.5, 100.5, "-", -50.0, id="float-sub-negative-result"),
        pytest.param(10.0, 0.1, "-", 9.9, id="float-sub-small"),
    ],
)
async def test_float_scalar_operators(ctx, data_a, data_b, operator, expected_result):
    """Test binary operators on float scalars with various inputs."""
    obj_a = await create_object_from_value(data_a, aai_id=True)
    obj_b = await create_object_from_value(data_b, aai_id=True)

    result = apply_operator(obj_a, obj_b, operator)
    result_data = await result.data()

    assert abs(result_data - expected_result) < THRESHOLD


# =============================================================================
# Float Array Tests
# =============================================================================


@pytest.mark.parametrize(
    "data_a,data_b,operator,expected_result",
    [
        # Addition tests
        pytest.param([10.0, 20.0, 30.0], [5.0, 10.0, 15.0], "+", [15.0, 30.0, 45.0], id="float-add-basic"),
        pytest.param([0.0, 0.0], [1.5, 2.5], "+", [1.5, 2.5], id="float-add-zeros"),
        pytest.param([-5.5, -10.5], [5.5, 10.5], "+", [0.0, 0.0], id="float-add-canceling"),
        pytest.param([1.1, 2.2, 3.3], [0.1, 0.2, 0.3], "+", [1.2, 2.4, 3.6], id="float-add-decimals"),
        # Subtraction tests
        pytest.param([100.5, 200.5, 300.5], [10.5, 20.5, 30.5], "-", [90.0, 180.0, 270.0], id="float-sub-basic"),
        pytest.param([10.0, 20.0], [10.0, 20.0], "-", [0.0, 0.0], id="float-sub-zeros"),
        pytest.param([5.5, 10.5], [10.5, 20.5], "-", [-5.0, -10.0], id="float-sub-negative"),
        pytest.param([100.0, 200.0], [0.1, 0.2], "-", [99.9, 199.8], id="float-sub-small"),
    ],
)
async def test_float_array_operators(ctx, data_a, data_b, operator, expected_result):
    """Test binary operators on float arrays with various inputs."""
    obj_a = await create_object_from_value(data_a, aai_id=True)
    obj_b = await create_object_from_value(data_b, aai_id=True)

    result = apply_operator(obj_a, obj_b, operator)
    result_data = await result.data()

    for i, val in enumerate(result_data):
        assert abs(val - expected_result[i]) < THRESHOLD


# =============================================================================
# Edge Cases
# =============================================================================


@pytest.mark.parametrize(
    "data_a,data_b,operator,expected_result",
    [
        # Single element arrays
        pytest.param([42], [8], "+", [50], id="int-add-single"),
        pytest.param([100], [30], "-", [70], id="int-sub-single"),
        # Large numbers
        pytest.param([1000000], [2000000], "+", [3000000], id="int-add-large"),
        pytest.param([999999999], [1], "+", [1000000000], id="int-add-very-large"),
        # Very small floats
        pytest.param([0.001, 0.002], [0.003, 0.004], "+", [0.004, 0.006], id="float-add-small"),
        # Negative numbers
        pytest.param([-100, -200], [-50, -75], "+", [-150, -275], id="int-add-negative"),
        pytest.param([-10, -20], [-5, -10], "-", [-5, -10], id="int-sub-negative"),
    ],
)
async def test_edge_case_operators(ctx, data_a, data_b, operator, expected_result):
    """Test binary operators with edge cases."""
    obj_a = await create_object_from_value(data_a, aai_id=True)
    obj_b = await create_object_from_value(data_b, aai_id=True)

    result = apply_operator(obj_a, obj_b, operator)
    result_data = await result.data()

    # Handle both scalar and array results
    if isinstance(expected_result, list):
        for i, val in enumerate(result_data):
            assert abs(val - expected_result[i]) < THRESHOLD
    else:
        assert abs(result_data - expected_result) < THRESHOLD


# =============================================================================
# Chained Operations
# =============================================================================


@pytest.mark.parametrize(
    "data_a,data_b,data_c,op1,op2,expected_result",
    [
        # (a + b) - c
        pytest.param([10, 20, 30], [5, 10, 15], [3, 6, 9], "+", "-", [12, 24, 36], id="int-add-sub"),
        # (a - b) + c
        pytest.param([100, 200], [30, 60], [5, 10], "-", "+", [75, 150], id="int-sub-add"),
        # (a + b) + c
        pytest.param([1, 2], [3, 4], [5, 6], "+", "+", [9, 12], id="int-add-add"),
        # (a - b) - c
        pytest.param([100, 200], [10, 20], [5, 10], "-", "-", [85, 170], id="int-sub-sub"),
    ],
)
async def test_chained_operators(ctx, data_a, data_b, data_c, op1, op2, expected_result):
    """Test chained binary operations."""
    obj_a = await create_object_from_value(data_a, aai_id=True)
    obj_b = await create_object_from_value(data_b, aai_id=True)
    obj_c = await create_object_from_value(data_c, aai_id=True)

    temp = apply_operator(obj_a, obj_b, op1)
    result = apply_operator(temp, obj_c, op2)
    result_data = await result.data()

    for i, val in enumerate(result_data):
        assert abs(val - expected_result[i]) < THRESHOLD


# =============================================================================
# Multiplication, division, floor division, modulo, power
# =============================================================================


@pytest.mark.parametrize(
    "data_a,data_b,operator,expected_result",
    [
        pytest.param(6, 7, "*", 42, id="int-scalar-mul"),
        pytest.param(10.0, 2.5, "*", 25.0, id="float-scalar-mul"),
        pytest.param(10.0, 4.0, "/", 2.5, id="float-scalar-div"),
        pytest.param(10, 3, "//", 3, id="int-scalar-floordiv"),
        pytest.param(10, 3, "%", 1, id="int-scalar-mod"),
        pytest.param(2.0, 10.0, "**", 1024.0, id="float-scalar-pow"),
    ],
)
async def test_mul_div_scalar(ctx, data_a, data_b, operator, expected_result):
    """Test multiplication, division, floordiv, mod, pow on scalars."""
    obj_a = await create_object_from_value(data_a, aai_id=True)
    obj_b = await create_object_from_value(data_b, aai_id=True)
    result = apply_operator(obj_a, obj_b, operator)
    result_data = await result.data()
    assert abs(result_data - expected_result) < THRESHOLD


@pytest.mark.parametrize(
    "data_a,data_b,operator,expected_result",
    [
        pytest.param([1, 2, 3], [4, 5, 6], "*", [4, 10, 18], id="int-array-mul"),
        pytest.param([10.0, 20.0], [2.0, 4.0], "/", [5.0, 5.0], id="float-array-div"),
        pytest.param([10, 23, 37], [3, 5, 7], "//", [3, 4, 5], id="int-array-floordiv"),
        pytest.param([10, 23, 37], [3, 5, 7], "%", [1, 3, 2], id="int-array-mod"),
        pytest.param([2.0, 3.0], [3.0, 2.0], "**", [8.0, 9.0], id="float-array-pow"),
    ],
)
async def test_mul_div_array(ctx, data_a, data_b, operator, expected_result):
    """Test multiplication, division, floordiv, mod, pow on arrays."""
    obj_a = await create_object_from_value(data_a, aai_id=True)
    obj_b = await create_object_from_value(data_b, aai_id=True)
    result = apply_operator(obj_a, obj_b, operator)
    result_data = await result.data()
    for i, val in enumerate(result_data):
        assert abs(val - expected_result[i]) < THRESHOLD
