"""
Tests for Testing Assertions.

This module provides tests for the assertion utilities in the
torivers_sdk.testing.assertions module.
"""

from __future__ import annotations

from dataclasses import dataclass, field

import pytest

from torivers_sdk.testing.assertions import (
    assert_execution_time,
    assert_memory_within_limit,
    assert_no_errors,
    assert_output_contains,
    assert_output_matches,
    assert_output_type,
    assert_success,
    assert_tokens_within_limit,
    assert_within_limits,
)

# =============================================================================
# Test Fixtures
# =============================================================================


@dataclass
class MockExecutionResult:
    """Mock execution result for testing assertions."""

    success: bool = True
    output_data: dict = field(default_factory=dict)
    errors: list = field(default_factory=list)
    progress_entries: list = field(default_factory=list)
    execution_time_seconds: float = 1.0
    peak_memory_mb: float = 100.0
    total_tokens_used: int = 500


def _json_output_data(data: dict) -> dict:
    return {
        "_version": 1,
        "_blocks": [
            {
                "type": "json_data",
                "label": "Result",
                "data": data,
                "display_hint": "key_value",
            }
        ],
    }


@pytest.fixture
def successful_result() -> MockExecutionResult:
    """Create a successful execution result."""
    return MockExecutionResult(
        success=True,
        output_data=_json_output_data(
            {"result": "success", "count": 42, "items": ["a", "b", "c"]}
        ),
        errors=[],
        execution_time_seconds=1.5,
        peak_memory_mb=128.0,
        total_tokens_used=1000,
    )


@pytest.fixture
def failed_result() -> MockExecutionResult:
    """Create a failed execution result."""
    return MockExecutionResult(
        success=False,
        output_data={},
        errors=["Something went wrong", "Another error"],
        execution_time_seconds=0.5,
    )


# =============================================================================
# Output Assertion Tests
# =============================================================================


class TestOutputAssertions:
    """Test suite for output assertions."""

    def test_assert_output_contains_pass(
        self, successful_result: MockExecutionResult
    ) -> None:
        """Test assert_output_contains passes when key exists."""
        assert_output_contains(successful_result, "result")
        assert_output_contains(successful_result, "count")
        assert_output_contains(successful_result, "items")

    def test_assert_output_contains_fail(
        self, successful_result: MockExecutionResult
    ) -> None:
        """Test assert_output_contains fails when key missing."""
        with pytest.raises(AssertionError) as exc_info:
            assert_output_contains(successful_result, "nonexistent")

        assert "missing key" in str(exc_info.value).lower()

    def test_assert_output_contains_custom_message(
        self, successful_result: MockExecutionResult
    ) -> None:
        """Test assert_output_contains with custom error message."""
        with pytest.raises(AssertionError) as exc_info:
            assert_output_contains(
                successful_result, "nonexistent", message="Custom error message"
            )

        assert "Custom error message" in str(exc_info.value)

    def test_assert_output_matches_pass(
        self, successful_result: MockExecutionResult
    ) -> None:
        """Test assert_output_matches passes when value matches."""
        assert_output_matches(successful_result, "result", "success")
        assert_output_matches(successful_result, "count", 42)

    def test_assert_output_matches_fail(
        self, successful_result: MockExecutionResult
    ) -> None:
        """Test assert_output_matches fails when value differs."""
        with pytest.raises(AssertionError) as exc_info:
            assert_output_matches(successful_result, "result", "failure")

        assert "success" in str(exc_info.value)
        assert "failure" in str(exc_info.value)

    def test_assert_output_type_pass(
        self, successful_result: MockExecutionResult
    ) -> None:
        """Test assert_output_type passes when type matches."""
        assert_output_type(successful_result, "result", str)
        assert_output_type(successful_result, "count", int)
        assert_output_type(successful_result, "items", list)

    def test_assert_output_type_fail(
        self, successful_result: MockExecutionResult
    ) -> None:
        """Test assert_output_type fails when type differs."""
        with pytest.raises(AssertionError) as exc_info:
            assert_output_type(successful_result, "result", int)

        assert "str" in str(exc_info.value)
        assert "int" in str(exc_info.value)


