"""Tests for model validation framework."""

from chuk_mcp_solver.models import (
    AllDifferentParams,
    Constraint,
    ConstraintKind,
    ElementParams,
    ImplicationParams,
    LinearConstraintParams,
    LinearTerm,
    Objective,
    ObjectiveSense,
    SolveConstraintModelRequest,
    SolverMode,
    TableParams,
    Variable,
    VariableDomain,
    VariableDomainType,
)
from chuk_mcp_solver.validation import (
    ModelValidator,
    validate_model,
)


def test_valid_model():
    """Test that a valid model passes validation."""
    request = SolveConstraintModelRequest(
        mode=SolverMode.SATISFY,
        variables=[
            Variable(
                id="x",
                domain=VariableDomain(type=VariableDomainType.INTEGER, lower=0, upper=10),
            ),
            Variable(
                id="y",
                domain=VariableDomain(type=VariableDomainType.INTEGER, lower=0, upper=10),
            ),
        ],
        constraints=[
            Constraint(
                id="c1",
                kind=ConstraintKind.LINEAR,
                params=LinearConstraintParams(
                    terms=[
                        LinearTerm(var="x", coef=1),
                        LinearTerm(var="y", coef=1),
                    ],
                    sense="<=",
                    rhs=15,
                ),
            )
        ],
    )

    result = validate_model(request)

    assert result.is_valid
    assert len(result.errors) == 0


def test_duplicate_variable_ids():
    """Test detection of duplicate variable IDs."""
    request = SolveConstraintModelRequest(
        mode=SolverMode.SATISFY,
        variables=[
            Variable(
                id="x",
                domain=VariableDomain(type=VariableDomainType.INTEGER, lower=0, upper=10),
            ),
            Variable(
                id="x",  # Duplicate
                domain=VariableDomain(type=VariableDomainType.INTEGER, lower=0, upper=10),
            ),
        ],
        constraints=[],
    )

    result = validate_model(request)

    assert not result.is_valid
    assert len(result.errors) == 1
    assert "duplicate" in result.errors[0].message.lower()
    assert "rename" in result.errors[0].suggestion.lower()


def test_invalid_variable_bounds():
    """Test detection of invalid variable bounds."""
    request = SolveConstraintModelRequest(
        mode=SolverMode.SATISFY,
        variables=[
            Variable(
                id="x",
                domain=VariableDomain(
                    type=VariableDomainType.INTEGER, lower=10, upper=5
                ),  # Invalid
            )
        ],
        constraints=[],
    )

    result = validate_model(request)

    assert not result.is_valid
    assert len(result.errors) == 1
    assert "lower bound" in result.errors[0].message.lower()
    assert "greater than upper bound" in result.errors[0].message.lower()
    assert "swap" in result.errors[0].suggestion.lower()


def test_large_domain_warning():
    """Test warning for very large variable domains."""
    request = SolveConstraintModelRequest(
        mode=SolverMode.SATISFY,
        variables=[
            Variable(
                id="x",
                domain=VariableDomain(type=VariableDomainType.INTEGER, lower=0, upper=10_000_000),
            )
        ],
        constraints=[
            # Add a constraint to avoid "no constraints" warning
            Constraint(
                id="c1",
                kind=ConstraintKind.LINEAR,
                params=LinearConstraintParams(
                    terms=[LinearTerm(var="x", coef=1)],
                    sense=">=",
                    rhs=0,
                ),
            )
        ],
    )

    result = validate_model(request)

    assert result.is_valid  # Warning, not error
    assert len(result.warnings) == 1
    assert "very large domain" in result.warnings[0].message.lower()
    assert "tighten" in result.warnings[0].suggestion.lower()


