from __future__ import annotations

import asyncio
import json
import os
import sys
import types
import unittest
import uuid
from unittest import mock
from urllib.parse import parse_qs, urlsplit

from app.livekit.adapters.agent_backend import BackendReply
from app.livekit.adapters.backend_factory import build_agent_backend
from app.livekit.adapters.fake_remote_ws_adapter import FakeRemoteWSAdapter
from app.livekit.adapters.remote_agent_ws_adapter import RemoteAgentWSAdapter


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


class _FakeWebSocket:
    def __init__(self, responses, send_responses=None) -> None:
        self._responses = list(responses)
        self._send_responses = [list(batch) for batch in (send_responses or [])]
        self._ready = asyncio.Event()
        if self._responses:
            self._ready.set()
        self.sent_messages = []

    async def send(self, message: str) -> None:
        self.sent_messages.append(message)
        if self._send_responses:
            self._responses.extend(self._send_responses.pop(0))
            self._ready.set()

    async def recv(self):
        while not self._responses:
            self._ready.clear()
            await self._ready.wait()
        return self._responses.pop(0)


class _FakeConnect:
    def __init__(self, websocket: _FakeWebSocket) -> None:
        self._websocket = websocket

    async def __aenter__(self) -> _FakeWebSocket:
        return self._websocket

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


class _FakeWebsocketsModule(types.SimpleNamespace):
    def __init__(self, sessions) -> None:
        super().__init__()
        self._sessions = []
        for spec in sessions:
            if isinstance(spec, dict):
                initial = spec.get("initial") or []
                send_responses = spec.get("after_sends") or []
                self._sessions.append(_FakeWebSocket(initial, send_responses))
            else:
                self._sessions.append(_FakeWebSocket(spec))
        self.calls = []

    def connect(self, url: str, **kwargs):
        if not self._sessions:
            raise AssertionError("No fake websocket session configured")
        websocket = self._sessions.pop(0)
        self.calls.append(
            {
                "url": url,
                "kwargs": kwargs,
                "websocket": websocket,
            }
        )
        return _FakeConnect(websocket)


