"""
Test file for issue #12940: Python GRPC interface validates field values

This test verifies that validation prevents invalid values from being set.
"""

import sys
import os
import pytest

sys.path.insert(1, os.path.join(sys.path[0], "../../"))
import rips


def test_range_validation_minimum_enforced(rips_instance, initialize_test):
    """
    Verifies that setting value below minimum range is rejected.

    The wellBoreFluidPvtTable field has setMinValue(0) in C++, so negative values
    should be rejected with a clear error message.
    """
    well_path_coll = rips_instance.project.well_path_collection()

    # Create a new modeled well path
    well_path = well_path_coll.add_new_object(rips.ModeledWellPath)
    well_path.name = "test_validation"
    well_path.update()

    # Get completion settings
    completions_settings = well_path.completion_settings()

    # Set wellBoreFluidPvtTable to a negative value (below minimum of 0)
    completions_settings.well_bore_fluid_pvt_table = -5

    # Should raise grpc._channel._InactiveRpcError with validation message
    with pytest.raises(Exception) as exc_info:
        completions_settings.update()

    error_message = str(exc_info.value)
    assert "Validation failed" in error_message
    assert (
        "WellBoreFluidPvtTable" in error_message
        or "well_bore_fluid_pvt_table" in error_message
    )
    assert "-5" in error_message or "below minimum" in error_message


def test_valid_value_should_succeed(rips_instance, initialize_test):
    """
    Control test: Valid values should always work.

    This test should pass both before and after implementing validation.
    """
    well_path_coll = rips_instance.project.well_path_collection()

    well_path = well_path_coll.add_new_object(rips.ModeledWellPath)
    well_path.name = "test_valid"
    well_path.update()

    completions_settings = well_path.completion_settings()

    # Set valid values (within range)
    completions_settings.well_bore_fluid_pvt_table = 5  # Valid: >= 0
    completions_settings.fluid_in_place_region = 10  # Valid

    # This should succeed both before and after validation implementation
    completions_settings.update()

    # Verify values were set correctly
    completions_settings_updated = well_path.completion_settings()
    assert completions_settings_updated.well_bore_fluid_pvt_table == 5
    assert completions_settings_updated.fluid_in_place_region == 10


def test_value_unchanged_after_validation_error(rips_instance, initialize_test):
    """Verifies field value doesn't change if validation fails (rollback)."""
    well_path_coll = rips_instance.project.well_path_collection()
    well_path = well_path_coll.add_new_object(rips.ModeledWellPath)
    well_path.name = "test_rollback"
    well_path.update()

    completions_settings = well_path.completion_settings()

    # Set valid initial value
    completions_settings.well_bore_fluid_pvt_table = 42
    completions_settings.fluid_in_place_region = 99
    completions_settings.update()

    # Try to set invalid value
    completions_settings.well_bore_fluid_pvt_table = -5

    # Also set a different version for (should also be rolled back)
    completions_settings.fluid_in_place_region = 12

    with pytest.raises(Exception):
        completions_settings.update()

    # Value should remain at the previous valid value (42)
    completions_settings_refreshed = well_path.completion_settings()
    assert completions_settings_refreshed.well_bore_fluid_pvt_table == 42, (
        "Field value should not change after failed validation (rollback should occur)"
    )

    assert completions_settings_refreshed.fluid_in_place_region == 99, (
        "Field value should not change after failed validation (rollback should occur)"
    )


def test_strict_double_validation_extra_text(rips_instance, initialize_test):
    """Verifies strict validation for double fields."""
    well_path_coll = rips_instance.project.well_path_collection()
    well_path = well_path_coll.add_new_object(rips.ModeledWellPath)
    well_path.name = "test_double_strict_extra"
    well_path.update()

    fishbones_collection = well_path.completions().fishbones()

    # Successfully set a value
    fishbones_collection.main_bore_skin_factor = 50.0
    fishbones_collection.update()

    # Try a badly formatted value
    fishbones_collection.main_bore_skin_factor = "100.5test"
    with pytest.raises(Exception) as exc_info:
        fishbones_collection.update()

    assert "Extra characters found" in str(exc_info.value)
    assert "100.5test" in str(exc_info.value)

    # Should not overwrite when validation fails
    assert well_path.completions().fishbones().main_bore_skin_factor == 50.0


def test_strict_double_validation_text(rips_instance, initialize_test):
    """Verifies strict validation for double fields."""
    well_path_coll = rips_instance.project.well_path_collection()
    well_path = well_path_coll.add_new_object(rips.ModeledWellPath)
    well_path.name = "test_double_strict"
    well_path.update()

    fishbones_collection = well_path.completions().fishbones()
    fishbones_collection.main_bore_skin_factor = "test"

    with pytest.raises(Exception) as exc_info:
        fishbones_collection.update()

    print("Error", exc_info.value)
    assert "unreadable" in str(exc_info.value)
    assert "test" in str(exc_info.value)