def test_undefined_variable_in_constraint():
    """Test detection of undefined variables in constraints."""
    request = SolveConstraintModelRequest(
        mode=SolverMode.SATISFY,
        variables=[
            Variable(
                id="x",
                domain=VariableDomain(type=VariableDomainType.INTEGER, lower=0, upper=10),
            )
        ],
        constraints=[
            Constraint(
                id="c1",
                kind=ConstraintKind.LINEAR,
                params=LinearConstraintParams(
                    terms=[
                        LinearTerm(var="x", coef=1),
                        LinearTerm(var="y", coef=1),  # Undefined
                    ],
                    sense="<=",
                    rhs=10,
                ),
            )
        ],
    )

    result = validate_model(request)

    assert not result.is_valid
    assert len(result.errors) == 1
    assert "not defined" in result.errors[0].message.lower()
    assert "define variable" in result.errors[0].suggestion.lower()


def test_similar_variable_suggestion():
    """Test that similar variable names are suggested."""
    request = SolveConstraintModelRequest(
        mode=SolverMode.SATISFY,
        variables=[
            Variable(
                id="x1",
                domain=VariableDomain(type=VariableDomainType.INTEGER, lower=0, upper=10),
            )
        ],
        constraints=[
            Constraint(
                id="c1",
                kind=ConstraintKind.LINEAR,
                params=LinearConstraintParams(
                    terms=[
                        LinearTerm(var="x2", coef=1),  # Typo - should be x1
                    ],
                    sense="<=",
                    rhs=10,
                ),
            )
        ],
    )

    result = validate_model(request)

    assert not result.is_valid
    assert "did you mean 'x1'" in result.errors[0].suggestion.lower()


def test_zero_coefficient_warning():
    """Test warning for zero coefficients in constraints."""
    request = SolveConstraintModelRequest(
        mode=SolverMode.SATISFY,
        variables=[
            Variable(
                id="x",
                domain=VariableDomain(type=VariableDomainType.INTEGER, lower=0, upper=10),
            ),
            Variable(
                id="y",
                domain=VariableDomain(type=VariableDomainType.INTEGER, lower=0, upper=10),
            ),
        ],
        constraints=[
            Constraint(
                id="c1",
                kind=ConstraintKind.LINEAR,
                params=LinearConstraintParams(
                    terms=[
                        LinearTerm(var="x", coef=1),
                        LinearTerm(var="y", coef=0),  # Zero coefficient
                    ],
                    sense="<=",
                    rhs=10,
                ),
            )
        ],
    )

    result = validate_model(request)

    assert result.is_valid  # Warning, not error
    assert len(result.warnings) == 1
    assert "zero coefficient" in result.warnings[0].message.lower()
    assert "remove" in result.warnings[0].suggestion.lower()


def test_duplicate_constraint_ids():
    """Test detection of duplicate constraint IDs."""
    request = SolveConstraintModelRequest(
        mode=SolverMode.SATISFY,
        variables=[
            Variable(
                id="x",
                domain=VariableDomain(type=VariableDomainType.INTEGER, lower=0, upper=10),
            )
        ],
        constraints=[
            Constraint(
                id="c1",
                kind=ConstraintKind.LINEAR,
                params=LinearConstraintParams(
                    terms=[LinearTerm(var="x", coef=1)],
                    sense="<=",
                    rhs=10,
                ),
            ),
            Constraint(
                id="c1",  # Duplicate
                kind=ConstraintKind.LINEAR,
                params=LinearConstraintParams(
                    terms=[LinearTerm(var="x", coef=1)],
                    sense=">=",
                    rhs=0,
                ),
            ),
        ],
    )

    result = validate_model(request)

    assert not result.is_valid
    assert len(result.errors) == 1
    assert "duplicate" in result.errors[0].message.lower()


def test_optimize_without_objective():
    """Test error for optimize mode without objective."""
    request = SolveConstraintModelRequest(
        mode=SolverMode.OPTIMIZE,
        variables=[
            Variable(
                id="x",
                domain=VariableDomain(type=VariableDomainType.INTEGER, lower=0, upper=10),
            )
        ],
        constraints=[],
        objective=None,  # Missing
    )

    result = validate_model(request)

    assert not result.is_valid
    assert len(result.errors) == 1
    assert "objective" in result.errors[0].message.lower()
    assert "add an objective" in result.errors[0].suggestion.lower()