class RemoteAgentWSAdapterTests(unittest.IsolatedAsyncioTestCase):
    async def test_run_sends_turn_payload_and_parses_response_for_non_conta_agents(self) -> None:
        fake_ws_module = _FakeWebsocketsModule(
            sessions=[
                [
                    json.dumps(
                        {
                            "type": "final",
                            "stage": "ARGUMENTATION",
                            "text": "Tenho uma oferta para voce.",
                        }
                    )
                ],
                [
                    json.dumps(
                        {
                            "type": "final",
                            "stage": "FORMALIZATION",
                            "text": "Continuando sem interrupcao.",
                        }
                    )
                ],
            ]
        )

        with mock.patch.dict(sys.modules, {"websockets": fake_ws_module}):
            adapter = RemoteAgentWSAdapter(
                intro="oi",
                url="ws://agent.internal/turn",
                url_by_agent={
                    "oferta": "ws://agent.internal/oferta",
                    "cobranca": "ws://agent.internal/cobranca",
                },
                request_context={
                    "agent": "oferta",
                    "RouterCallKeyDay": "20260329",
                    "RouterCallKey": "0001",
                    "ANI": "3133334444",
                    "GSM": "31999999999",
                    "callIdGed": "GED-123",
                },
            )
            await adapter.prepare(True, "PRT-123")
            await adapter.set_interruption(
                True,
                listened_text="pode repetir",
                skipped=False,
                speech_id="7f3a0000-0000-4000-8000-000000000000",
            )
            await adapter.inject_idle_nudge("Alo, voce esta ai?")

            reply = await adapter.run({"text": "quero detalhes"})
            self.assertEqual(
                reply,
                BackendReply(
                    stage="ARGUMENTATION",
                    text="Tenho uma oferta para voce.",
                    done=False,
                    export_payload=None,
                ),
            )

            first_payload = json.loads(fake_ws_module.calls[0]["websocket"].sent_messages[0])
            self.assertEqual(fake_ws_module.calls[0]["url"], "ws://agent.internal/oferta")
            self.assertIn("timestamp", first_payload)
            self.assertEqual(first_payload["agent"], "oferta")
            self.assertEqual(first_payload["RouterCallKeyDay"], "20260329")
            self.assertEqual(first_payload["RouterCallKey"], "0001")
            self.assertEqual(first_payload["ANI"], "3133334444")
            self.assertEqual(first_payload["GSM"], "31999999999")
            self.assertEqual(first_payload["callIdGed"], "GED-123")
            self.assertEqual(first_payload["protocol"], "PRT-123")
            self.assertEqual(first_payload["text"], "quero detalhes")
            self.assertEqual(
                first_payload["speech_interruption"],
                {
                    "speech_id": "7f3a0000-0000-4000-8000-000000000000",
                    "heard_text": "pode repetir",
                },
            )
            self.assertNotIn("processing_interruption", first_payload)
            self.assertEqual(
                first_payload["events"],
                [{"type": "idle_nudge", "text": "Alo, voce esta ai?"}],
            )

            reply_2 = await adapter.run({"text": "segue"})
            self.assertEqual(
                reply_2,
                BackendReply(
                    stage="FORMALIZATION",
                    text="Continuando sem interrupcao.",
                    done=False,
                    export_payload=None,
                ),
            )

            second_payload = json.loads(fake_ws_module.calls[1]["websocket"].sent_messages[0])
            self.assertNotIn("interruption", second_payload)
            self.assertNotIn("speech_interruption", second_payload)
            self.assertNotIn("events", second_payload)

    async def test_conta_prepare_buffers_ready_and_reuses_socket_for_proactive_results(self) -> None:
        fake_ws_module = _FakeWebsocketsModule(
            sessions=[
                {
                    "initial": [
                        json.dumps(
                            {
                                "type": "ready",
                                "message": "Entendi que voce precisa de mais detalhes sobre os valores da sua fatura.",
                                "actions": ["chat", "ping"],
                                "speech_id": "11111111-1111-4111-8111-111111111111",
                                "is_interruptible": False,
                                "metadata": {"wait_timeout_seconds": 30},
                            }
                        )
                    ],
                    "after_sends": [
                        [
                            json.dumps(
                                {
                                    "type": "proactive_result",
                                    "action": "explicar_fatura",
                                    "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": [],
                                },
                                }
                            ),
                            json.dumps(
                                {
                                    "type": "proactive_result",
                                    "action": "explicar_fatura",
                                    "result": {
                                        "type": "final",
                                        "content": "Esses sao os principais fatores que podem gerar a diferenca na fatura.",
                                        "tool_calls": [],
                                    },
                                }
                            ),
                        ]
                    ],
                }
            ]
        )

        with mock.patch.dict(sys.modules, {"websockets": fake_ws_module}):
            adapter = RemoteAgentWSAdapter(
                intro="oi",
                url="ws://agent.internal/conta",
                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("")
            connection_message_id = _assert_uuid(self, ready_reply.metadata["message_id"])

            self.assertEqual(
                ready_reply,
                BackendReply(
                    stage="PRESENTATION",
                    text="Entendi que voce precisa de mais detalhes sobre os valores da sua fatura.",
                    done=False,
                    export_payload=None,
                    metadata={
                        "message_id": connection_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,
                    },
                ),
            )

            ws = fake_ws_module.calls[0]["websocket"]
            self.assertEqual(ws.sent_messages, [])

            await adapter.set_interruption(
                True,
                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")
            self.assertEqual(reply.metadata["message_id"], connection_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": connection_message_id,
                        "speech_id": "7f3a0000-0000-4000-8000-000000000000",
                        "is_interruptible": True,
                        "agent_message_type": "proactive_result",
                        "agent_result_type": "final",
                        "expects_user_response": True,
                        "drop_user_input_while_speaking": False,
                    },
                ),
            )

            first_payload = json.loads(ws.sent_messages[0])
            parsed_url = urlsplit(fake_ws_module.calls[0]["url"])
            self.assertEqual(
                f"{parsed_url.scheme}://{parsed_url.netloc}{parsed_url.path}",
                "ws://agent.internal/conta",
            )
            self.assertEqual(
                parse_qs(parsed_url.query),
                {
                    "msisdn": ["551199993339999"],
                    "current_invoice_number": ["fat-555"],
                    "ani": ["3133338888"],
                    "protocol_id": ["PRT-555"],
                    "session_id": ["call-session-555"],
                    "message_id": [connection_message_id],
                    "channelId": ["ura"],
                    "uraCallId": ["GED-555"],
                },
            )
            self.assertEqual(first_payload["action"], "chat")
            self.assertEqual(first_payload["payload"]["message"], "teste")
            self.assertEqual(first_payload["payload"]["channel"], "SUPERVISOR")
            self.assertEqual(first_payload["payload"]["msisdn"], "551199993339999")
            self.assertEqual(first_payload["payload"]["current_invoice_number"], "fat-555")
            self.assertEqual(
                first_payload["payload"]["speech_interruption"],
                {
                    "speech_id": "11111111-1111-4111-8111-111111111111",
                    "heard_text": "pode repetir",
                },
            )
            self.assertNotIn("processing_interruption", first_payload["payload"])
            self.assertNotIn("interruption", first_payload["payload"])
            self.assertEqual(
                first_payload["payload"]["events"],
                [{"type": "idle_nudge", "text": "Alo, voce esta ai?"}],
            )

            reply_2 = await adapter.wait_for_server_push()
            self.assertEqual(
                reply_2,
                BackendReply(
                    stage="PRESENTATION",
                    text="Esses sao os principais fatores que podem gerar a diferenca na fatura.",
                    done=False,
                    export_payload={
                        "type": "final",
                        "content": "Esses sao os principais fatores que podem gerar a diferenca na fatura.",
                        "tool_calls": [],
                    },
                    metadata={
                        "message_id": connection_message_id,
                        "agent_message_type": "proactive_result",
                        "agent_result_type": "final",
                        "expects_user_response": True,
                        "drop_user_input_while_speaking": False,
                    },
                ),
            )
            self.assertEqual(len(ws.sent_messages), 1)

    async def test_conta_run_parses_top_level_final_content(self) -> None:
        fake_ws_module = _FakeWebsocketsModule(
            sessions=[
                {
                    "initial": [
                        json.dumps(
                            {
                                "type": "ready",
                                "message": "Posso verificar sua fatura.",
                                "actions": ["chat", "ping"],
                            }
                        )
                    ],
                    "after_sends": [
                        [
                            json.dumps(
                                {
                                    "type": "final",
                                    "content": "Infelizmente **não encontrei** ",
                                }
                            )
                        ]
                    ],
                }
            ]
        )

        with mock.patch.dict(sys.modules, {"websockets": fake_ws_module}):
            adapter = RemoteAgentWSAdapter(
                intro="oi",
                url="ws://agent.internal/conta",
                request_context={
                    "agent": "conta",
                    "RouterCallKeyDay": "20260329",
                    "RouterCallKey": "0006",
                    "ANI": "3133330000",
                    "GSM": "5511999900000",
                    "callIdGed": "GED-556",
                },
            )
            await adapter.prepare(True, "PRT-556")

            ready_reply = await adapter.run("")
            self.assertEqual(ready_reply.text, "Posso verificar sua fatura.")

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

            self.assertEqual(
                reply,
                BackendReply(
                    stage="PRESENTATION",
                    text="Infelizmente **não encontrei**",
                    done=False,
                    export_payload=None,
                    metadata={
                        "message_id": connection_message_id,
                        "agent_message_type": "final",
                        "expects_user_response": True,
                        "drop_user_input_while_speaking": False,
                    },
                ),
            )

    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.",
        }
        fake_ws_module = _FakeWebsocketsModule(
            sessions=[
                {
                    "initial": [
                        json.dumps(
                            {
                                "type": "ready",
                                "message": "Posso verificar sua fatura.",
                                "actions": ["chat", "ping"],
                            }
                        )
                    ],
                    "after_sends": [
                        [
                            json.dumps(feedback_payload),
                            json.dumps(
                                {
                                    "type": "final",
                                    "content": "Encontrei os detalhes da sua fatura.",
                                }
                            ),
                        ]
                    ],
                }
            ]
        )

        with mock.patch.dict(sys.modules, {"websockets": fake_ws_module}):
            adapter = RemoteAgentWSAdapter(
                intro="oi",
                url="ws://agent.internal/conta",
                request_context={
                    "agent": "conta",
                    "GSM": "5511999900000",
                    "callIdGed": "GED-556",
                },
            )
            await adapter.prepare(True, "PRT-556")

            ready_reply = await adapter.run("")
            self.assertEqual(ready_reply.text, "Posso verificar sua fatura.")

            reply = await adapter.run("teste")
            feedback_reply = await adapter.wait_for_server_push()
            connection_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": connection_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": connection_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_conta_run_marks_expected_agent_result_type_as_done(self) -> None:
        fake_ws_module = _FakeWebsocketsModule(
            sessions=[
                {
                    "initial": [
                        json.dumps(
                            {
                                "type": "ready",
                                "message": "Posso verificar sua fatura.",
                                "actions": ["chat", "ping"],
                            }
                        )
                    ],
                    "after_sends": [
                        [
                            json.dumps(
                                {
                                    "type": "result",
                                    "action": "chat",
                                    "result": {
                                        "type": "resolvido",
                                        "content": "Atendimento encerrado com sucesso.",
                                        "tool_calls": [
                                            {
                                                "id": "call-final",
                                                "name": "finalizar_atendimento",
                                                "args": {
                                                    "status": "resolvido",
                                                    "summary": "Resumo da conversa.",
                                                },
                                                "result": {
                                                    "success": True,
                                                    "status": "resolvido",
                                                    "summary": "Encerramento realizado pelo agente.",
                                                },
                                            }
                                        ],
                                    },
                                }
                            )
                        ]
                    ],
                }
            ]
        )

        with mock.patch.dict(sys.modules, {"websockets": fake_ws_module}):
            adapter = RemoteAgentWSAdapter(
                intro="oi",
                url="ws://agent.internal/conta",
                request_context={
                    "agent": "conta",
                    "GSM": "5511999900000",
                    "callIdGed": "GED-560",
                },
            )
            await adapter.prepare(True, "PRT-560")

            ready_reply = await adapter.run("")
            self.assertEqual(ready_reply.text, "Posso verificar sua fatura.")

            reply = await adapter.run("pode encerrar")
            connection_message_id = _assert_uuid(self, reply.metadata["message_id"])

            self.assertEqual(
                reply,
                BackendReply(
                    stage="DONE",
                    text="Atendimento encerrado com sucesso.",
                    done=True,
                    export_payload={
                        "type": "resolvido",
                        "content": "Atendimento encerrado com sucesso.",
                        "tool_calls": [
                            {
                                "id": "call-final",
                                "name": "finalizar_atendimento",
                                "args": {
                                    "status": "resolvido",
                                    "summary": "Resumo da conversa.",
                                },
                                "result": {
                                    "success": True,
                                    "status": "resolvido",
                                    "summary": "Encerramento realizado pelo agente.",
                                },
                            }
                        ],
                    },
                    metadata={
                        "message_id": connection_message_id,
                        "agent_message_type": "result",
                        "agent_result_type": "resolvido",
                        "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,
        }
        fake_ws_module = _FakeWebsocketsModule(
            sessions=[
                {
                    "initial": [
                        json.dumps(
                            {
                                "type": "ready",
                                "message": "Posso verificar sua fatura.",
                                "actions": ["chat", "ping"],
                            }
                        )
                    ],
                    "after_sends": [
                        [
                            json.dumps(feedback_payload),
                            json.dumps(
                                {
                                    "type": "result",
                                    "action": "chat",
                                    "result": {
                                        "type": "final",
                                        "content": "Cancelamento concluido.",
                                        "tool_calls": [],
                                    },
                                }
                            ),
                        ]
                    ],
                }
            ]
        )

        with mock.patch.dict(sys.modules, {"websockets": fake_ws_module}):
            adapter = RemoteAgentWSAdapter(
                intro="oi",
                url="ws://agent.internal/conta",
                request_context={
                    "agent": "conta",
                    "GSM": "5511999900000",
                    "callIdGed": "GED-561",
                },
            )
            await adapter.prepare(True, "PRT-561")

            ready_reply = await adapter.run("")
            self.assertEqual(ready_reply.text, "Posso verificar sua fatura.")

            reply = await adapter.run("correto")
            feedback_reply = await adapter.wait_for_server_push()
            connection_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": connection_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": connection_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_conta_wait_for_server_push_receives_proactive_result_without_new_run(self) -> None:
        fake_ws_module = _FakeWebsocketsModule(
            sessions=[
                [
                    json.dumps(
                        {
                            "type": "ready",
                            "message": "Posso verificar sua fatura.",
                            "actions": ["chat", "ping"],
                        }
                    ),
                    json.dumps(
                        {
                            "type": "proactive_result",
                            "action": "explicar_fatura",
                            "result": {
                                "type": "final",
                                "content": "A variacao entre a fatura atual e a anterior pode ocorrer por alguns motivos comuns.",
                                "tool_calls": [],
                            },
                        }
                    ),
                ]
            ]
        )

        with mock.patch.dict(sys.modules, {"websockets": fake_ws_module}):
            adapter = RemoteAgentWSAdapter(
                intro="oi",
                url="ws://agent.internal/conta",
                request_context={
                    "agent": "conta",
                    "RouterCallKeyDay": "20260329",
                    "RouterCallKey": "0007",
                    "ANI": "3133330001",
                    "GSM": "5511999900001",
                    "callIdGed": "GED-557",
                },
            )
            await adapter.prepare(True, "PRT-557")

            ready_reply = await adapter.run("")
            self.assertEqual(ready_reply.text, "Posso verificar sua fatura.")

            push_reply = await adapter.wait_for_server_push()
            connection_message_id = _assert_uuid(self, push_reply.metadata["message_id"])
            self.assertEqual(
                push_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.",
                        "tool_calls": [],
                    },
                    metadata={
                        "message_id": connection_message_id,
                        "agent_message_type": "proactive_result",
                        "agent_result_type": "final",
                        "expects_user_response": True,
                        "drop_user_input_while_speaking": False,
                    },
                ),
            )

    async def test_conta_can_send_new_chat_after_idle_period_without_killing_reader(self) -> None:
        fake_ws_module = _FakeWebsocketsModule(
            sessions=[
                {
                    "initial": [
                        json.dumps(
                            {
                                "type": "ready",
                                "message": "Posso verificar sua fatura.",
                                "actions": ["chat", "ping"],
                            }
                        )
                    ],
                    "after_sends": [
                        [
                            json.dumps(
                                {
                                    "type": "proactive_result",
                                    "action": "explicar_fatura",
                                    "result": {
                                        "type": "final",
                                        "content": "Resumo inicial da fatura.",
                                        "tool_calls": [],
                                    },
                                }
                            )
                        ],
                        [
                            json.dumps(
                                {
                                    "type": "final",
                                    "content": "Os servicos ativos sao Netflix e Deezer.",
                                }
                            )
                        ],
                    ],
                }
            ]
        )

        with mock.patch.dict(sys.modules, {"websockets": fake_ws_module}):
            adapter = RemoteAgentWSAdapter(
                intro="oi",
                url="ws://agent.internal/conta",
                request_context={
                    "agent": "conta",
                    "RouterCallKeyDay": "20260329",
                    "RouterCallKey": "0008",
                    "ANI": "3133330002",
                    "GSM": "5511999900002",
                    "callIdGed": "GED-558",
                },
                read_timeout_s=0.1,
            )
            await adapter.prepare(True, "PRT-558")

            ready_reply = await adapter.run("")
            self.assertEqual(ready_reply.text, "Posso verificar sua fatura.")

            first_reply = await adapter.run("quero detalhes")
            self.assertEqual(first_reply.text, "Resumo inicial da fatura.")

            await asyncio.sleep(0.12)

            second_reply = await adapter.run("Quais servicos eu tenho?")
            self.assertEqual(second_reply.text, "Os servicos ativos sao Netflix e Deezer.")

    async def test_conta_end_service_once_skips_end_action_when_backend_does_not_advertise_it(self) -> None:
        fake_ws_module = _FakeWebsocketsModule(
            sessions=[
                [
                    json.dumps(
                        {
                            "type": "ready",
                            "message": "Posso verificar sua fatura.",
                            "actions": ["chat", "ping"],
                        }
                    )
                ]
            ]
        )

        with mock.patch.dict(sys.modules, {"websockets": fake_ws_module}):
            adapter = RemoteAgentWSAdapter(
                intro="oi",
                url="ws://agent.internal/conta",
                request_context={
                    "agent": "conta",
                    "RouterCallKeyDay": "20260329",
                    "RouterCallKey": "0009",
                    "ANI": "3133330003",
                    "GSM": "5511999900003",
                    "callIdGed": "GED-559",
                },
            )
            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=[],
                ),
            )

            ws = fake_ws_module.calls[0]["websocket"]
            self.assertEqual(ws.sent_messages, [])

    async def test_run_accumulates_streamed_chunks_until_done(self) -> None:
        fake_ws_module = _FakeWebsocketsModule(
            sessions=[
                [
                    json.dumps({"type": "delta", "delta": "Oferta "}),
                    json.dumps({"type": "delta", "delta": "liberada"}),
                    json.dumps({"type": "done", "stage": "DONE"}),
                ]
            ]
        )

        with mock.patch.dict(sys.modules, {"websockets": fake_ws_module}):
            adapter = RemoteAgentWSAdapter(
                intro="oi",
                url="ws://agent.internal/turn",
                url_by_agent={"cobranca": "ws://agent.internal/cobranca"},
                request_context={
                    "agent": "cobra",
                    "RouterCallKeyDay": "20260329",
                    "RouterCallKey": "0002",
                    "ANI": "3133335555",
                    "GSM": "31988887777",
                    "callIdGed": "GED-456",
                },
            )
            await adapter.prepare(True, "PRT-456")

            reply = await adapter.run("teste")

            self.assertEqual(
                reply,
                BackendReply(
                    stage="DONE",
                    text="Oferta liberada",
                    done=True,
                    export_payload=None,
                ),
            )
            payload = json.loads(fake_ws_module.calls[0]["websocket"].sent_messages[0])
            self.assertEqual(fake_ws_module.calls[0]["url"], "ws://agent.internal/cobranca")
            self.assertEqual(payload["agent"], "cobranca")
            self.assertEqual(payload["callIdGed"], "GED-456")
            self.assertNotIn("ID_FATURA", payload)

    async def test_run_uses_default_stage_when_remote_does_not_return_stage(self) -> None:
        fake_ws_module = _FakeWebsocketsModule(
            sessions=[
                [
                    json.dumps({"type": "final", "text": "Resposta sem stage."}),
                ]
            ]
        )

        with mock.patch.dict(sys.modules, {"websockets": fake_ws_module}):
            adapter = RemoteAgentWSAdapter(
                intro="oi",
                url="ws://agent.internal/turn",
                default_stage="presentation",
                request_context={
                    "agent": "ofert",
                    "RouterCallKeyDay": "20260329",
                    "RouterCallKey": "0003",
                    "ANI": "3133336666",
                    "GSM": "31977776666",
                    "callIdGed": "GED-777",
                },
            )
            await adapter.prepare(True, "PRT-777")

            reply = await adapter.run("teste")

            self.assertEqual(
                reply,
                BackendReply(
                    stage="PRESENTATION",
                    text="Resposta sem stage.",
                    done=False,
                    export_payload=None,
                ),
            )

    async def test_end_service_once_returns_remote_result_and_is_idempotent(self) -> None:
        fake_ws_module = _FakeWebsocketsModule(
            sessions=[
                [
                    json.dumps(
                        {
                            "type": "done",
                            "result": [{"success_purchase": 1, "protocol": "PRT-999"}],
                        }
                    )
                ]
            ]
        )

        with mock.patch.dict(sys.modules, {"websockets": fake_ws_module}):
            adapter = RemoteAgentWSAdapter(
                intro="oi",
                url="ws://agent.internal/end",
                request_context={
                    "agent": "oferta",
                    "RouterCallKeyDay": "20260329",
                    "RouterCallKey": "0004",
                    "ANI": "3133337777",
                    "GSM": "31966665555",
                    "callIdGed": "GED-999",
                },
            )
            await adapter.prepare(True, "PRT-999")

            output_1 = await adapter.end_service_once()
            output_2 = await adapter.end_service_once()

            self.assertEqual(
                output_1,
                BackendReply(
                    stage="DONE",
                    text="",
                    done=True,
                    export_payload=[{"success_purchase": 1, "protocol": "PRT-999"}],
                ),
            )
            self.assertEqual(output_2, output_1)
            self.assertEqual(len(fake_ws_module.calls), 1)


