from __future__ import annotations

import json
import os
import unittest
import uuid
from unittest import mock

import httpx

from app.livekit.adapters.agent_backend import BackendReply
from app.livekit.adapters.backend_factory import build_agent_backend
from app.livekit.adapters.remote_agent_sse_adapter import RemoteAgentSSEAdapter


def _assert_uuid(testcase: unittest.TestCase, value: object) -> str:
    text = str(value or "")
    testcase.assertEqual(str(uuid.UUID(text)), text)
    return text


def _sse_event(event_name: str, data: object) -> list[str]:
    return [
        f"event: {event_name}",
        f"data: {json.dumps(data)}",
        "",
    ]


class _FakeStreamResponse:
    def __init__(self, lines, *, method: str, url: str) -> None:
        self._lines = list(lines)
        self.request = httpx.Request(method, url)
        self.status_code = 200
        self.closed = False

    def raise_for_status(self) -> None:
        return None

    async def aiter_lines(self):
        for line in self._lines:
            yield line

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


class _FakeStreamContextManager:
    def __init__(self, response: _FakeStreamResponse) -> None:
        self._response = response

    async def __aenter__(self) -> _FakeStreamResponse:
        return self._response

    async def __aexit__(self, exc_type, exc, tb) -> bool:
        await self._response.aclose()
        return False


class _FakeAsyncClientFactory:
    def __init__(self, responses) -> None:
        self._responses = [list(response) for response in responses]
        self.stream_calls: list[dict] = []
        self.client_kwargs: list[dict] = []

    def build(self, *args, **kwargs):
        self.client_kwargs.append(dict(kwargs))
        return _FakeAsyncClient(self)


class _FakeAsyncClient:
    def __init__(self, factory: _FakeAsyncClientFactory) -> None:
        self._factory = factory
        self.closed = False

    async def __aenter__(self):
        return self

    async def __aexit__(self, exc_type, exc, tb) -> bool:
        await self.aclose()
        return False

    def stream(self, method: str, url: str, **kwargs):
        if not self._factory._responses:
            raise AssertionError("No fake SSE response configured")

        lines = self._factory._responses.pop(0)
        self._factory.stream_calls.append(
            {
                "method": method,
                "url": url,
                "params": dict(kwargs.get("params") or {}),
                "headers": dict(kwargs.get("headers") or {}),
                "json": kwargs.get("json"),
            }
        )
        return _FakeStreamContextManager(
            _FakeStreamResponse(lines, method=method, url=url)
        )

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


class _FakeTimeline:
    def __init__(self) -> None:
        self.events: list[tuple[str, dict]] = []

    def emit(self, event: str, **fields) -> None:
        self.events.append((event, dict(fields)))


