# pyright: reportPrivateUsage=false
"""Unit tests: stream_json_events — sync SSE transport with fake session."""

import json
from collections.abc import Iterator, Mapping
from typing import Any
from unittest.mock import MagicMock

import pytest

from seaart._transport_sse import SSEEvent, stream_json_events
from seaart.exceptions import APIError

# Type alias for the parsed item returned by _identity_parser.
_ParsedItem = tuple[str, dict[str, Any] | None]


# ── Fake response ───────────────────────────────────────────────────────────


class FakeResponse:
    def __init__(
        self,
        status_code: int = 200,
        headers: dict[str, str] | None = None,
        chunks: list[bytes] | None = None,
    ) -> None:
        self.status_code = status_code
        self.headers: dict[str, str] = headers or {}
        self._chunks: list[bytes] = chunks or []
        self.closed: bool = False

    def iter_content(self) -> Iterator[bytes]:
        yield from self._chunks

    def close(self) -> None:
        self.closed = True


def _sse_bytes(event: str, data: dict[str, Any]) -> bytes:
    """Build raw SSE bytes for a single event."""
    return f"event: {event}\ndata: {json.dumps(data)}\n\n".encode()


def _identity_parser(ev: SSEEvent, obj: dict[str, Any] | None) -> _ParsedItem:
    """Passthrough parser returning (event_type, parsed_obj)."""
    return (ev.event, obj)


# ── Happy path ──────────────────────────────────────────────────────────────


class TestSyncStreamHappyPath:
    def test_single_event(self) -> None:
        resp = FakeResponse(chunks=[_sse_bytes("chunk", {"text": "hello"})])
        session_request = MagicMock(return_value=resp)

        items: list[_ParsedItem] = list(stream_json_events(
            session_request=session_request,
            method="POST", url="https://example.com/stream",
            headers={}, params=None, json_body=None, data=None,
            timeout=10.0, parser=_identity_parser,
        ))

        assert len(items) == 1
        assert items[0][0] == "chunk"
        assert items[0][1] == {"text": "hello"}

    def test_multiple_events(self) -> None:
        raw_chunks: list[bytes] = [
            _sse_bytes("chunk", {"i": 1}),
            _sse_bytes("chunk", {"i": 2}),
            _sse_bytes("end", {"done": True}),
        ]
        resp = FakeResponse(chunks=raw_chunks)
        session_request = MagicMock(return_value=resp)

        items: list[_ParsedItem] = list(stream_json_events(
            session_request=session_request,
            method="POST", url="https://example.com/stream",
            headers={}, params=None, json_body=None, data=None,
            timeout=10.0, parser=_identity_parser,
        ))

        assert len(items) == 3
        assert items[2][0] == "end"

    def test_response_closed_after_iteration(self) -> None:
        resp = FakeResponse(chunks=[_sse_bytes("chunk", {"x": 1})])
        session_request = MagicMock(return_value=resp)

        list(stream_json_events(
            session_request=session_request,
            method="POST", url="https://example.com/stream",
            headers={}, params=None, json_body=None, data=None,
            timeout=10.0, parser=_identity_parser,
        ))

        assert resp.closed

    def test_multiple_events_in_one_chunk(self) -> None:
        combined = _sse_bytes("chunk", {"a": 1}) + _sse_bytes("chunk", {"b": 2})
        resp = FakeResponse(chunks=[combined])
        session_request = MagicMock(return_value=resp)

        items: list[_ParsedItem] = list(stream_json_events(
            session_request=session_request,
            method="POST", url="https://example.com/stream",
            headers={}, params=None, json_body=None, data=None,
            timeout=10.0, parser=_identity_parser,
        ))

        assert len(items) == 2


# ── Error handling ──────────────────────────────────────────────────────────