# =============================================================================
# Success/Error Assertion Tests
# =============================================================================


class TestSuccessAssertions:
    """Test suite for success and error assertions."""

    def test_assert_success_pass(self, successful_result: MockExecutionResult) -> None:
        """Test assert_success passes when successful."""
        assert_success(successful_result)

    def test_assert_success_fail(self, failed_result: MockExecutionResult) -> None:
        """Test assert_success fails when not successful."""
        with pytest.raises(AssertionError) as exc_info:
            assert_success(failed_result)

        assert "failed" in str(exc_info.value).lower()

    def test_assert_no_errors_pass(
        self, successful_result: MockExecutionResult
    ) -> None:
        """Test assert_no_errors passes when no errors."""
        assert_no_errors(successful_result)

    def test_assert_no_errors_fail(self, failed_result: MockExecutionResult) -> None:
        """Test assert_no_errors fails when errors exist."""
        with pytest.raises(AssertionError) as exc_info:
            assert_no_errors(failed_result)

        assert "errors" in str(exc_info.value).lower()


# =============================================================================
# Resource Limit Assertion Tests
# =============================================================================


class TestResourceLimitAssertions:
    """Test suite for resource limit assertions."""

    def test_assert_execution_time_pass(
        self, successful_result: MockExecutionResult
    ) -> None:
        """Test assert_execution_time passes within limit."""
        assert_execution_time(successful_result, max_seconds=10.0)

    def test_assert_execution_time_fail(
        self, successful_result: MockExecutionResult
    ) -> None:
        """Test assert_execution_time fails when exceeded."""
        with pytest.raises(AssertionError) as exc_info:
            assert_execution_time(successful_result, max_seconds=1.0)

        assert "1.50" in str(exc_info.value)  # Actual time
        assert "1.00" in str(exc_info.value)  # Max time

    def test_assert_within_limits_all_pass(
        self, successful_result: MockExecutionResult
    ) -> None:
        """Test assert_within_limits passes when all within limits."""
        assert_within_limits(
            successful_result,
            max_duration=10.0,
            max_memory=512.0,
            max_tokens=5000,
        )

    def test_assert_within_limits_duration_fail(
        self, successful_result: MockExecutionResult
    ) -> None:
        """Test assert_within_limits fails when duration exceeded."""
        with pytest.raises(AssertionError) as exc_info:
            assert_within_limits(successful_result, max_duration=1.0)

        assert "duration" in str(exc_info.value).lower()
        assert "1.50" in str(exc_info.value)

    def test_assert_within_limits_memory_fail(
        self, successful_result: MockExecutionResult
    ) -> None:
        """Test assert_within_limits fails when memory exceeded."""
        with pytest.raises(AssertionError) as exc_info:
            assert_within_limits(successful_result, max_memory=64.0)

        assert "memory" in str(exc_info.value).lower()
        assert "128.00" in str(exc_info.value)

    def test_assert_within_limits_tokens_fail(
        self, successful_result: MockExecutionResult
    ) -> None:
        """Test assert_within_limits fails when tokens exceeded."""
        with pytest.raises(AssertionError) as exc_info:
            assert_within_limits(successful_result, max_tokens=500)

        assert "tokens" in str(exc_info.value).lower()
        assert "1000" in str(exc_info.value)

    def test_assert_within_limits_multiple_fail(
        self, successful_result: MockExecutionResult
    ) -> None:
        """Test assert_within_limits reports multiple violations."""
        with pytest.raises(AssertionError) as exc_info:
            assert_within_limits(
                successful_result,
                max_duration=1.0,
                max_memory=64.0,
                max_tokens=500,
            )

        error_msg = str(exc_info.value)
        assert "duration" in error_msg.lower()
        assert "memory" in error_msg.lower()
        assert "tokens" in error_msg.lower()

    def test_assert_within_limits_custom_message(
        self, successful_result: MockExecutionResult
    ) -> None:
        """Test assert_within_limits with custom message prefix."""
        with pytest.raises(AssertionError) as exc_info:
            assert_within_limits(
                successful_result,
                max_duration=1.0,
                message="Production limits exceeded",
            )

        assert "Production limits exceeded" in str(exc_info.value)

    def test_assert_within_limits_partial_checks(
        self, successful_result: MockExecutionResult
    ) -> None:
        """Test assert_within_limits with only some checks."""
        # Only check duration - should pass
        assert_within_limits(successful_result, max_duration=10.0)

        # Only check memory - should pass
        assert_within_limits(successful_result, max_memory=512.0)

        # Only check tokens - should pass
        assert_within_limits(successful_result, max_tokens=5000)

    def test_assert_tokens_within_limit_pass(
        self, successful_result: MockExecutionResult
    ) -> None:
        """Test assert_tokens_within_limit passes within limit."""
        assert_tokens_within_limit(successful_result, max_tokens=5000)

    def test_assert_tokens_within_limit_fail(
        self, successful_result: MockExecutionResult
    ) -> None:
        """Test assert_tokens_within_limit fails when exceeded."""
        with pytest.raises(AssertionError) as exc_info:
            assert_tokens_within_limit(successful_result, max_tokens=500)

        assert "1000" in str(exc_info.value)
        assert "500" in str(exc_info.value)

    def test_assert_memory_within_limit_pass(
        self, successful_result: MockExecutionResult
    ) -> None:
        """Test assert_memory_within_limit passes within limit."""
        assert_memory_within_limit(successful_result, max_memory_mb=512.0)

    def test_assert_memory_within_limit_fail(
        self, successful_result: MockExecutionResult
    ) -> None:
        """Test assert_memory_within_limit fails when exceeded."""
        with pytest.raises(AssertionError) as exc_info:
            assert_memory_within_limit(successful_result, max_memory_mb=64.0)

        assert "128.00" in str(exc_info.value)
        assert "64.00" in str(exc_info.value)


