"""Tests for SSE (Server-Sent Events) formatting helpers.

Tests the event formatting functions used for streaming responses.
"""

import json

import pytest
from langflow.agentic.helpers.sse import (
    format_complete_event,
    format_error_event,
    format_flow_update_event,
    format_progress_event,
    format_token_event,
)


class TestFormatProgressEvent:
    """Tests for format_progress_event function."""

    def test_should_format_basic_progress_event(self):
        """Should format a basic progress event correctly."""
        result = format_progress_event("generating", 1, 4)

        assert result.startswith("data: ")
        assert result.endswith("\n\n")

        json_str = result[6:-2]
        data = json.loads(json_str)

        assert data["event"] == "progress"
        assert data["step"] == "generating"
        assert data["attempt"] == 1
        assert data["max_attempts"] == 4

    def test_should_include_optional_message(self):
        """Should include optional message when provided."""
        result = format_progress_event("validating", 2, 3, message="Validating component...")

        data = json.loads(result[6:-2])

        assert data["message"] == "Validating component..."

    def test_should_include_optional_error(self):
        """Should include optional error when provided."""
        result = format_progress_event("validation_failed", 1, 3, error="SyntaxError: invalid syntax")

        data = json.loads(result[6:-2])

        assert data["error"] == "SyntaxError: invalid syntax"

    def test_should_include_optional_class_name(self):
        """Should include optional class_name when provided."""
        result = format_progress_event("validated", 1, 3, class_name="MyComponent")

        data = json.loads(result[6:-2])

        assert data["class_name"] == "MyComponent"

    def test_should_include_optional_component_code(self):
        """Should include optional component_code when provided."""
        code = "class Test(Component): pass"
        result = format_progress_event("validation_failed", 1, 3, component_code=code)

        data = json.loads(result[6:-2])

        assert data["component_code"] == code

    def test_should_include_all_optional_fields(self):
        """Should include all optional fields when provided."""
        result = format_progress_event(
            "validation_failed",
            2,
            4,
            message="Validation failed",
            error="SyntaxError",
            class_name="BrokenComponent",
            component_code="class Broken: pass",
        )

        data = json.loads(result[6:-2])

        assert data["event"] == "progress"
        assert data["step"] == "validation_failed"
        assert data["attempt"] == 2
        assert data["max_attempts"] == 4
        assert data["message"] == "Validation failed"
        assert data["error"] == "SyntaxError"
        assert data["class_name"] == "BrokenComponent"
        assert data["component_code"] == "class Broken: pass"

    def test_should_omit_none_optional_fields(self):
        """Should not include optional fields when they are None."""
        result = format_progress_event("generating", 1, 3)

        data = json.loads(result[6:-2])

        assert "message" not in data
        assert "error" not in data
        assert "class_name" not in data
        assert "component_code" not in data

    @pytest.mark.parametrize(
        "step",
        [
            "generating",
            "generation_complete",
            "extracting_code",
            "validating",
            "validated",
            "validation_failed",
            "retrying",
        ],
    )
    def test_should_accept_all_valid_step_types(self, step: str):
        """Should accept all valid step types."""
        result = format_progress_event(step, 1, 3)

        data = json.loads(result[6:-2])

        assert data["step"] == step


class TestFormatCompleteEvent:
    """Tests for format_complete_event function."""

    def test_should_format_complete_event_with_data(self):
        """Should format complete event with provided data."""
        test_data = {"result": "test", "validated": True, "class_name": "TestComponent"}
        result = format_complete_event(test_data)

        assert result.startswith("data: ")
        assert result.endswith("\n\n")

        parsed = json.loads(result[6:-2])

        assert parsed["event"] == "complete"
        assert parsed["data"] == test_data

    def test_should_format_complete_event_with_empty_data(self):
        """Should format complete event with empty data dict."""
        result = format_complete_event({})

        parsed = json.loads(result[6:-2])

        assert parsed["event"] == "complete"
        assert parsed["data"] == {}

    def test_should_preserve_nested_data_structure(self):
        """Should preserve nested data structures."""
        nested_data = {
            "result": "success",
            "metadata": {"attempts": 3, "duration": 1.5},
            "items": [1, 2, 3],
        }
        result = format_complete_event(nested_data)

        parsed = json.loads(result[6:-2])

        assert parsed["data"]["metadata"]["attempts"] == 3
        assert parsed["data"]["items"] == [1, 2, 3]


