from __future__ import annotations

import asyncio
import os
from typing import Any, Dict, List, Mapping, Optional, Sequence

from app.livekit.adapters.agent_backend import BackendReply
from app.ws_gateway.fake_remote_agent import build_fake_remote_agent_response


class FakeRemoteWSAdapter:
    def __init__(
        self,
        *,
        intro: str,
        backend_label: str = "remote_ws_fake",
        request_context: Optional[Dict[str, Any]] = None,
        timeline: Any | None = None,
        default_stage: str = "PRESENTATION",
        delay_ms: int | None = None,
        responses: Sequence[str] | None = None,
    ) -> None:
        self._intro = intro
        self._backend_label = str(backend_label or "remote_ws_fake").strip().lower()
        self._request_context = dict(request_context or {})
        self._timeline = timeline
        self._default_stage = (default_stage or "PRESENTATION").strip().upper()
        configured_delay = (
            os.getenv("FAKE_AGENT_DELAY_MS", "0") if delay_ms is None else delay_ms
        )
        try:
            self._delay_s = max(0.0, min(180.0, float(configured_delay) / 1000.0))
        except (TypeError, ValueError):
            self._delay_s = 0.0
        self._responses = tuple(str(item).strip() for item in (responses or ()))
        self._response_index = 0
        self._scripted_final_reply: Optional[BackendReply] = None

        self._elegibility = True
        self._protocol = ""
        self._last_stage = "INTRO"
        self._pending_interrupt: Optional[Dict[str, Any]] = None
        self._pending_events: List[Dict[str, Any]] = []

        self._end_lock = asyncio.Lock()
        self._ended = False
        self._end_reply: Optional[BackendReply] = None

    @staticmethod
    def _normalize_agent_name(value: Any) -> str:
        raw = str(value or "").strip().lower()
        aliases = {
            "conta": "conta",
            "contas": "conta",
            "ofert": "oferta",
            "oferta": "oferta",
            "ofertas": "oferta",
            "cobra": "cobranca",
            "cobranca": "cobranca",
            "cobrança": "cobranca",
            "cobrancas": "cobranca",
            "cobranças": "cobranca",
        }
        return aliases.get(raw, raw)

    def _current_agent_name(self) -> str:
        return self._normalize_agent_name(
            self._request_context.get("agent")
            or self._request_context.get("Agent")
            or self._request_context.get("agente")
            or ""
        )

    def _request_field(self, *keys: str) -> str:
        for key in keys:
            value = self._request_context.get(key)
            if value not in (None, ""):
                return str(value).strip()

        lowered = {str(key).lower(): value for key, value in self._request_context.items()}
        for key in keys:
            value = lowered.get(str(key).lower())
            if value not in (None, ""):
                return str(value).strip()

        return ""

    def _current_invoice_number(self) -> str:
        return self._request_field(
            "current_invoice_number",
            "currentInvoiceNumber",
            "ID_FATURA",
            "idFatura",
            "id_fatura",
            "IdFatura",
        )

    def _current_msisdn(self) -> str:
        return self._request_field("msisdn", "GSM", "gsm", "NUM_TELEFONE")

    def _current_channel(self) -> str:
        return self._request_field("channel", "Channel", "canal") or "SUPERVISOR"

    @staticmethod
    def _extract_user_text(user_input: Any) -> str:
        if isinstance(user_input, str):
            return user_input.strip()
        if isinstance(user_input, Mapping):
            for key in ("text", "transcript", "utterance", "message", "content"):
                value = user_input.get(key)
                if value:
                    return str(value).strip()
            return ""
        return str(user_input or "").strip()

    def _base_payload(self) -> Dict[str, Any]:
        payload = {
            "agent": self._current_agent_name(),
            "RouterCallKeyDay": str(self._request_context.get("RouterCallKeyDay") or "").strip(),
            "RouterCallKey": str(self._request_context.get("RouterCallKey") or "").strip(),
            "ANI": str(self._request_context.get("ANI") or "").strip(),
            "GSM": str(self._request_context.get("GSM") or "").strip(),
            "callIdGed": str(self._request_context.get("callIdGed") or "").strip(),
            "protocol": self._protocol,
            "stage": self._last_stage or self._default_stage,
        }
        id_fatura = str(
            self._request_context.get("ID_FATURA")
            or self._request_context.get("id_fatura")
            or self._request_context.get("IdFatura")
            or ""
        ).strip()
        if id_fatura:
            payload["ID_FATURA"] = id_fatura
        return payload

    def _build_turn_payload(self, user_input: Any) -> Dict[str, Any]:
        text = self._extract_user_text(user_input)
        payload = self._base_payload()
        if self._current_agent_name() == "conta":
            payload = {
                "message": text,
                "channel": self._current_channel(),
                "msisdn": self._current_msisdn(),
            }
            current_invoice_number = self._current_invoice_number()
            if current_invoice_number:
                payload["current_invoice_number"] = current_invoice_number
            if self._pending_interrupt is not None:
                self._add_pending_speech_interruption(payload)
            if self._pending_events:
                payload["events"] = list(self._pending_events)
            return {
                "action": "chat",
                "payload": payload,
                "_agent": self._current_agent_name(),
                "_stage": self._last_stage or self._default_stage,
            }

        payload["text"] = text
        if self._pending_interrupt is not None:
            self._add_pending_speech_interruption(payload)
        if self._pending_events:
            payload["events"] = list(self._pending_events)
        return payload

    def _build_end_payload(self) -> Dict[str, Any]:
        payload = self._base_payload()
        if self._current_agent_name() == "conta":
            payload["msisdn"] = self._current_msisdn()
            payload["channel"] = self._current_channel()
            current_invoice_number = self._current_invoice_number()
            if current_invoice_number:
                payload["current_invoice_number"] = current_invoice_number
            if self._pending_interrupt is not None:
                self._add_pending_speech_interruption(payload)
            if self._pending_events:
                payload["events"] = list(self._pending_events)
            return {
                "action": "end",
                "payload": payload,
                "_agent": self._current_agent_name(),
                "_stage": self._last_stage or self._default_stage,
            }

        payload["type"] = "end"
        if self._pending_interrupt is not None:
            self._add_pending_speech_interruption(payload)
        if self._pending_events:
            payload["events"] = list(self._pending_events)
        return payload

    def _add_pending_speech_interruption(self, payload: Dict[str, Any]) -> None:
        if self._pending_interrupt is None:
            return
        interruption = dict(self._pending_interrupt)
        interruption_key = str(
            interruption.pop("_interruption_field", "speech_interruption")
        )
        payload[interruption_key] = interruption

    @staticmethod
    def _reply_text(response: Mapping[str, Any]) -> str:
        result = response.get("result")
        if isinstance(result, Mapping):
            content = result.get("content")
            if content:
                return str(content).strip()
        text = response.get("text")
        if text:
            return str(text).strip()
        return ""

    @staticmethod
    def _export_payload(response: Mapping[str, Any]) -> Any:
        return response.get("result")

    def _to_backend_reply(self, response: Mapping[str, Any]) -> BackendReply:
        stage = str(response.get("stage") or self._last_stage or self._default_stage).strip().upper()
        done = stage == "DONE" or str(response.get("type") or "").strip().lower() == "done"
        return BackendReply(
            stage=stage,
            text=self._reply_text(response),
            done=done,
            export_payload=self._export_payload(response),
        )

    def _clear_pending_state(self) -> None:
        self._pending_interrupt = None
        self._pending_events.clear()

    async def prepare(
        self,
        elegibility: bool,
        protocol: str,
    ) -> None:
        if self._timeline is not None:
            self._timeline.emit(
                "backend_prepare_started",
                backend=self._backend_label,
                backend_family="remote_ws_fake",
                agent=self._current_agent_name(),
            )
        self._elegibility = bool(elegibility)
        self._protocol = str(protocol or "")
        self._last_stage = "INTRO"
        self._pending_interrupt = None
        self._pending_events.clear()
        self._ended = False
        self._end_reply = None
        self._response_index = 0
        self._scripted_final_reply = None
        if self._timeline is not None:
            self._timeline.emit(
                "backend_prepare_completed",
                backend=self._backend_label,
                backend_family="remote_ws_fake",
                agent=self._current_agent_name(),
                protocol=self._protocol,
            )

    async def run(self, user_input: Any) -> BackendReply:
        if self._scripted_final_reply is not None:
            return self._scripted_final_reply

        payload = self._build_turn_payload(user_input)
        if self._timeline is not None:
            self._timeline.emit(
                "remote_agent_request",
                backend=self._backend_label,
                backend_family="remote_ws_fake",
                agent=self._current_agent_name(),
                action=str(payload.get("action") or "chat"),
                payload=payload,
            )

        if self._delay_s:
            await asyncio.sleep(self._delay_s)
        if self._responses:
            index = self._response_index
            text = self._responses[index]
            if index == len(self._responses) - 1:
                stage = "DONE"
            elif index == len(self._responses) - 2:
                stage = "FORMALIZATION"
            else:
                stage = "ARGUMENTATION"
            done = stage == "DONE"
            reply = BackendReply(
                stage=stage,
                text=text,
                done=done,
                export_payload=(
                    {
                        "type": "final",
                        "content": text,
                        "tool_calls": [],
                        "result": [{"status": "ok", "reason": "fake_done"}],
                    }
                    if done
                    else None
                ),
            )
            self._response_index += 1
            if done:
                self._scripted_final_reply = reply
        else:
            response = build_fake_remote_agent_response(payload)
            reply = self._to_backend_reply(response)
        self._last_stage = reply.stage or self._last_stage or self._default_stage
        self._clear_pending_state()

        if self._timeline is not None:
            self._timeline.emit(
                "remote_agent_response",
                backend=self._backend_label,
                backend_family="remote_ws_fake",
                agent=self._current_agent_name(),
                stage=reply.stage,
                done=reply.done,
                text_len=len(reply.text),
                has_result=reply.export_payload is not None,
            )
        return reply

    async def set_interruption(
        self,
        interrupted: bool,
        listened_text: str = "",
        skipped: bool = False,
        speech_id: str = "",
    ) -> None:
        if not interrupted:
            self._pending_interrupt = None
        else:
            self._pending_interrupt = {
                "speech_id": str(speech_id or "").strip(),
                "heard_text": listened_text or "",
            }
        if self._timeline is not None:
            self._timeline.emit(
                "backend_set_interruption",
                backend=self._backend_label,
                backend_family="remote_ws_fake",
                agent=self._current_agent_name(),
                interrupted=bool(interrupted),
                skipped=bool(skipped),
                listened_text=listened_text,
                speech_id=str(speech_id or "").strip(),
            )

    async def set_processing_interruption(
        self,
        listened_text: str = "",
        skipped: bool = False,
        speech_id: str = "",
    ) -> None:
        await self.set_interruption(
            True,
            listened_text=listened_text,
            skipped=skipped,
            speech_id=speech_id,
        )
        if self._pending_interrupt is not None:
            self._pending_interrupt["_interruption_field"] = "processing_interruption"

    async def inject_idle_nudge(self, nudge_text: str) -> None:
        text = str(nudge_text or "").strip()
        if not text:
            return
        event = {"type": "idle_nudge", "text": text}
        # Cada frase de inatividade substitui a anterior: o cliente responde ao
        # que ouviu por ultimo, e as intermediarias so empilham falas do agente
        # no historico remoto -- inclusive o aviso de encerramento, que passa a
        # constar como dito logo antes de a conversa seguir normalmente.
        if self._pending_events and self._pending_events[-1].get("type") == "idle_nudge":
            self._pending_events[-1] = event
        else:
            self._pending_events.append(event)
        if self._timeline is not None:
            self._timeline.emit(
                "backend_idle_nudge_buffered",
                backend=self._backend_label,
                backend_family="remote_ws_fake",
                agent=self._current_agent_name(),
                text=text,
            )

    def supports_server_push(self) -> bool:
        return False

    async def wait_for_server_push(self) -> BackendReply | None:
        return None

    async def end_service_once(self) -> BackendReply:
        async with self._end_lock:
            if self._ended:
                return self._end_reply or BackendReply(stage="DONE", done=True, export_payload=[])

            self._ended = True
            if self._timeline is not None:
                self._timeline.emit(
                    "backend_end_started",
                    backend=self._backend_label,
                    backend_family="remote_ws_fake",
                    agent=self._current_agent_name(),
                )
            response = build_fake_remote_agent_response(self._build_end_payload())
            reply = self._to_backend_reply(response)
            if not reply.done:
                reply = BackendReply(
                    stage="DONE",
                    text=reply.text,
                    done=True,
                    export_payload=reply.export_payload,
                )
            self._last_stage = reply.stage
            self._clear_pending_state()
            self._end_reply = reply
            if self._timeline is not None:
                self._timeline.emit(
                    "backend_end_completed",
                    backend=self._backend_label,
                    backend_family="remote_ws_fake",
                    agent=self._current_agent_name(),
                    result_type=type(reply.export_payload).__name__,
                )
            return reply