def test_strict_int_validation(rips_instance, initialize_test):
    """Verifies strict validation for int fields."""
    well_path_coll = rips_instance.project.well_path_collection()
    well_path = well_path_coll.add_new_object(rips.ModeledWellPath)
    well_path.name = "test_int_strict"
    well_path.update()

    completions_settings = well_path.completion_settings()

    # well_bore_fluid_pvt_table is int
    completions_settings.well_bore_fluid_pvt_table = 0.99

    with pytest.raises(Exception) as exc_info:
        completions_settings.update()

    assert "Extra characters found" in str(exc_info.value)
    assert "0.99" in str(exc_info.value)


def test_validation_rollback(rips_instance, initialize_test):
    """Verifies that failed updates rollback local state."""
    well_path_coll = rips_instance.project.well_path_collection()
    well_path = well_path_coll.add_new_object(rips.ModeledWellPath)
    well_path.update()

    completions_settings = well_path.completion_settings()

    # Set valid value
    completions_settings.well_bore_fluid_pvt_table = 5
    completions_settings.update()
    assert completions_settings.well_bore_fluid_pvt_table == 5

    # Try to set invalid value (float instead of int)
    completions_settings.well_bore_fluid_pvt_table = 0.99

    with pytest.raises(Exception) as exc_info:
        completions_settings.update()

    assert "Extra characters found" in str(exc_info.value)

    # Verify rollback: should be back to 5, not 0.99
    assert completions_settings.well_bore_fluid_pvt_table == 5, (
        "Field should rollback to previous valid value after failed update"
    )


def test_enum_validation_message(rips_instance, initialize_test):
    """Verifies enum validation error message lists valid options."""
    well_path_coll = rips_instance.project.well_path_collection()
    well_path = well_path_coll.add_new_object(rips.ModeledWellPath)
    well_path.update()

    completions_settings = well_path.completion_settings()

    # gas_inflow_eq is an Enum
    # Valid values: STD, R-G, P-P, GPP

    completions_settings.gas_inflow_eq = "InvalidValue"

    with pytest.raises(Exception) as exc_info:
        completions_settings.update()

    error_msg = str(exc_info.value)
    assert "Failed to set enum text value" in error_msg
    assert "Valid options are:" in error_msg
    assert "STD" in error_msg
    assert "GPP" in error_msg


def test_strict_enum_validation(rips_instance, initialize_test):
    """Verifies strict validation for enum fields (trailing garbage)."""
    well_path_coll = rips_instance.project.well_path_collection()
    well_path = well_path_coll.add_new_object(rips.ModeledWellPath)
    well_path.update()

    completions_settings = well_path.completion_settings()
    completions_settings.gas_inflow_eq = "R-G"
    completions_settings.update()

    # STD is valid. "STD.garbage" should fail.
    completions_settings.gas_inflow_eq = "STD.garbage"

    with pytest.raises(Exception) as exc_info:
        completions_settings.update()

    error_msg = str(exc_info.value)
    # Expect "Extra characters found" because "STD" is parsed successfully, leaving ".garbage"
    assert "Extra characters found" in error_msg


def test_strict_bool_validation(rips_instance, initialize_test):
    """Verifies strict validation for bool fields."""
    # Get or create a FractureTemplate to test bool validation
    fracture_templates = rips_instance.project.descendants(rips.FractureTemplate)

    # Skip test if no FractureTemplate available (need a project with templates)
    if len(fracture_templates) == 0:
        pytest.skip("No FractureTemplate available for testing")

    fracture_template = fracture_templates[0]

    # Test 1: True and False should work
    fracture_template.user_defined_perforation_length = True
    fracture_template.update()
    assert fracture_template.user_defined_perforation_length is True

    fracture_template.user_defined_perforation_length = False
    fracture_template.update()
    assert fracture_template.user_defined_perforation_length is False

    # Test 2: Integer 1 should fail
    fracture_template.user_defined_perforation_length = 1
    with pytest.raises(Exception) as exc_info:
        fracture_template.update()

    error_msg = str(exc_info.value)
    assert (
        "Extra characters found" in error_msg
        or "unreadable" in error_msg
        or "does not evaluate to either true or false" in error_msg
    )

    # Verify automatic rollback: should be back to False
    assert fracture_template.user_defined_perforation_length is False

    # Test 3: Float 0.99 should fail
    fracture_template.user_defined_perforation_length = 0.99
    with pytest.raises(Exception) as exc_info:
        fracture_template.update()

    error_msg = str(exc_info.value)
    assert (
        "Extra characters found" in error_msg
        or "unreadable" in error_msg
        or "does not evaluate to either true or false" in error_msg
    )

    # Verify automatic rollback: should be back to False
    assert fracture_template.user_defined_perforation_length is False

    # Test 4: String "yes" should fail
    fracture_template.user_defined_perforation_length = "yes"
    with pytest.raises(Exception) as exc_info:
        fracture_template.update()

    error_msg = str(exc_info.value)
    assert (
        "Extra characters found" in error_msg
        or "unreadable" in error_msg
        or "does not evaluate to either true or false" in error_msg
    )

    # Verify automatic rollback: should be back to False
    assert fracture_template.user_defined_perforation_length is False