def test_satisfy_with_objective_warning():
    """Test warning for satisfy mode with objective."""
    request = SolveConstraintModelRequest(
        mode=SolverMode.SATISFY,
        variables=[
            Variable(
                id="x",
                domain=VariableDomain(type=VariableDomainType.INTEGER, lower=0, upper=10),
            )
        ],
        constraints=[
            Constraint(
                id="c1",
                kind=ConstraintKind.LINEAR,
                params=LinearConstraintParams(
                    terms=[LinearTerm(var="x", coef=1)],
                    sense=">=",
                    rhs=0,
                ),
            )
        ],
        objective=Objective(sense=ObjectiveSense.MINIMIZE, terms=[LinearTerm(var="x", coef=1)]),
    )

    result = validate_model(request)

    assert result.is_valid  # Warning, not error
    assert len(result.warnings) == 1
    assert "ignored" in result.warnings[0].message.lower()


def test_undefined_variable_in_objective():
    """Test detection of undefined variables in objective."""
    request = SolveConstraintModelRequest(
        mode=SolverMode.OPTIMIZE,
        variables=[
            Variable(
                id="x",
                domain=VariableDomain(type=VariableDomainType.INTEGER, lower=0, upper=10),
            )
        ],
        constraints=[],
        objective=Objective(
            sense=ObjectiveSense.MINIMIZE,
            terms=[LinearTerm(var="y", coef=1)],  # Undefined
        ),
    )

    result = validate_model(request)

    assert not result.is_valid
    assert len(result.errors) >= 1  # At least the undefined variable error
    # Find the undefined variable error
    var_errors = [e for e in result.errors if "not defined" in e.message.lower()]
    assert len(var_errors) == 1
    assert "objective" in var_errors[0].location.lower()


def test_all_different_undefined_variable():
    """Test detection of undefined variables in all_different constraint."""
    request = SolveConstraintModelRequest(
        mode=SolverMode.SATISFY,
        variables=[
            Variable(
                id="x",
                domain=VariableDomain(type=VariableDomainType.INTEGER, lower=0, upper=10),
            )
        ],
        constraints=[
            Constraint(
                id="c1",
                kind=ConstraintKind.ALL_DIFFERENT,
                params=AllDifferentParams(vars=["x", "y", "z"]),  # y, z undefined
            )
        ],
    )

    result = validate_model(request)

    assert not result.is_valid
    assert len(result.errors) == 2  # y and z


def test_element_undefined_variables():
    """Test detection of undefined variables in element constraint."""
    request = SolveConstraintModelRequest(
        mode=SolverMode.SATISFY,
        variables=[
            Variable(
                id="x",
                domain=VariableDomain(type=VariableDomainType.INTEGER, lower=0, upper=10),
            )
        ],
        constraints=[
            Constraint(
                id="c1",
                kind=ConstraintKind.ELEMENT,
                params=ElementParams(
                    index_var="idx",  # Undefined
                    array=[1, 2, 3],
                    target_var="tgt",  # Undefined
                ),
            )
        ],
    )

    result = validate_model(request)

    assert not result.is_valid
    assert len(result.errors) == 2  # idx and tgt


def test_table_undefined_variables():
    """Test detection of undefined variables in table constraint."""
    request = SolveConstraintModelRequest(
        mode=SolverMode.SATISFY,
        variables=[
            Variable(
                id="x",
                domain=VariableDomain(type=VariableDomainType.INTEGER, lower=0, upper=10),
            )
        ],
        constraints=[
            Constraint(
                id="c1",
                kind=ConstraintKind.TABLE,
                params=TableParams(
                    vars=["x", "y"],  # y undefined
                    allowed_tuples=[[1, 2], [3, 4]],
                ),
            )
        ],
    )

    result = validate_model(request)

    assert not result.is_valid
    assert len(result.errors) == 1