class BackendFactoryTests(unittest.TestCase):
    def test_selects_remote_ws_backend(self) -> None:
        with mock.patch.dict(
            os.environ,
            {
                "AGENT_BACKEND": "remote_ws",
                "REMOTE_AGENT_WS_URL": "ws://agent.internal/turn",
                "REMOTE_AGENT_WS_URL_CONTA": "ws://agent.internal/conta",
            },
            clear=False,
        ):
            backend = build_agent_backend(
                intro="oi",
                remote_agent_context={"agent": "conta"},
                streaming=False,
            )

        self.assertIsInstance(backend, RemoteAgentWSAdapter)
        self.assertEqual(backend._url, "ws://agent.internal/turn")

    def test_selects_fake_remote_ws_backend(self) -> None:
        with mock.patch.dict(
            os.environ,
            {
                "REMOTE_AGENT_WS_URL": "ws://agent.internal/real",
                "REMOTE_AGENT_WS_FAKE_URL": "ws://127.0.0.1:8000/fake-agent/ws",
            },
            clear=False,
        ):
            backend = build_agent_backend(
                intro="oi",
                backend_name="remote_ws_fake",
                remote_agent_context={"agent": "conta"},
                streaming=False,
            )

        self.assertIsInstance(backend, FakeRemoteWSAdapter)
        self.assertEqual(backend._backend_label, "remote_ws_fake")


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