"""Tests for custom RLM exception classes."""

import pytest

from groknroll.core.exceptions import (
    CompletionTimeoutError,
    CostLimitExceededError,
    EnvironmentCleanupError,
    EnvironmentError,
    EnvironmentSetupError,
    FinalAnswerNotFoundError,
    IterationLimitExceededError,
    LMHandlerConnectionError,
    LMHandlerError,
    LMHandlerTimeoutError,
    ParsingError,
    REPLExecutionError,
    RLMError,
)


class TestRLMErrorHierarchy:
    """Test that all custom exceptions inherit from RLMError."""

    def test_repl_execution_error_is_rlm_error(self):
        assert issubclass(REPLExecutionError, RLMError)

    def test_lm_handler_error_is_rlm_error(self):
        assert issubclass(LMHandlerError, RLMError)

    def test_environment_error_is_rlm_error(self):
        assert issubclass(EnvironmentError, RLMError)

    def test_cost_limit_error_is_rlm_error(self):
        assert issubclass(CostLimitExceededError, RLMError)

    def test_timeout_error_is_rlm_error(self):
        assert issubclass(CompletionTimeoutError, RLMError)

    def test_parsing_error_is_rlm_error(self):
        assert issubclass(ParsingError, RLMError)


class TestREPLExecutionError:
    """Test REPLExecutionError functionality."""

    def test_basic_creation(self):
        error = REPLExecutionError("Execution failed")
        assert str(error) == "Execution failed"
        assert error.code is None
        assert error.stderr is None

    def test_with_code_and_stderr(self):
        error = REPLExecutionError(
            "Execution failed", code="x = 1/0", stderr="ZeroDivisionError: division by zero"
        )
        assert error.code == "x = 1/0"
        assert "ZeroDivisionError" in error.stderr


class TestLMHandlerTimeoutError:
    """Test LMHandlerTimeoutError functionality."""

    def test_basic_creation(self):
        error = LMHandlerTimeoutError("Request timed out")
        assert str(error) == "Request timed out"
        assert error.timeout_seconds is None

    def test_with_timeout_value(self):
        error = LMHandlerTimeoutError("Request timed out after 30s", timeout_seconds=30.0)
        assert error.timeout_seconds == 30.0


class TestCostLimitExceededError:
    """Test CostLimitExceededError functionality."""

    def test_basic_creation(self):
        error = CostLimitExceededError("Cost limit exceeded")
        assert str(error) == "Cost limit exceeded"
        assert error.current_cost is None
        assert error.cost_limit is None

    def test_with_cost_values(self):
        error = CostLimitExceededError(
            "Cost $15.50 exceeded limit $10.00", current_cost=15.50, cost_limit=10.00
        )
        assert error.current_cost == 15.50
        assert error.cost_limit == 10.00
        assert "$15.50" in str(error)
        assert "$10.00" in str(error)


class TestCompletionTimeoutError:
    """Test CompletionTimeoutError functionality."""

    def test_basic_creation(self):
        error = CompletionTimeoutError("Completion timed out")
        assert str(error) == "Completion timed out"
        assert error.timeout_seconds is None
        assert error.elapsed_seconds is None

    def test_with_timeout_values(self):
        error = CompletionTimeoutError(
            "Timeout after 120s", timeout_seconds=120.0, elapsed_seconds=125.3
        )
        assert error.timeout_seconds == 120.0
        assert error.elapsed_seconds == 125.3


class TestIterationLimitExceededError:
    """Test IterationLimitExceededError functionality."""

    def test_basic_creation(self):
        error = IterationLimitExceededError("Max iterations reached")
        assert str(error) == "Max iterations reached"
        assert error.max_iterations is None
        assert error.current_iteration is None

    def test_with_iteration_values(self):
        error = IterationLimitExceededError(
            "Exceeded 30 iterations", max_iterations=30, current_iteration=31
        )
        assert error.max_iterations == 30
        assert error.current_iteration == 31


class TestFinalAnswerNotFoundError:
    """Test FinalAnswerNotFoundError functionality."""

    def test_basic_creation(self):
        error = FinalAnswerNotFoundError("No final answer found")
        assert str(error) == "No final answer found"
        assert error.iterations_completed is None

    def test_with_iterations(self):
        error = FinalAnswerNotFoundError(
            "No final answer after 30 iterations", iterations_completed=30
        )
        assert error.iterations_completed == 30


class TestParsingError:
    """Test ParsingError functionality."""

    def test_basic_creation(self):
        error = ParsingError("Failed to parse response")
        assert str(error) == "Failed to parse response"
        assert error.response_text is None

    def test_with_response_text(self):
        response = "This is some invalid response text"
        error = ParsingError("Failed to parse", response_text=response)
        assert error.response_text == response


class TestEnvironmentErrors:
    """Test environment-related errors."""

    def test_environment_setup_error(self):
        error = EnvironmentSetupError("Failed to setup environment")
        assert isinstance(error, EnvironmentError)
        assert isinstance(error, RLMError)

    def test_environment_cleanup_error(self):
        error = EnvironmentCleanupError("Failed to cleanup environment")
        assert isinstance(error, EnvironmentError)
        assert isinstance(error, RLMError)


class TestLMHandlerErrors:
    """Test LM handler-related errors."""

    def test_lm_handler_connection_error(self):
        error = LMHandlerConnectionError("Failed to connect to handler")
        assert isinstance(error, LMHandlerError)
        assert isinstance(error, RLMError)

    def test_lm_handler_timeout_error(self):
        error = LMHandlerTimeoutError("Handler request timed out")
        assert isinstance(error, LMHandlerError)
        assert isinstance(error, RLMError)


class TestExceptionCatching:
    """Test that exceptions can be caught by their base classes."""

    def test_catch_all_rlm_errors(self):
        """Test that all RLM exceptions can be caught by RLMError."""
        exceptions = [
            REPLExecutionError("test"),
            LMHandlerError("test"),
            CostLimitExceededError("test"),
            CompletionTimeoutError("test"),
            ParsingError("test"),
            EnvironmentError("test"),
        ]

        for exc in exceptions:
            with pytest.raises(RLMError):
                raise exc

    def test_catch_specific_error_types(self):
        """Test that specific error types can be caught."""
        with pytest.raises(LMHandlerError):
            raise LMHandlerTimeoutError("test")

        with pytest.raises(EnvironmentError):
            raise EnvironmentSetupError("test")

        with pytest.raises(ParsingError):
            raise FinalAnswerNotFoundError("test")
