"""Test streaming evaluation functionality."""

from unittest.mock import Mock, patch

import pytest

from veritell_langchain import (
    VeritellAPIError,
    VeritellAuthError,
    VeritellConnectionError,
    VeritellEvaluator,
    VeritellStreamEvent,
    VeritellValidationError,
)


@pytest.fixture
def evaluator():
    """Create a VeritellEvaluator instance with test API key."""
    return VeritellEvaluator(api_key="test-api-overview-key")


def test_evaluate_stream_basic(evaluator):
    """Test basic streaming evaluation."""
    # Mock streaming response with NDJSON
    mock_response = Mock()
    mock_response.status_code = 200
    mock_response.iter_lines.return_value = [
        '{"type": "start", "message": "Starting evaluation"}',
        '{"type": "chunk", "content": "Evaluating..."}',
        '{"type": "complete", "score": 0.95, "passed": true}',
    ]

    with patch("requests.post", return_value=mock_response):
        events = list(
            evaluator.evaluate_stream(
                prompt="Test prompt",
                primary_model="gpt-4o",
                judges=["gpt-4o-mini", "grok-3-latest"],
            )
        )

        assert len(events) == 3
        assert all(isinstance(e, VeritellStreamEvent) for e in events)
        assert events[0].event_type == "start"
        assert events[1].event_type == "chunk"
        assert events[2].event_type == "complete"


def test_evaluate_stream_with_model_output(evaluator):
    """Test streaming evaluation with pre-generated output."""
    mock_response = Mock()
    mock_response.status_code = 200
    mock_response.iter_lines.return_value = [
        '{"type": "complete", "score": 0.8}',
    ]

    with patch("requests.post", return_value=mock_response) as mock_post:
        list(
            evaluator.evaluate_stream(
                prompt="Test prompt",
                primary_model="gpt-4o",
                judges=["gpt-4o-mini"],
                model_output="Pre-generated output",
            )
        )

        # Verify model_output was passed
        call_args = mock_post.call_args
        assert call_args[1]["json"]["model_output"] == "Pre-generated output"


def test_evaluate_stream_validation_empty_prompt(evaluator):
    """Test that empty prompt raises validation error."""
    with pytest.raises(VeritellValidationError) as exc_info:
        list(evaluator.evaluate_stream(prompt="", primary_model="gpt-4o", judges=["gpt-4o-mini"]))
    assert "prompt must be a non-empty string" in str(exc_info.value)


def test_evaluate_stream_validation_empty_model(evaluator):
    """Test that empty primary_model raises validation error."""
    with pytest.raises(VeritellValidationError) as exc_info:
        list(evaluator.evaluate_stream(prompt="test", primary_model="", judges=["gpt-4o-mini"]))
    assert "primary_model must be a non-empty string" in str(exc_info.value)


def test_evaluate_stream_validation_empty_judges(evaluator):
    """Test that empty judges list raises validation error."""
    with pytest.raises(VeritellValidationError) as exc_info:
        list(evaluator.evaluate_stream(prompt="test", primary_model="gpt-4o", judges=[]))
    assert "judges must be a non-empty list" in str(exc_info.value)


def test_evaluate_stream_auth_error(evaluator):
    """Test that 401 response raises auth error."""
    mock_response = Mock()
    mock_response.status_code = 401

    with patch("requests.post", return_value=mock_response):
        with pytest.raises(VeritellAuthError) as exc_info:
            list(
                evaluator.evaluate_stream(
                    prompt="test", primary_model="gpt-4o", judges=["gpt-4o-mini"]
                )
            )
        assert "Authentication failed" in str(exc_info.value)


def test_evaluate_stream_api_error(evaluator):
    """Test that API errors are handled correctly."""
    mock_response = Mock()
    mock_response.status_code = 500
    mock_response.text = "Internal server error"

    with patch("requests.post", return_value=mock_response):
        with pytest.raises(VeritellAPIError) as exc_info:
            list(
                evaluator.evaluate_stream(
                    prompt="test", primary_model="gpt-4o", judges=["gpt-4o-mini"]
                )
            )
        assert exc_info.value.status_code == 500
        assert "Internal server error" in str(exc_info.value)