class RemoteAgentSSEAdapterTests(unittest.IsolatedAsyncioTestCase):
    async def test_prepare_captures_session_and_post_reads_own_short_stream(self) -> None:
        factory = _FakeAsyncClientFactory(
            responses=[
                [
                    *_sse_event(
                        "ready",
                        {
                            "message": "Posso verificar sua fatura.",
                            "actions": ["chat", "ping"],
                            "session_id": "sessao-123",
                            "speech_id": "11111111-1111-4111-8111-111111111111",
                            "is_interruptible": False,
                            "metadata": {"wait_timeout_seconds": 30},
                        },
                    ),
                    *_sse_event("prefetch_done", {"session_id": "sessao-123"}),
                ],
                [
                    *_sse_event(
                        "ready",
                        {
                            "message": "Processando.",
                            "actions": ["chat", "ping"],
                            "session_id": "sessao-123",
                        },
                    ),
                    *_sse_event(
                        "result",
                        {
                            "type": "result",
                            "action": "chat",
                            "result": {
                                "type": "final",
                                "content": "A variacao entre a fatura atual e a anterior pode ocorrer por alguns motivos comuns.",
                                "speech_id": "7f3a0000-0000-4000-8000-000000000000",
                                "is_interruptible": True,
                                "tool_calls": [],
                            },
                        },
                    ),
                ],
            ]
        )

        with mock.patch(
            "app.livekit.adapters.remote_agent_sse_adapter.httpx.AsyncClient",
            side_effect=factory.build,
        ):
            adapter = RemoteAgentSSEAdapter(
                intro="oi",
                url="http://agent.internal/agent/sse",
                request_context={
                    "agent": "conta",
                    "RouterCallKeyDay": "20260329",
                    "RouterCallKey": "0005",
                    "ANI": "3133338888",
                    "GSM": "551199993339999",
                    "ID_FATURA": "fat-555",
                    "callIdGed": "GED-555",
                    "protocol_id": "PRT-555",
                    "sessionId": "call-session-555",
                },
            )
            await adapter.prepare(True, "PRT-555")

            ready_reply = await adapter.run("")
            ready_message_id = _assert_uuid(self, ready_reply.metadata["message_id"])
            self.assertEqual(
                ready_reply,
                BackendReply(
                    stage="PRESENTATION",
                    text="Posso verificar sua fatura.",
                    done=False,
                    export_payload=None,
                    metadata={
                        "message_id": ready_message_id,
                        "wait_timeout_seconds": 30,
                        "speech_id": "11111111-1111-4111-8111-111111111111",
                        "is_interruptible": False,
                        "agent_message_type": "ready",
                        "expects_user_response": True,
                        "drop_user_input_while_speaking": True,
                    },
                ),
            )
            self.assertEqual(len(factory.stream_calls), 1)

            get_call = factory.stream_calls[0]
            self.assertEqual(get_call["method"], "GET")
            self.assertEqual(get_call["url"], "http://agent.internal/agent/sse")
            self.assertEqual(
                get_call["params"],
                {
                    "msisdn": "551199993339999",
                    "invoice_id": "fat-555",
                    "ani": "3133338888",
                    "protocol_id": "PRT-555",
                    "session_id": "call-session-555",
                    "message_id": ready_message_id,
                    "channelId": "ura",
                    "uraCallId": "GED-555",
                },
            )

            await adapter.set_processing_interruption(
                listened_text="pode repetir",
                skipped=False,
                speech_id="11111111-1111-4111-8111-111111111111",
            )
            await adapter.inject_idle_nudge("Alo, voce esta ai?")

            reply = await adapter.run("teste")
            reply_message_id = _assert_uuid(self, reply.metadata["message_id"])
            self.assertNotEqual(reply_message_id, ready_message_id)
            self.assertEqual(
                reply,
                BackendReply(
                    stage="PRESENTATION",
                    text="A variacao entre a fatura atual e a anterior pode ocorrer por alguns motivos comuns.",
                    done=False,
                    export_payload={
                        "type": "final",
                        "content": "A variacao entre a fatura atual e a anterior pode ocorrer por alguns motivos comuns.",
                        "speech_id": "7f3a0000-0000-4000-8000-000000000000",
                        "is_interruptible": True,
                        "tool_calls": [],
                    },
                    metadata={
                        "message_id": reply_message_id,
                        "speech_id": "7f3a0000-0000-4000-8000-000000000000",
                        "is_interruptible": True,
                        "agent_message_type": "result",
                        "agent_result_type": "final",
                        "expects_user_response": True,
                        "drop_user_input_while_speaking": False,
                    },
                ),
            )

            post_call = factory.stream_calls[1]
            self.assertEqual(post_call["method"], "POST")
            self.assertEqual(post_call["url"], "http://agent.internal/agent/sse")
            self.assertEqual(
                post_call["params"],
                {
                    "session_id": "sessao-123",
                    "ani": "3133338888",
                    "protocol_id": "PRT-555",
                    "message_id": reply_message_id,
                    "channelId": "ura",
                    "uraCallId": "GED-555",
                },
            )
            self.assertNotEqual(post_call["params"]["message_id"], post_call["params"]["session_id"])
            self.assertEqual(adapter._remote_session_id, "sessao-123")
            self.assertEqual(post_call["json"]["action"], "chat")
            self.assertEqual(post_call["json"]["payload"]["message"], "teste")
            self.assertEqual(
                post_call["json"]["payload"]["message_id"],
                reply_message_id,
            )
            self.assertNotIn("msisdn", post_call["json"]["payload"])
            self.assertNotIn("invoice_id", post_call["json"]["payload"])
            self.assertEqual(
                post_call["json"]["payload"]["processing_interruption"],
                {
                    "speech_id": "11111111-1111-4111-8111-111111111111",
                    "heard_text": "pode repetir",
                },
            )
            self.assertNotIn("speech_interruption", post_call["json"]["payload"])
            self.assertNotIn("interruption", post_call["json"]["payload"])
            self.assertEqual(
                post_call["json"]["payload"]["events"],
                [{"type": "idle_nudge", "text": "Alo, voce esta ai?"}],
            )
            self.assertFalse(adapter.supports_server_push())

    async def test_conta_feedback_is_buffered_and_run_waits_for_top_level_final(self) -> None:
        feedback_payload = {
            "type": "feedback",
            "text": "Ainda estou consultando sua fatura.",
        }
        factory = _FakeAsyncClientFactory(
            responses=[
                [
                    *_sse_event(
                        "ready",
                        {
                            "message": "Posso verificar sua fatura.",
                            "actions": ["chat", "ping"],
                            "session_id": "sessao-125",
                        },
                    ),
                    *_sse_event("prefetch_done", {"session_id": "sessao-125"}),
                ],
                [
                    *_sse_event("message", feedback_payload),
                    *_sse_event(
                        "message",
                        {
                            "type": "final",
                            "content": "Encontrei os detalhes da sua fatura.",
                        },
                    ),
                ],
            ]
        )

        with mock.patch(
            "app.livekit.adapters.remote_agent_sse_adapter.httpx.AsyncClient",
            side_effect=factory.build,
        ):
            adapter = RemoteAgentSSEAdapter(
                intro="oi",
                url="http://agent.internal/agent/sse",
                request_context={
                    "agent": "conta",
                    "GSM": "551199993339999",
                    "ID_FATURA": "fat-557",
                },
            )
            await adapter.prepare(True, "PRT-557")
            await adapter.run("")

            reply = await adapter.run("teste")
            feedback_reply = await adapter.wait_for_server_push()
            reply_message_id = _assert_uuid(self, reply.metadata["message_id"])

            self.assertEqual(
                reply,
                BackendReply(
                    stage="PRESENTATION",
                    text="Encontrei os detalhes da sua fatura.",
                    done=False,
                    export_payload=None,
                    metadata={
                        "message_id": reply_message_id,
                        "agent_message_type": "final",
                        "expects_user_response": True,
                        "drop_user_input_while_speaking": False,
                    },
                ),
            )
            self.assertEqual(
                feedback_reply,
                BackendReply(
                    stage="PRESENTATION",
                    text="Ainda estou consultando sua fatura.",
                    done=False,
                    export_payload=None,
                    metadata={
                        "message_id": reply_message_id,
                        "agent_message_type": "feedback",
                        "expects_user_response": False,
                        "drop_user_input_while_speaking": True,
                        "is_interruptible": False,
                        "event": "feedback",
                        "payload": feedback_payload,
                    },
                ),
            )

    async def test_run_marks_expected_agent_result_type_as_done(self) -> None:
        factory = _FakeAsyncClientFactory(
            responses=[
                [
                    *_sse_event(
                        "ready",
                        {
                            "message": "Posso verificar sua fatura.",
                            "actions": ["chat", "ping"],
                            "session_id": "sessao-124",
                        },
                    ),
                    *_sse_event("prefetch_done", {"session_id": "sessao-124"}),
                ],
                [
                    *_sse_event(
                        "result",
                        {
                            "type": "result",
                            "action": "chat",
                            "result": {
                                "type": "erro_no_match",
                                "content": "Atendimento encerrado por falta de correspondencia.",
                                "tool_calls": [],
                            },
                        },
                    ),
                ],
            ]
        )

        with mock.patch(
            "app.livekit.adapters.remote_agent_sse_adapter.httpx.AsyncClient",
            side_effect=factory.build,
        ):
            adapter = RemoteAgentSSEAdapter(
                intro="oi",
                url="http://agent.internal/agent/sse",
                request_context={
                    "agent": "conta",
                    "GSM": "551199993339999",
                    "ID_FATURA": "fat-556",
                },
            )
            await adapter.prepare(True, "PRT-556")
            await adapter.run("")

            reply = await adapter.run("teste")
            reply_message_id = _assert_uuid(self, reply.metadata["message_id"])

            self.assertEqual(
                reply,
                BackendReply(
                    stage="DONE",
                    text="Atendimento encerrado por falta de correspondencia.",
                    done=True,
                    export_payload={
                        "type": "erro_no_match",
                        "content": "Atendimento encerrado por falta de correspondencia.",
                        "tool_calls": [],
                    },
                    metadata={
                        "message_id": reply_message_id,
                        "agent_message_type": "result",
                        "agent_result_type": "erro_no_match",
                        "expects_user_response": False,
                        "drop_user_input_while_speaking": True,
                        "is_interruptible": False,
                    },
                ),
            )

    async def test_conta_result_feedback_is_buffered_and_run_waits_for_final(self) -> None:
        feedback_result = {
            "type": "feedback",
            "content": "Perfeito, seguiremos com o cancelamento. Aguarde um instante.",
            "speech_id": "332a02d1-031d-4d08-b14a-f896fe630fbc",
            "is_interruptible": True,
            "tool_calls": [],
            "metadata": {"stage": "feedback"},
        }
        feedback_payload = {
            "type": "result",
            "action": "chat",
            "result": feedback_result,
        }
        factory = _FakeAsyncClientFactory(
            responses=[
                [
                    *_sse_event(
                        "ready",
                        {
                            "message": "Posso verificar sua fatura.",
                            "actions": ["chat", "ping"],
                            "session_id": "sessao-126",
                        },
                    ),
                    *_sse_event("prefetch_done", {"session_id": "sessao-126"}),
                ],
                [
                    *_sse_event(
                        "result",
                        feedback_payload,
                    ),
                    *_sse_event(
                        "result",
                        {
                            "type": "result",
                            "action": "chat",
                            "result": {
                                "type": "final",
                                "content": "Cancelamento concluido.",
                                "tool_calls": [],
                            },
                        },
                    ),
                ],
            ]
        )

        with mock.patch(
            "app.livekit.adapters.remote_agent_sse_adapter.httpx.AsyncClient",
            side_effect=factory.build,
        ):
            adapter = RemoteAgentSSEAdapter(
                intro="oi",
                url="http://agent.internal/agent/sse",
                request_context={
                    "agent": "conta",
                    "GSM": "551199993339999",
                    "ID_FATURA": "fat-558",
                },
            )
            await adapter.prepare(True, "PRT-558")
            await adapter.run("")

            reply = await adapter.run("correto")
            feedback_reply = await adapter.wait_for_server_push()
            reply_message_id = _assert_uuid(self, reply.metadata["message_id"])

            self.assertEqual(
                reply,
                BackendReply(
                    stage="PRESENTATION",
                    text="Cancelamento concluido.",
                    done=False,
                    export_payload={
                        "type": "final",
                        "content": "Cancelamento concluido.",
                        "tool_calls": [],
                    },
                    metadata={
                        "message_id": reply_message_id,
                        "agent_message_type": "result",
                        "agent_result_type": "final",
                        "expects_user_response": True,
                        "drop_user_input_while_speaking": False,
                    },
                ),
            )
            self.assertEqual(
                feedback_reply,
                BackendReply(
                    stage="PRESENTATION",
                    text="Perfeito, seguiremos com o cancelamento. Aguarde um instante.",
                    done=False,
                    export_payload=None,
                    metadata={
                        "message_id": reply_message_id,
                        "speech_id": "332a02d1-031d-4d08-b14a-f896fe630fbc",
                        "is_interruptible": False,
                        "agent_message_type": "result",
                        "agent_result_type": "feedback",
                        "expects_user_response": False,
                        "drop_user_input_while_speaking": True,
                        "event": "feedback",
                        "payload": feedback_payload,
                    },
                ),
            )

    async def test_prepare_stops_after_ready_even_if_stream_has_late_error(self) -> None:
        factory = _FakeAsyncClientFactory(
            responses=[
                [
                    *_sse_event(
                        "ready",
                        {
                            "message": "Posso verificar sua fatura.",
                            "actions": ["chat"],
                            "session_id": "sessao-999",
                        },
                    ),
                    *_sse_event("error", {"error": "late stream error after ready"}),
                ]
            ]
        )

        with mock.patch(
            "app.livekit.adapters.remote_agent_sse_adapter.httpx.AsyncClient",
            side_effect=factory.build,
        ):
            adapter = RemoteAgentSSEAdapter(
                intro="oi",
                url="http://agent.internal/agent/sse",
                request_context={
                    "agent": "conta",
                    "GSM": "5511999900000",
                    "ID_FATURA": "fat-999",
                },
            )

            await adapter.prepare(True, "ignored-protocol")
            ready_reply = await adapter.run("")
            ready_message_id = _assert_uuid(self, ready_reply.metadata["message_id"])

            self.assertEqual(
                ready_reply,
                BackendReply(
                    stage="PRESENTATION",
                    text="Posso verificar sua fatura.",
                    done=False,
                    export_payload=None,
                    metadata={
                        "message_id": ready_message_id,
                        "agent_message_type": "ready",
                        "expects_user_response": True,
                        "drop_user_input_while_speaking": True,
                        "is_interruptible": False,
                    },
                ),
            )
            self.assertEqual(adapter._remote_session_id, "sessao-999")
            self.assertEqual(len(factory.stream_calls), 1)
            self.assertEqual(factory.stream_calls[0]["method"], "GET")
            self.assertEqual(
                factory.stream_calls[0]["params"],
                {
                    "msisdn": "5511999900000",
                    "invoice_id": "fat-999",
                    "message_id": ready_message_id,
                    "channelId": "ura",
                },
            )

    async def test_run_accumulates_progress_chunks_until_done(self) -> None:
        factory = _FakeAsyncClientFactory(
            responses=[
                [
                    *_sse_event(
                        "ready",
                        {
                            "message": "Posso ajudar.",
                            "actions": ["chat"],
                            "session_id": "PRT-777",
                        },
                    )
                ],
                [
                    *_sse_event("progress", {"delta": "Oferta "}),
                    *_sse_event("progress", {"delta": "liberada"}),
                    *_sse_event("result", {"stage": "DONE"}),
                ],
            ]
        )

        with mock.patch(
            "app.livekit.adapters.remote_agent_sse_adapter.httpx.AsyncClient",
            side_effect=factory.build,
        ):
            adapter = RemoteAgentSSEAdapter(
                intro="oi",
                url="http://agent.internal/agent/sse",
                request_context={
                    "agent": "conta",
                    "GSM": "5511999900000",
                },
            )
            await adapter.prepare(True, "PRT-777")
            await adapter.run("")

            reply = await adapter.run("teste")
            reply_message_id = _assert_uuid(self, reply.metadata["message_id"])
            self.assertEqual(
                reply,
                BackendReply(
                    stage="DONE",
                    text="Oferta liberada",
                    done=True,
                    export_payload={"stage": "DONE", "type": "result"},
                    metadata={
                        "message_id": reply_message_id,
                        "agent_message_type": "result",
                        "agent_result_type": "result",
                        "expects_user_response": False,
                        "drop_user_input_while_speaking": True,
                        "is_interruptible": False,
                    },
                ),
            )

    async def test_end_service_once_skips_end_action_when_backend_does_not_advertise_it(self) -> None:
        factory = _FakeAsyncClientFactory(
            responses=[
                [
                    *_sse_event(
                        "ready",
                        {
                            "message": "Posso verificar sua fatura.",
                            "actions": ["chat", "ping"],
                            "session_id": "PRT-559",
                        },
                    )
                ]
            ]
        )

        with mock.patch(
            "app.livekit.adapters.remote_agent_sse_adapter.httpx.AsyncClient",
            side_effect=factory.build,
        ):
            adapter = RemoteAgentSSEAdapter(
                intro="oi",
                url="http://agent.internal/agent/sse",
                request_context={
                    "agent": "conta",
                    "GSM": "5511999900003",
                },
            )
            await adapter.prepare(True, "PRT-559")
            ready_reply = await adapter.run("")
            self.assertEqual(ready_reply.text, "Posso verificar sua fatura.")

            end_reply = await adapter.end_service_once()
            self.assertEqual(
                end_reply,
                BackendReply(
                    stage="DONE",
                    text="",
                    done=True,
                    export_payload=[],
                ),
            )
            self.assertEqual(len(factory.stream_calls), 1)

    async def test_oferta_initial_turn_posts_execute_and_streams_sse_events(self) -> None:
        factory = _FakeAsyncClientFactory(
            responses=[
                [
                    *_sse_event(
                        "schedule_message",
                        {
                            "scheduledMessage": {
                                "timeInSeconds": 5,
                                "message": "Aguarde, estou consultando sua oferta.",
                            }
                        },
                    ),
                    *_sse_event(
                        "message",
                        {
                            "response": "Tenho uma oferta disponivel para voce.",
                            "sessionId": "remote-session-001",
                            "additionalInformations": {"foo": "bar"},
                        },
                    ),
                    *_sse_event(
                        "done",
                        {
                            "status": "completed",
                            "additionalInformations": {"service_status": "RESOLVED"},
                        },
                    ),
                ],
            ]
        )

        with mock.patch(
            "app.livekit.adapters.remote_agent_sse_adapter.httpx.AsyncClient",
            side_effect=factory.build,
        ):
            adapter = RemoteAgentSSEAdapter(
                intro="oi",
                url="http://agent.internal/agent/execute",
                request_context={
                    "agent": "oferta",
                    "RouterCallKeyDay": "20260329",
                    "RouterCallKey": "0005",
                    "ANI": "3133338888",
                    "GSM": "551199993339999",
                    "callIdGed": "GED-555",
                    "protocolo": "PRT-555",
                    "assetId": "asset-555",
                    "channelId": "ura",
                },
            )
            await adapter.prepare(True, "PRT-555")

            first_reply = await adapter.run("")
            message_id = _assert_uuid(self, first_reply.metadata["message_id"])
            self.assertEqual(
                first_reply,
                BackendReply(
                    stage="PRESENTATION",
                    text="Aguarde, estou consultando sua oferta.",
                    done=False,
                    export_payload=None,
                    metadata={
                        "message_id": message_id,
                        "event": "schedule_message",
                        "scheduledMessage": {
                            "timeInSeconds": 5,
                            "message": "Aguarde, estou consultando sua oferta.",
                        },
                        "payload": {
                            "scheduledMessage": {
                                "timeInSeconds": 5,
                                "message": "Aguarde, estou consultando sua oferta.",
                            },
                            "type": "schedule_message",
                        },
                    },
                ),
            )

            message_reply = await adapter.wait_for_server_push()
            self.assertEqual(
                message_reply,
                BackendReply(
                    stage="PRESENTATION",
                    text="Tenho uma oferta disponivel para voce.",
                    done=False,
                    export_payload=None,
                    metadata={
                        "message_id": message_id,
                        "event": "message",
                        "payload": {
                            "response": "Tenho uma oferta disponivel para voce.",
                            "sessionId": "remote-session-001",
                            "additionalInformations": {"foo": "bar"},
                            "type": "message",
                        },
                        "sessionId": "remote-session-001",
                        "additionalInformations": {"foo": "bar"},
                    },
                ),
            )

            done_reply = await adapter.wait_for_server_push()
            self.assertEqual(done_reply.stage, "DONE")
            self.assertTrue(done_reply.done)
            self.assertEqual(done_reply.export_payload["type"], "resolvido")
            self.assertEqual(adapter._remote_session_id, "remote-session-001")

            self.assertEqual(len(factory.stream_calls), 1)
            post_call = factory.stream_calls[0]
            self.assertEqual(post_call["method"], "POST")
            self.assertEqual(post_call["url"], "http://agent.internal/agent/execute")
            self.assertEqual(post_call["params"], {})
            self.assertEqual(post_call["headers"]["Channel-id"], "ura")
            self.assertEqual(post_call["json"]["message"], "inicio_atendimento")
            self.assertEqual(post_call["json"]["messageId"], message_id)
            self.assertEqual(
                post_call["json"]["context"],
                {
                    "protocolNumber": "PRT-555",
                    "protocolo": "PRT-555",
                    "gsm": "551199993339999",
                    "uraId": "GED-555",
                    "callIdGed": "GED-555",
                    "ani": "3133338888",
                    "routerCallKey": "0005",
                    "routerCallKeyDay": "20260329",
                    "agent": "oferta",
                    "assetId": "asset-555",
                },
            )
            self.assertNotIn("sessionId", post_call["json"]["context"])

    async def test_oferta_forwards_only_explicit_session_id(self) -> None:
        factory = _FakeAsyncClientFactory(
            responses=[
                [
                    *_sse_event(
                        "done",
                        {
                            "status": "completed",
                            "additionalInformations": {"service_status": "UNRESOLVED"},
                        },
                    )
                ],
            ]
        )

        with mock.patch(
            "app.livekit.adapters.remote_agent_sse_adapter.httpx.AsyncClient",
            side_effect=factory.build,
        ):
            adapter = RemoteAgentSSEAdapter(
                intro="oi",
                url="http://agent.internal/agent/execute",
                request_context={
                    "agent": "oferta",
                    "GSM": "551199993339999",
                    "callIdGed": "GED-556",
                    "protocolo": "PRT-556",
                    "sessionId": "session-556",
                },
            )
            await adapter.prepare(True, "PRT-556")
            reply = await adapter.run("quero oferta")

            self.assertEqual(reply.stage, "PRESENTATION")
            self.assertFalse(reply.done)
            self.assertEqual(reply.export_payload["type"], "unresolved")
            self.assertEqual(
                factory.stream_calls[0]["json"]["context"]["sessionId"],
                "session-556",
            )
            self.assertEqual(factory.stream_calls[0]["json"]["message"], "quero oferta")

    async def test_oferta_ignores_removed_test_session_id_env(self) -> None:
        factory = _FakeAsyncClientFactory(
            responses=[
                [
                    *_sse_event(
                        "done",
                        {
                            "status": "completed",
                            "additionalInformations": {"service_status": "UNRESOLVED"},
                        },
                    )
                ],
            ]
        )

        with mock.patch.dict(
            os.environ,
            {"REMOTE_AGENT_OFERTA_TEST_SESSION_ID": "dev-test-session"},
        ), mock.patch(
            "app.livekit.adapters.remote_agent_sse_adapter.httpx.AsyncClient",
            side_effect=factory.build,
        ):
            adapter = RemoteAgentSSEAdapter(
                intro="oi",
                url="http://agent.internal/agent/execute",
                request_context={
                    "agent": "oferta",
                    "GSM": "551199993339999",
                    "callIdGed": "GED-556",
                    "protocolo": "PRT-556",
                },
            )
            await adapter.prepare(True, "PRT-556")
            await adapter.run("quero oferta")

        self.assertNotIn("sessionId", factory.stream_calls[0]["json"]["context"])

    async def test_oferta_can_disable_tls_verification_by_env(self) -> None:
        factory = _FakeAsyncClientFactory(
            responses=[
                [
                    *_sse_event(
                        "done",
                        {
                            "status": "completed",
                            "additionalInformations": {"service_status": "UNRESOLVED"},
                        },
                    )
                ],
            ]
        )

        with mock.patch.dict(
            os.environ,
            {"REMOTE_AGENT_SSE_TLS_VERIFY_OFERTA": "0"},
        ), mock.patch(
            "app.livekit.adapters.remote_agent_sse_adapter.httpx.AsyncClient",
            side_effect=factory.build,
        ):
            adapter = RemoteAgentSSEAdapter(
                intro="oi",
                url="http://agent.internal/agent/execute",
                request_context={
                    "agent": "oferta",
                    "GSM": "551199993339999",
                    "callIdGed": "GED-556",
                    "protocolo": "PRT-556",
                },
            )
            await adapter.prepare(True, "PRT-556")
            await adapter.run("quero oferta")

        self.assertEqual(factory.client_kwargs[0]["verify"], False)

    async def test_oferta_timeline_records_sse_event_without_emit_collision(self) -> None:
        factory = _FakeAsyncClientFactory(
            responses=[
                [
                    *_sse_event(
                        "schedule_message",
                        {
                            "scheduledMessage": {
                                "timeInSeconds": 5,
                                "message": "Processando sua solicitação.",
                            }
                        },
                    ),
                    *_sse_event(
                        "done",
                        {
                            "status": "completed",
                            "additionalInformations": {"service_status": "UNRESOLVED"},
                        },
                    ),
                ],
            ]
        )
        timeline = _FakeTimeline()

        with mock.patch(
            "app.livekit.adapters.remote_agent_sse_adapter.httpx.AsyncClient",
            side_effect=factory.build,
        ):
            adapter = RemoteAgentSSEAdapter(
                intro="oi",
                url="http://agent.internal/agent/execute",
                request_context={
                    "agent": "oferta",
                    "GSM": "551199993339999",
                    "callIdGed": "GED-556",
                    "protocolo": "PRT-556",
                },
                timeline=timeline,
            )
            await adapter.prepare(True, "PRT-556")
            await adapter.run("")

        response_events = [
            fields
            for event_name, fields in timeline.events
            if event_name == "remote_agent_response"
        ]
        self.assertEqual(response_events[0]["sse_event"], "schedule_message")
        self.assertNotIn("event", response_events[0])

    async def test_oferta_done_maps_supported_service_statuses_and_transfer(self) -> None:
        cases = [
            ("RESOLVED", "completed", "resolvido", "DONE", True),
            ("UNRESOLVED", "completed", "unresolved", "PRESENTATION", False),
            ("RESOLVED_WITH_NEW_REQUEST", "completed", "resolved_with_new_request", "PRESENTATION", False),
            ("", "completed", "completed", "PRESENTATION", False),
            ("RESOLVED", "transferred", "transferred", "PRESENTATION", False),
        ]

        for service_status, status, expected_result_type, expected_stage, expected_done in cases:
            with self.subTest(service_status=service_status, status=status):
                factory = _FakeAsyncClientFactory(
                    responses=[
                        [
                            *_sse_event(
                                "done",
                                {
                                    "status": status,
                                    "additionalInformations": {
                                        "service_status": service_status,
                                        "changeAgentId": "ATH-001",
                                        "context": "handover",
                                    },
                                },
                            )
                        ],
                    ]
                )

                with mock.patch(
                    "app.livekit.adapters.remote_agent_sse_adapter.httpx.AsyncClient",
                    side_effect=factory.build,
                ):
                    adapter = RemoteAgentSSEAdapter(
                        intro="oi",
                        url="http://agent.internal/agent/execute",
                        request_context={
                            "agent": "oferta",
                            "GSM": "551199993339999",
                            "callIdGed": "GED-557",
                            "protocolo": "PRT-557",
                        },
                    )
                    await adapter.prepare(True, "PRT-557")
                    reply = await adapter.run("teste")

                self.assertEqual(reply.stage, expected_stage)
                self.assertEqual(reply.done, expected_done)
                self.assertEqual(reply.export_payload["type"], expected_result_type)
                self.assertEqual(reply.export_payload["status"], status)
                self.assertEqual(
                    reply.export_payload["additionalInformations"]["changeAgentId"],
                    "ATH-001",
                )

    async def test_idle_nudge_keeps_only_the_last_phrase_before_the_turn(self) -> None:
        adapter = RemoteAgentSSEAdapter(
            intro="",
            url="http://agent.internal/agent/sse",
            request_context={"agent": "conta"},
        )

        # metadata.wait_retry_messages manda tres frases distintas por chamada;
        # sem a colapsagem elas viram tres falas do agente no historico remoto.
        for retry_text in (
            "Voce ainda esta na linha?",
            "Sigo por aqui.",
            "Vou confirmar mais uma vez, senao vou precisar encerrar o atendimento.",
        ):
            await adapter.inject_idle_nudge(retry_text)

        self.assertEqual(
            adapter._pending_events,
            [
                {
                    "type": "idle_nudge",
                    "text": "Vou confirmar mais uma vez, senao vou precisar encerrar o atendimento.",
                }
            ],
        )

        payload = adapter._build_turn_payload("estou na linha", message_id="MSG-1")
        self.assertEqual(len(payload["payload"]["events"]), 1)


class BackendFactoryTests(unittest.TestCase):
    def test_selects_remote_sse_backend(self) -> None:
        with mock.patch.dict(
            os.environ,
            {
                "AGENT_BACKEND": "remote_sse",
                "REMOTE_AGENT_SSE_URL_CONTA": "http://agent.internal/conta/sse",
            },
            clear=False,
        ):
            backend = build_agent_backend(
                intro="oi",
                remote_agent_context={"agent": "conta"},
                streaming=False,
            )

        self.assertIsInstance(backend, RemoteAgentSSEAdapter)
        self.assertEqual(backend._url, "http://agent.internal/conta/sse")


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