class TestFormatErrorEvent:
    """Tests for format_error_event function."""

    def test_should_format_error_event_with_message(self):
        """Should format error event with provided message."""
        result = format_error_event("Rate limit exceeded")

        assert result.startswith("data: ")
        assert result.endswith("\n\n")

        parsed = json.loads(result[6:-2])

        assert parsed["event"] == "error"
        assert parsed["message"] == "Rate limit exceeded"

    def test_should_format_error_event_with_empty_message(self):
        """Should format error event with empty message."""
        result = format_error_event("")

        parsed = json.loads(result[6:-2])

        assert parsed["event"] == "error"
        assert parsed["message"] == ""

    def test_should_preserve_special_characters_in_message(self):
        """Should preserve special characters in error message."""
        message = 'Error: "invalid" <syntax> & issues'
        result = format_error_event(message)

        parsed = json.loads(result[6:-2])

        assert parsed["message"] == message


class TestFormatTokenEvent:
    """Tests for format_token_event function."""

    def test_should_format_token_event_with_chunk(self):
        """Should format token event with provided chunk."""
        result = format_token_event("Hello")

        assert result.startswith("data: ")
        assert result.endswith("\n\n")

        parsed = json.loads(result[6:-2])

        assert parsed["event"] == "token"
        assert parsed["chunk"] == "Hello"

    def test_should_format_token_event_with_empty_chunk(self):
        """Should format token event with empty chunk."""
        result = format_token_event("")

        parsed = json.loads(result[6:-2])

        assert parsed["event"] == "token"
        assert parsed["chunk"] == ""

    def test_should_preserve_whitespace_in_chunk(self):
        """Should preserve whitespace in token chunk."""
        result = format_token_event("  hello  world  ")

        parsed = json.loads(result[6:-2])

        assert parsed["chunk"] == "  hello  world  "

    def test_should_preserve_newlines_in_chunk(self):
        """Should preserve newlines in token chunk."""
        result = format_token_event("line1\nline2\n")

        parsed = json.loads(result[6:-2])

        assert parsed["chunk"] == "line1\nline2\n"

    def test_should_handle_unicode_in_chunk(self):
        """Should handle unicode characters in chunk."""
        result = format_token_event("Hello 世界 🌍")

        parsed = json.loads(result[6:-2])

        assert parsed["chunk"] == "Hello 世界 🌍"


class TestFormatFlowUpdateEventProposePlan:
    """Contract tests for the propose_plan action flowing through
    format_flow_update_event.

    The ProposePlan MCP tool pushes ``{"action": "propose_plan", "markdown": "..."}``
    onto the flow-event queue. The assistant service drains it and feeds it to
    ``format_flow_update_event``, which spreads the dict into the SSE payload.
    The frontend then routes by ``action`` to render the plan card.

    These tests lock in the wire format so future refactors of the SSE layer
    cannot silently break the assistant's planning step.
    """  # noqa: D205

    def test_should_emit_flow_update_event_with_propose_plan_action(self):
        update = {"action": "propose_plan", "markdown": "Plan text."}

        result = format_flow_update_event(update)

        assert result.startswith("data: ")
        assert result.endswith("\n\n")
        parsed = json.loads(result[6:-2])
        assert parsed["event"] == "flow_update"
        assert parsed["action"] == "propose_plan"
        assert parsed["markdown"] == "Plan text."

    def test_should_preserve_markdown_with_newlines_as_escaped_json(self):
        # Multi-line markdown must round-trip cleanly. SSE protocol uses
        # blank lines as delimiters, so a raw newline inside the payload
        # would split the event. json.dumps must escape them.
        plan = "## Plan\n\n- Step 1\n- Step 2"
        update = {"action": "propose_plan", "markdown": plan}

        result = format_flow_update_event(update)

        json_str = result[6:-2]
        # SSE payload must be on a single line (no raw newlines mid-event).
        assert "\n" not in json_str
        parsed = json.loads(json_str)
        # But the markdown content must round-trip with newlines intact.
        assert parsed["markdown"] == plan

    def test_should_keep_event_type_when_action_is_propose_plan(self):
        # Regression guard: someone might be tempted to overwrite ``event``
        # if ``action`` is set. The SSE consumer relies on the outer event
        # type to dispatch handlers — it must stay "flow_update".
        update = {"action": "propose_plan", "markdown": "x"}

        result = format_flow_update_event(update)
        parsed = json.loads(result[6:-2])

        assert parsed["event"] == "flow_update"


