"""Tests for edge cases and error handling in provider."""

import pytest

from chuk_mcp_solver.models import SolveConstraintModelRequest, SolverStatus
from chuk_mcp_solver.solver.ortools import ORToolsSolver as ORToolsProvider


@pytest.mark.asyncio
async def test_satisfy_mode_with_objective_warns():
    """Test that satisfy mode with objective still works (objective ignored)."""
    request = SolveConstraintModelRequest(
        mode="satisfy",
        variables=[{"id": "x", "domain": {"type": "integer", "lower": 0, "upper": 5}}],
        constraints=[],
        objective={"sense": "min", "terms": [{"var": "x", "coef": 1}]},
    )

    provider = ORToolsProvider()
    # Should not raise, just warn (we test it doesn't crash)
    response = await provider.solve_constraint_model(request)
    assert response.status == SolverStatus.SATISFIED


@pytest.mark.asyncio
async def test_unsupported_constraint_kind():
    """Test that unsupported constraint kind is handled."""
    # We can't easily create an invalid constraint through Pydantic,
    # but we can test the error path by checking the enum covers all cases
    from chuk_mcp_solver.models import ConstraintKind

    # Verify all constraint kinds are handled
    kinds = [
        ConstraintKind.LINEAR,
        ConstraintKind.ALL_DIFFERENT,
        ConstraintKind.ELEMENT,
        ConstraintKind.TABLE,
        ConstraintKind.IMPLICATION,
    ]
    assert len(kinds) == 5  # If we add more, we need to update the provider


@pytest.mark.asyncio
async def test_implication_with_non_linear_constraint_fails():
    """Test that implication with non-linear nested constraint fails."""
    request = SolveConstraintModelRequest(
        mode="satisfy",
        variables=[
            {"id": "x", "domain": {"type": "bool"}},
            {"id": "y", "domain": {"type": "integer", "lower": 0, "upper": 10}},
            {"id": "z", "domain": {"type": "integer", "lower": 0, "upper": 10}},
        ],
        constraints=[
            {
                "id": "impl",
                "kind": "implication",
                "params": {
                    "if_var": "x",
                    "then": {
                        "id": "nested_all_diff",
                        "kind": "all_different",
                        "params": {"vars": ["y", "z"]},
                    },
                },
            }
        ],
    )

    provider = ORToolsProvider()
    response = await provider.solve_constraint_model(request)

    # Should return error status
    assert response.status == SolverStatus.ERROR
    assert "implication only supports linear" in response.explanation.summary.lower()


@pytest.mark.asyncio
async def test_large_domain_variables():
    """Test handling of variables with large domains."""
    request = SolveConstraintModelRequest(
        mode="optimize",
        variables=[{"id": "x", "domain": {"type": "integer", "lower": 0, "upper": 1000000}}],
        constraints=[],
        objective={"sense": "max", "terms": [{"var": "x", "coef": 1}]},
    )

    provider = ORToolsProvider()
    response = await provider.solve_constraint_model(request)

    assert response.status == SolverStatus.OPTIMAL
    assert response.solutions[0].variables[0].value == 1000000


@pytest.mark.asyncio
async def test_zero_coefficient_in_constraint():
    """Test constraint with zero coefficient."""
    request = SolveConstraintModelRequest(
        mode="satisfy",
        variables=[
            {"id": "x", "domain": {"type": "integer", "lower": 0, "upper": 10}},
            {"id": "y", "domain": {"type": "integer", "lower": 0, "upper": 10}},
        ],
        constraints=[
            {
                "id": "c1",
                "kind": "linear",
                "params": {
                    "terms": [{"var": "x", "coef": 0}, {"var": "y", "coef": 1}],
                    "sense": ">=",
                    "rhs": 5,
                },
            }
        ],
    )

    provider = ORToolsProvider()
    response = await provider.solve_constraint_model(request)

    assert response.status == SolverStatus.SATISFIED
    # y should be >= 5
    values = {var.id: var.value for var in response.solutions[0].variables}
    assert values["y"] >= 5


@pytest.mark.asyncio
async def test_negative_coefficients():
    """Test constraints with negative coefficients."""
    request = SolveConstraintModelRequest(
        mode="optimize",
        variables=[
            {"id": "x", "domain": {"type": "integer", "lower": 0, "upper": 10}},
            {"id": "y", "domain": {"type": "integer", "lower": 0, "upper": 10}},
        ],
        constraints=[
            {
                "id": "c1",
                "kind": "linear",
                "params": {
                    "terms": [{"var": "x", "coef": 1}, {"var": "y", "coef": -1}],
                    "sense": ">=",
                    "rhs": 0,
                },
            }
        ],
        objective={"sense": "max", "terms": [{"var": "x", "coef": 1}, {"var": "y", "coef": 1}]},
    )

    provider = ORToolsProvider()
    response = await provider.solve_constraint_model(request)

    assert response.status == SolverStatus.OPTIMAL
    # x >= y, so maximize x + y means x=10, y=10 but constraint requires x >= y


@pytest.mark.asyncio
async def test_equality_constraint():
    """Test equality constraint explicitly."""
    request = SolveConstraintModelRequest(
        mode="satisfy",
        variables=[
            {"id": "x", "domain": {"type": "integer", "lower": 0, "upper": 10}},
            {"id": "y", "domain": {"type": "integer", "lower": 0, "upper": 10}},
        ],
        constraints=[
            {
                "id": "equal",
                "kind": "linear",
                "params": {
                    "terms": [{"var": "x", "coef": 1}, {"var": "y", "coef": -1}],
                    "sense": "==",
                    "rhs": 0,
                },
            },
            {
                "id": "fix_x",
                "kind": "linear",
                "params": {"terms": [{"var": "x", "coef": 1}], "sense": "==", "rhs": 7},
            },
        ],
    )

    provider = ORToolsProvider()
    response = await provider.solve_constraint_model(request)

    assert response.status == SolverStatus.SATISFIED
    values = {var.id: var.value for var in response.solutions[0].variables}
    assert values["x"] == values["y"] == 7


@pytest.mark.asyncio
async def test_empty_constraint_list():
    """Test model with no constraints."""
    request = SolveConstraintModelRequest(
        mode="satisfy",
        variables=[{"id": "x", "domain": {"type": "integer", "lower": 0, "upper": 10}}],
        constraints=[],
    )

    provider = ORToolsProvider()
    response = await provider.solve_constraint_model(request)

    assert response.status == SolverStatus.SATISFIED


@pytest.mark.asyncio
async def test_fractional_rhs_rounds_to_int():
    """Test that fractional RHS values are converted to int."""
    request = SolveConstraintModelRequest(
        mode="satisfy",
        variables=[{"id": "x", "domain": {"type": "integer", "lower": 0, "upper": 10}}],
        constraints=[
            {
                "id": "c1",
                "kind": "linear",
                "params": {"terms": [{"var": "x", "coef": 1}], "sense": "<=", "rhs": 5.9},
            }
        ],
    )

    provider = ORToolsProvider()
    response = await provider.solve_constraint_model(request)

    # rhs 5.9 should round to 5
    assert response.status == SolverStatus.SATISFIED
    assert response.solutions[0].variables[0].value <= 5