# =============================================================================
# Edge Case Tests
# =============================================================================


class TestEdgeCases:
    """Test edge cases and boundary conditions."""

    def test_assert_within_limits_no_limits_specified(self) -> None:
        """Test assert_within_limits with no limits doesn't raise."""
        result = MockExecutionResult()
        # Should not raise when no limits are specified
        assert_within_limits(result)

    def test_assert_within_limits_exactly_at_limit(self) -> None:
        """Test assert_within_limits at exactly the limit."""
        result = MockExecutionResult(
            execution_time_seconds=10.0,
            peak_memory_mb=512.0,
            total_tokens_used=1000,
        )
        # Exactly at limit should not raise
        assert_within_limits(
            result,
            max_duration=10.0,
            max_memory=512.0,
            max_tokens=1000,
        )

    def test_assert_within_limits_slightly_over_limit(self) -> None:
        """Test assert_within_limits slightly over limit."""
        result = MockExecutionResult(execution_time_seconds=10.001)

        with pytest.raises(AssertionError):
            assert_within_limits(result, max_duration=10.0)

    def test_assert_within_limits_missing_attributes(self) -> None:
        """Test assert_within_limits with result missing attributes."""

        # Create result without memory/token tracking
        @dataclass
        class MinimalResult:
            execution_time_seconds: float = 1.0

        result = MinimalResult()

        # Should not raise - missing attributes default to 0
        assert_within_limits(result, max_memory=100.0, max_tokens=1000)

    def test_empty_output_data(self) -> None:
        """Test assertions with empty output data."""
        result = MockExecutionResult(output_data={})

        with pytest.raises(AssertionError):
            assert_output_contains(result, "any_key")

    def test_none_values_in_output(self) -> None:
        """Test assertions handle None values correctly."""
        result = MockExecutionResult(output_data=_json_output_data({"nullable": None}))

        assert_output_contains(result, "nullable")
        assert_output_matches(result, "nullable", None)
        assert_output_type(result, "nullable", type(None))