class TestSSEFormatConsistency:
    """Tests for SSE format consistency across all event types."""

    def test_all_events_should_have_consistent_format(self):
        """All events should have consistent SSE format."""
        events = [
            format_progress_event("generating", 1, 3),
            format_complete_event({"result": "test"}),
            format_error_event("error"),
            format_token_event("chunk"),
        ]

        for event in events:
            assert event.startswith("data: ")
            assert event.endswith("\n\n")
            json_str = event[6:-2]
            parsed = json.loads(json_str)
            assert "event" in parsed

    def test_events_should_produce_valid_json(self):
        """All events should produce valid JSON."""
        test_cases = [
            format_progress_event("validating", 2, 4, message="Testing"),
            format_complete_event({"complex": {"nested": [1, 2, 3]}}),
            format_error_event("Test error with 'quotes' and \"double quotes\""),
            format_token_event("Token with special chars: <>&"),
        ]

        for event in test_cases:
            json_str = event[6:-2]
            json.loads(json_str)


class TestBugsAndEdgeCases:
    """Tests that challenge the code — exposing real bugs and untested edge cases."""

    def test_newlines_in_values_are_json_escaped(self):
        """Newlines in SSE field values must be JSON-escaped to not break SSE protocol.

        SSE uses newline-newline as delimiter. json.dumps escapes newlines, so the
        raw payload between 'data: ' prefix and final newlines is valid single-line JSON.
        """
        malicious_error = 'error\n\ndata: {"event":"fake_complete"}\n\n'
        result = format_error_event(malicious_error)

        # The JSON payload (between "data: " and final "\n\n") must be on one line
        json_str = result[6:-2]
        assert "\n" not in json_str  # No raw newlines inside the JSON
        parsed = json.loads(json_str)
        assert parsed["message"] == malicious_error

    def test_non_serializable_data_crashes_without_handling(self):
        """format_complete_event crashes on non-JSON-serializable data.

        No try-except around json.dumps — TypeError propagates to caller.
        """
        from datetime import datetime, timezone

        with pytest.raises(TypeError):
            format_complete_event({"timestamp": datetime.now(tz=timezone.utc)})

    def test_negative_attempt_accepted_without_validation(self):
        """Negative attempt/max_attempts are accepted silently — no input validation."""
        result = format_progress_event("generating", -1, -5)
        data = json.loads(result[6:-2])
        assert data["attempt"] == -1
        assert data["max_attempts"] == -5

    def test_attempt_greater_than_max_accepted(self):
        """Attempt > max_attempts is accepted silently — no consistency check."""
        result = format_progress_event("generating", 10, 3)
        data = json.loads(result[6:-2])
        assert data["attempt"] == 10
        assert data["max_attempts"] == 3

    def test_format_cancelled_event_structure(self):
        """format_cancelled_event should return well-formed SSE event."""
        from langflow.agentic.helpers.sse import format_cancelled_event

        result = format_cancelled_event()
        assert result.startswith("data: ")
        assert result.endswith("\n\n")
        parsed = json.loads(result[6:-2])
        assert parsed["event"] == "cancelled"
