from __future__ import annotations

import asyncio
from typing import Any, Dict, Optional

from agent.pipeline.customer_pipeline_langgraph import CustomerPipeline
from app.livekit.adapters.agent_backend import BackendReply
from app.utils.logging import setup_minimal_logging

logger = setup_minimal_logging()


class PipelineAdapter:
    def __init__(
        self,
        *,
        session_data: Dict[str, Any],
        intro: str,
        timeline: Any | None = None,
        streaming: bool = False,
    ) -> None:
        self._session_data = session_data
        self._intro = intro
        self._timeline = timeline
        self._streaming = streaming

        self._pipeline: Optional[CustomerPipeline] = None
        self._last_stage = "INTRO"
        self._end_lock = asyncio.Lock()
        self._ended = False
        self._end_reply: Optional[BackendReply] = None

    def _require_pipeline(self) -> CustomerPipeline:
        if self._pipeline is None:
            raise RuntimeError("PipelineAdapter used before prepare() completed")
        return self._pipeline

    def _call_set_interruption(
        self,
        pipeline: CustomerPipeline,
        interrupted: bool,
        listened_text: str = "",
        skipped: bool = False,
        speech_id: str = "",
    ) -> None:
        fn = getattr(pipeline, "set_interruption", None)
        if not callable(fn):
            return

        try:
            fn(interrupted, listened_text, skipped, speech_id)
            return
        except TypeError:
            pass

        try:
            fn(interrupted, listened_text, skipped)
            return
        except TypeError:
            pass

        try:
            fn(interrupted, listened_text)
            return
        except TypeError:
            pass

        fn(interrupted)

    async def prepare(
        self,
        elegibility: bool,
        protocol: str,
    ) -> None:
        if self._timeline is not None:
            self._timeline.emit(
                "backend_prepare_started",
                backend="langgraph",
                elegibility=bool(elegibility),
            )

        def _build_pipeline() -> CustomerPipeline:
            pipeline = CustomerPipeline(self._session_data, streaming=self._streaming)
            pipeline.intro = self._intro
            return pipeline

        pipeline = await asyncio.to_thread(_build_pipeline)
        self._pipeline = pipeline

        def _prepare() -> None:
            pipeline.prepare(elegibility, protocol)

        await asyncio.to_thread(_prepare)
        if self._timeline is not None:
            self._timeline.emit("backend_prepare_completed", backend="langgraph")

    async def run(self, user_input: Any) -> BackendReply:
        pipeline = self._require_pipeline()
        result = await asyncio.to_thread(pipeline.run, user_input)
        if not isinstance(result, tuple) or len(result) != 2:
            raise RuntimeError(f"Unexpected pipeline.run() result: {type(result)!r}")

        stage_raw, output_raw = result
        stage = str(stage_raw or self._last_stage or "PRESENTATION").strip().upper()
        text = str(output_raw or "").strip()
        self._last_stage = stage
        reply = BackendReply(
            stage=stage,
            text=text,
            done=stage == "DONE",
            export_payload=None,
        )
        if self._timeline is not None:
            self._timeline.emit(
                "backend_run_completed",
                backend="langgraph",
                stage=reply.stage,
                output_len=len(reply.text),
            )
        return reply

    async def set_interruption(
        self,
        interrupted: bool,
        listened_text: str = "",
        skipped: bool = False,
        speech_id: str = "",
    ) -> None:
        pipeline = self._require_pipeline()
        if self._timeline is not None:
            self._timeline.emit(
                "backend_set_interruption",
                backend="langgraph",
                interrupted=bool(interrupted),
                skipped=bool(skipped),
                listened_text=listened_text,
                speech_id=speech_id,
            )
        self._call_set_interruption(pipeline, interrupted, listened_text, skipped, speech_id)

    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,
        )

    async def inject_idle_nudge(self, nudge_text: str) -> None:
        pipeline = self._require_pipeline()

        inject_user = getattr(getattr(pipeline, "agent", None), "inject_user_message", None)
        inject_ai = getattr(getattr(pipeline, "agent", None), "inject_ai_message", None)
        if callable(inject_user) and callable(inject_ai):
            inject_user("")
            inject_ai(nudge_text)

        self._call_set_interruption(pipeline, False, "", False, "")

        update_auto = getattr(pipeline, "update_langfuse_auto", None)
        if callable(update_auto):
            update_auto("###idle###", nudge_text)

    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="langgraph")
            pipeline = self._pipeline
            if pipeline is None:
                self._end_reply = BackendReply(stage="DONE", done=True, export_payload=[])
                return self._end_reply

            end_output: Any
            try:
                end_output = await pipeline.end_service()
            except Exception:
                logger.exception("[pipeline] end_service() falhou")
                end_output = []

            self._end_reply = BackendReply(
                stage="DONE",
                text="",
                done=True,
                export_payload=end_output if end_output is not None else [],
            )

            if self._timeline is not None:
                self._timeline.emit(
                    "backend_end_completed",
                    backend="langgraph",
                    result_type=type(self._end_reply.export_payload).__name__,
                )
            return self._end_reply