def test_implication_undefined_variable():
    """Test detection of undefined variables in implication constraint."""
    request = SolveConstraintModelRequest(
        mode=SolverMode.SATISFY,
        variables=[
            Variable(
                id="x",
                domain=VariableDomain(type=VariableDomainType.BOOL),
            )
        ],
        constraints=[
            Constraint(
                id="c1",
                kind=ConstraintKind.IMPLICATION,
                params=ImplicationParams(
                    if_var="b",  # Undefined
                    then=Constraint(
                        id="c2",
                        kind=ConstraintKind.LINEAR,
                        params=LinearConstraintParams(
                            terms=[LinearTerm(var="x", coef=1)],
                            sense="<=",
                            rhs=5,
                        ),
                    ),
                ),
            )
        ],
    )

    result = validate_model(request)

    assert not result.is_valid
    assert len(result.errors) == 1
    assert "condition" in result.errors[0].message.lower()


def test_under_constrained_model_info():
    """Test info message for under-constrained models."""
    request = SolveConstraintModelRequest(
        mode=SolverMode.SATISFY,
        variables=[
            Variable(
                id=f"x{i}",
                domain=VariableDomain(type=VariableDomainType.INTEGER, lower=0, upper=10),
            )
            for i in range(20)  # 20 variables
        ],
        constraints=[  # Only 2 constraints
            Constraint(
                id="c1",
                kind=ConstraintKind.LINEAR,
                params=LinearConstraintParams(
                    terms=[LinearTerm(var="x0", coef=1)],
                    sense="<=",
                    rhs=5,
                ),
            )
        ],
    )

    result = validate_model(request)

    assert result.is_valid
    assert len(result.infos) >= 1
    assert any("under-constrained" in info.message.lower() for info in result.infos)


def test_over_constrained_model_info():
    """Test info message for over-constrained models."""
    request = SolveConstraintModelRequest(
        mode=SolverMode.SATISFY,
        variables=[
            Variable(
                id="x",
                domain=VariableDomain(type=VariableDomainType.INTEGER, lower=0, upper=10),
            )
        ],
        constraints=[
            Constraint(
                id=f"c{i}",
                kind=ConstraintKind.LINEAR,
                params=LinearConstraintParams(
                    terms=[LinearTerm(var="x", coef=1)],
                    sense="<=",
                    rhs=10,
                ),
            )
            for i in range(15)  # 15 constraints for 1 variable
        ],
    )

    result = validate_model(request)

    assert result.is_valid
    assert len(result.infos) >= 1
    assert any("over-constrained" in info.message.lower() for info in result.infos)


def test_no_constraints_warning():
    """Test warning for model with no constraints in satisfy mode."""
    request = SolveConstraintModelRequest(
        mode=SolverMode.SATISFY,
        variables=[
            Variable(
                id="x",
                domain=VariableDomain(type=VariableDomainType.INTEGER, lower=0, upper=10),
            )
        ],
        constraints=[],  # No constraints
    )

    result = validate_model(request)

    assert result.is_valid  # Warning, not error
    assert len(result.warnings) == 1
    assert "no constraints" in result.warnings[0].message.lower()


def test_validation_result_to_dict():
    """Test ValidationResult to_dict conversion."""
    request = SolveConstraintModelRequest(
        mode=SolverMode.SATISFY,
        variables=[
            Variable(
                id="x",
                domain=VariableDomain(type=VariableDomainType.INTEGER, lower=10, upper=5),  # Error
            )
        ],
        constraints=[],
    )

    result = validate_model(request)
    result_dict = result.to_dict()

    assert result_dict["is_valid"] is False
    assert result_dict["error_count"] == 1
    assert result_dict["warning_count"] >= 0
    assert "issues" in result_dict
    assert len(result_dict["issues"]) > 0


def test_model_validator_class():
    """Test using ModelValidator class directly."""
    validator = ModelValidator()

    request = SolveConstraintModelRequest(
        mode=SolverMode.SATISFY,
        variables=[
            Variable(
                id="x",
                domain=VariableDomain(type=VariableDomainType.INTEGER, lower=0, upper=10),
            )
        ],
        constraints=[],
    )

    result = validator.validate(request)

    assert result.is_valid
    assert len(result.errors) == 0
