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

import unittest
from pygeai_orchestration.core.exceptions import (
    OrchestrationError,
    PatternExecutionError,
    PatternConfigurationError,
    AgentError,
    ToolExecutionError,
    StateError,
    ValidationError,
)


class TestExceptionHierarchy(unittest.TestCase):
    """Test exception inheritance and base functionality."""

    def test_orchestration_error_base(self):
        """Test base OrchestrationError."""
        error = OrchestrationError("Test error")
        self.assertIsInstance(error, Exception)
        self.assertEqual(str(error), "Test error")

    def test_pattern_execution_error_inheritance(self):
        """Test PatternExecutionError inherits from OrchestrationError."""
        error = PatternExecutionError("Pattern failed")
        self.assertIsInstance(error, OrchestrationError)
        self.assertIsInstance(error, Exception)

    def test_all_exceptions_inherit_from_base(self):
        """Test all custom exceptions inherit from OrchestrationError."""
        exceptions = [
            PatternExecutionError("test"),
            PatternConfigurationError("test"),
            AgentError("test"),
            ToolExecutionError("test"),
            StateError("test"),
            ValidationError("test"),
        ]

        for exc in exceptions:
            self.assertIsInstance(exc, OrchestrationError)


class TestPatternExecutionError(unittest.TestCase):
    """Test PatternExecutionError functionality."""

    def test_basic_error(self):
        """Test basic error creation."""
        error = PatternExecutionError("Execution failed")
        self.assertEqual(str(error), "Execution failed")

    def test_error_with_pattern_name(self):
        """Test error with pattern name."""
        error = PatternExecutionError(
            "Execution failed",
            pattern_name="reflection"
        )
        self.assertIn("reflection", str(error))
        self.assertEqual(error.pattern_name, "reflection")

    def test_error_with_iteration(self):
        """Test error with iteration number."""
        error = PatternExecutionError(
            "Execution failed",
            pattern_name="reflection",
            iteration=5
        )
        self.assertIn("5", str(error))
        self.assertEqual(error.iteration, 5)

    def test_error_with_details(self):
        """Test error with additional details."""
        details = {"max_iterations": 3, "timeout": True}
        error = PatternExecutionError(
            "Execution failed",
            pattern_name="reflection",
            iteration=5,
            details=details
        )
        self.assertEqual(error.details, details)
        self.assertIn("max_iterations", str(error))

    def test_error_string_formatting(self):
        """Test formatted error message."""
        error = PatternExecutionError(
            "Pattern execution failed",
            pattern_name="reflection",
            iteration=3,
            details={"error": "timeout"}
        )
        error_str = str(error)
        self.assertIn("Pattern execution failed", error_str)
        self.assertIn("Pattern: reflection", error_str)
        self.assertIn("Iteration: 3", error_str)
        self.assertIn("Details:", error_str)


class TestPatternConfigurationError(unittest.TestCase):
    """Test PatternConfigurationError functionality."""

    def test_basic_error(self):
        """Test basic configuration error."""
        error = PatternConfigurationError("Invalid configuration")
        self.assertEqual(str(error), "Invalid configuration")

    def test_error_with_field(self):
        """Test error with config field."""
        error = PatternConfigurationError(
            "Invalid value",
            config_field="max_iterations"
        )
        self.assertIn("max_iterations", str(error))
        self.assertEqual(error.config_field, "max_iterations")

    def test_error_with_expected_received(self):
        """Test error with expected and received values."""
        error = PatternConfigurationError(
            "Invalid max_iterations",
            config_field="max_iterations",
            expected="positive integer",
            received=-1
        )
        self.assertIn("positive integer", str(error))
        self.assertIn("-1", str(error))
        self.assertEqual(error.expected, "positive integer")
        self.assertEqual(error.received, -1)


class TestAgentError(unittest.TestCase):
    """Test AgentError functionality."""

    def test_basic_error(self):
        """Test basic agent error."""
        error = AgentError("Agent failed")
        self.assertEqual(str(error), "Agent failed")

    def test_error_with_agent_name(self):
        """Test error with agent name."""
        error = AgentError(
            "Generation failed",
            agent_name="research-agent"
        )
        self.assertIn("research-agent", str(error))
        self.assertEqual(error.agent_name, "research-agent")

    def test_error_with_operation(self):
        """Test error with operation name."""
        error = AgentError(
            "Operation failed",
            agent_name="research-agent",
            operation="generate"
        )
        self.assertIn("generate", str(error))
        self.assertEqual(error.operation, "generate")

    def test_error_with_details(self):
        """Test error with additional details."""
        details = {"timeout": True, "retry_count": 3}
        error = AgentError(
            "Agent failed",
            agent_name="research-agent",
            operation="generate",
            details=details
        )
        self.assertEqual(error.details, details)