def test_evaluate_stream_timeout_error(evaluator):
    """Test that timeout errors are handled."""
    import requests

    with patch("requests.post", side_effect=requests.exceptions.Timeout()):
        with pytest.raises(VeritellConnectionError) as exc_info:
            list(
                evaluator.evaluate_stream(
                    prompt="test", primary_model="gpt-4o", judges=["gpt-4o-mini"]
                )
            )
        assert "timed out" in str(exc_info.value)


def test_evaluate_stream_connection_error(evaluator):
    """Test that connection errors are handled."""
    import requests

    with patch(
        "requests.post",
        side_effect=requests.exceptions.ConnectionError(),
    ):
        with pytest.raises(VeritellConnectionError) as exc_info:
            list(
                evaluator.evaluate_stream(
                    prompt="test", primary_model="gpt-4o", judges=["gpt-4o-mini"]
                )
            )
        assert "Connection error" in str(exc_info.value)


def test_evaluate_stream_with_metadata(evaluator):
    """Test streaming evaluation with metadata."""
    mock_response = Mock()
    mock_response.status_code = 200
    mock_response.iter_lines.return_value = ['{"type": "complete"}']

    with patch("requests.post", return_value=mock_response) as mock_post:
        metadata = {"user_id": "123", "session_id": "abc"}
        list(
            evaluator.evaluate_stream(
                prompt="test",
                primary_model="gpt-4o",
                judges=["gpt-4o-mini"],
                metadata=metadata,
            )
        )

        call_args = mock_post.call_args
        assert call_args[1]["json"]["metadata"] == metadata


def test_evaluate_stream_headers(evaluator):
    """Test that streaming request headers are set correctly."""
    mock_response = Mock()
    mock_response.status_code = 200
    mock_response.iter_lines.return_value = []

    with patch("requests.post", return_value=mock_response) as mock_post:
        list(
            evaluator.evaluate_stream(prompt="test", primary_model="gpt-4o", judges=["gpt-4o-mini"])
        )

        call_args = mock_post.call_args
        headers = call_args[1]["headers"]

        # The backend supports API key auth primarily via X-Api-Key.
        # We also keep an Authorization: Bearer header for compatibility.
        assert ("X-Api-Key" in headers) or ("Authorization" in headers)
        if "X-Api-Key" in headers:
            assert headers["X-Api-Key"] == evaluator.api_key
        assert headers["Content-Type"] == "application/json"
        assert headers["Accept"] == "application/x-ndjson"
        assert call_args[1]["stream"] is True


def test_evaluate_stream_request_format(evaluator):
    """Test that streaming request is formatted correctly."""
    mock_response = Mock()
    mock_response.status_code = 200
    mock_response.iter_lines.return_value = []

    with patch("requests.post", return_value=mock_response) as mock_post:
        list(
            evaluator.evaluate_stream(
                prompt="test prompt",
                primary_model="gpt-4o",
                judges=["gpt-4o-mini", "grok-3-latest"],
            )
        )

        call_args = mock_post.call_args
        assert call_args[0][0] == f"{evaluator.api_base_url}/evaluate/stream"
        payload = call_args[1]["json"]
        assert payload["prompt"] == "test prompt"
        assert payload["primary_model"] == "gpt-4o"
        assert payload["judges"] == ["gpt-4o-mini", "grok-3-latest"]


def test_evaluate_stream_malformed_json(evaluator):
    """Test handling of malformed JSON in stream."""
    mock_response = Mock()
    mock_response.status_code = 200
    mock_response.iter_lines.return_value = [
        '{"type": "start"}',
        "not valid json",
        '{"type": "complete"}',
    ]

    with patch("requests.post", return_value=mock_response):
        events = list(
            evaluator.evaluate_stream(prompt="test", primary_model="gpt-4o", judges=["gpt-4o-mini"])
        )

        assert len(events) == 3
        assert events[0].event_type == "start"
        assert events[1].event_type == "raw"  # Malformed JSON becomes raw event
        assert events[1].data == {"text": "not valid json"}
        assert events[2].event_type == "complete"


def test_stream_event_repr():
    """Test VeritellStreamEvent string representation."""
    event = VeritellStreamEvent(
        event_type="chunk", data={"content": "test"}, raw={"content": "test"}
    )
    repr_str = repr(event)
    assert "chunk" in repr_str
    assert "VeritellStreamEvent" in repr_str