class TestSyncStreamErrors:
    def test_http_error_raises_api_error(self) -> None:
        resp = FakeResponse(status_code=500, chunks=[])
        session_request = MagicMock(return_value=resp)

        with pytest.raises(APIError):
            list(stream_json_events(
                session_request=session_request,
                method="POST", url="https://example.com/stream",
                headers={}, params=None, json_body=None, data=None,
                timeout=10.0, parser=_identity_parser,
            ))

    def test_invalid_json_skipped(self) -> None:
        bad_chunk = b"event: chunk\ndata: not-json\n\n"
        good_chunk = _sse_bytes("chunk", {"ok": True})
        resp = FakeResponse(chunks=[bad_chunk, good_chunk])
        session_request = MagicMock(return_value=resp)

        items: list[_ParsedItem] = list(stream_json_events(
            session_request=session_request,
            method="POST", url="https://example.com/stream",
            headers={}, params=None, json_body=None, data=None,
            timeout=10.0, parser=_identity_parser,
        ))

        good_items = [i for i in items if i[1] is not None]
        assert len(good_items) == 1
        assert good_items[0][1] == {"ok": True}

    def test_done_marker_skipped(self) -> None:
        done_chunk = b"data: [DONE]\n\n"
        real_chunk = _sse_bytes("chunk", {"x": 1})
        resp = FakeResponse(chunks=[real_chunk, done_chunk])
        session_request = MagicMock(return_value=resp)

        items: list[_ParsedItem] = list(stream_json_events(
            session_request=session_request,
            method="POST", url="https://example.com/stream",
            headers={}, params=None, json_body=None, data=None,
            timeout=10.0, parser=_identity_parser,
        ))

        assert len(items) == 1

    def test_parser_error_skipped(self) -> None:
        resp = FakeResponse(chunks=[
            _sse_bytes("chunk", {"x": 1}),
            _sse_bytes("chunk", {"x": 2}),
        ])
        session_request = MagicMock(return_value=resp)
        call_count = 0

        def failing_parser(ev: SSEEvent, obj: dict[str, Any] | None) -> _ParsedItem:
            nonlocal call_count
            call_count += 1
            if call_count == 1:
                raise ValueError("parse fail")
            return (ev.event, obj)

        items: list[_ParsedItem] = list(stream_json_events(
            session_request=session_request,
            method="POST", url="https://example.com/stream",
            headers={}, params=None, json_body=None, data=None,
            timeout=10.0, parser=failing_parser,
        ))

        assert len(items) == 1
        assert items[0][1] == {"x": 2}


# ── Request forwarding ──────────────────────────────────────────────────────


class TestSyncStreamRequestForwarding:
    def test_passes_method_url_headers(self) -> None:
        resp = FakeResponse(chunks=[])
        session_request = MagicMock(return_value=resp)

        headers: dict[str, str] = {"Authorization": "Bearer token"}
        list(stream_json_events(
            session_request=session_request,
            method="POST", url="https://api.example.com/v1/chat",
            headers=headers, params=None,
            json_body={"prompt": "test"}, data=None,
            timeout=30.0, parser=_identity_parser,
        ))

        session_request.assert_called_once()
        call_args: Any = session_request.call_args
        assert call_args[0][0] == "POST"
        assert call_args[0][1] == "https://api.example.com/v1/chat"
        assert call_args[1]["headers"] == headers
        assert call_args[1]["json"] == {"prompt": "test"}
        assert call_args[1]["timeout"] == 30.0
        assert call_args[1]["stream"] is True

    def test_headers_forwarded_to_events(self) -> None:
        resp = FakeResponse(
            headers={"x-request-id": "req-123"},
            chunks=[_sse_bytes("chunk", {"x": 1})],
        )
        session_request = MagicMock(return_value=resp)

        def header_parser(ev: SSEEvent, obj: dict[str, Any] | None) -> Mapping[str, str] | None:
            return ev.headers

        items: list[Mapping[str, str] | None] = list(stream_json_events(
            session_request=session_request,
            method="POST", url="https://example.com/stream",
            headers={}, params=None, json_body=None, data=None,
            timeout=10.0, parser=header_parser,
        ))

        assert len(items) == 1
        assert items[0] is not None
        assert items[0]["x-request-id"] == "req-123"