class TestToolExecutionError(unittest.TestCase):
    """Test ToolExecutionError functionality."""

    def test_basic_error(self):
        """Test basic tool error."""
        error = ToolExecutionError("Tool failed")
        self.assertEqual(str(error), "Tool failed")

    def test_error_with_tool_name(self):
        """Test error with tool name."""
        error = ToolExecutionError(
            "Execution failed",
            tool_name="calculator"
        )
        self.assertIn("calculator", str(error))
        self.assertEqual(error.tool_name, "calculator")

    def test_error_with_input_data(self):
        """Test error with input data."""
        input_data = {"expression": "2 + + 2"}
        error = ToolExecutionError(
            "Invalid expression",
            tool_name="calculator",
            input_data=input_data
        )
        self.assertEqual(error.input_data, input_data)
        self.assertIn("expression", str(error))

    def test_error_with_details(self):
        """Test error with additional details."""
        details = {"error_type": "SyntaxError"}
        error = ToolExecutionError(
            "Execution failed",
            tool_name="calculator",
            details=details
        )
        self.assertEqual(error.details, details)


class TestStateError(unittest.TestCase):
    """Test StateError functionality."""

    def test_basic_error(self):
        """Test basic state error."""
        error = StateError("State error")
        self.assertEqual(str(error), "State error")

    def test_error_with_states(self):
        """Test error with state information."""
        error = StateError(
            "Invalid transition",
            current_state="running",
            attempted_state="completed"
        )
        self.assertIn("running", str(error))
        self.assertIn("completed", str(error))
        self.assertEqual(error.current_state, "running")
        self.assertEqual(error.attempted_state, "completed")

    def test_error_with_details(self):
        """Test error with additional details."""
        details = {"reason": "tasks pending"}
        error = StateError(
            "Cannot transition",
            current_state="running",
            attempted_state="completed",
            details=details
        )
        self.assertEqual(error.details, details)


class TestValidationError(unittest.TestCase):
    """Test ValidationError functionality."""

    def test_basic_error(self):
        """Test basic validation error."""
        error = ValidationError("Validation failed")
        self.assertEqual(str(error), "Validation failed")

    def test_error_with_field(self):
        """Test error with field information."""
        error = ValidationError(
            "Invalid value",
            field="task"
        )
        self.assertIn("task", str(error))
        self.assertEqual(error.field, "task")

    def test_error_with_expected_received(self):
        """Test error with expected and received values."""
        error = ValidationError(
            "Invalid task",
            field="task",
            expected="non-empty string",
            received=""
        )
        self.assertIn("non-empty string", str(error))
        self.assertEqual(error.expected, "non-empty string")
        self.assertEqual(error.received, "")

    def test_error_with_example(self):
        """Test error with example."""
        error = ValidationError(
            "Invalid input",
            field="task",
            expected="non-empty string",
            received="",
            example='task="Summarize this"'
        )
        self.assertIn("Summarize", str(error))
        self.assertEqual(error.example, 'task="Summarize this"')

    def test_error_string_formatting(self):
        """Test complete formatted error message."""
        error = ValidationError(
            "Invalid task input",
            field="task",
            expected="non-empty string",
            received="",
            example='task="Summarize document"'
        )
        error_str = str(error)
        self.assertIn("Invalid task input", error_str)
        self.assertIn("Field: task", error_str)
        self.assertIn("Expected: non-empty string", error_str)
        self.assertIn("Received:", error_str)
        self.assertIn("Example:", error_str)


class TestExceptionCatching(unittest.TestCase):
    """Test exception catching patterns."""

    def test_catch_specific_exception(self):
        """Test catching specific exception type."""
        with self.assertRaises(PatternExecutionError):
            raise PatternExecutionError("Test")

    def test_catch_as_base_exception(self):
        """Test catching any orchestration error."""
        with self.assertRaises(OrchestrationError):
            raise PatternExecutionError("Test")

    def test_catch_all_orchestration_errors(self):
        """Test catching different errors as base type."""
        errors = [
            PatternExecutionError("test"),
            AgentError("test"),
            ValidationError("test"),
        ]

        for error in errors:
            with self.assertRaises(OrchestrationError):
                raise error


if __name__ == "__main__":
    unittest.main()
