"""Telegram Bot long-polling listener — runs inside the daemon."""

import asyncio
import os
from pathlib import Path

import httpx
import structlog

from fliiq.runtime.agent.config import AgentConfig
from fliiq.runtime.agent.loop import agent_loop
from fliiq.runtime.agent.prompt import assemble_agent_prompt
from fliiq.runtime.agent.setup import setup_agent_resources
from fliiq.runtime.llm.providers import LLMConfig

log = structlog.get_logger()

TELEGRAM_API_BASE = "https://api.telegram.org"
POLL_TIMEOUT = 30  # seconds — Telegram holds connection open
MAX_MESSAGE_LEN = 4096  # Telegram's per-message limit
BACKOFF_BASE = 2
BACKOFF_MAX = 60


class TelegramListener:
    """Long-polls Telegram getUpdates and routes messages through agent_loop."""

    def __init__(
        self,
        bot_token: str,
        llm_config: LLMConfig,
        project_root: Path,
        allowed_chat_ids: set[int] | None = None,
    ):
        self._token = bot_token
        self._llm_config = llm_config
        self._project_root = project_root
        self._allowed_chat_ids = allowed_chat_ids
        self._offset = 0
        self._running = False
        self._sessions: dict[int, dict] = {}  # chat_id → {"messages": [...], "resources": ...}

    @property
    def api_base(self) -> str:
        return f"{TELEGRAM_API_BASE}/bot{self._token}"

    async def send_message(self, chat_id: int, text: str) -> None:
        """Send a text message. Splits if >4096 chars."""
        chunks = _split_message(text)
        async with httpx.AsyncClient(timeout=15) as client:
            for chunk in chunks:
                resp = await client.post(
                    f"{self.api_base}/sendMessage",
                    json={"chat_id": chat_id, "text": chunk, "parse_mode": "Markdown"},
                )
                if resp.status_code != 200:
                    # Retry without Markdown if parse error
                    if resp.status_code == 400 and "parse" in resp.text.lower():
                        await client.post(
                            f"{self.api_base}/sendMessage",
                            json={"chat_id": chat_id, "text": chunk},
                        )
                    else:
                        log.warning(
                            "telegram_send_failed",
                            chat_id=chat_id,
                            status=resp.status_code,
                            error=resp.text[:200],
                        )

    async def _poll_updates(self, client: httpx.AsyncClient) -> list[dict]:
        """Long-poll getUpdates. Returns list of Update objects."""
        resp = await client.get(
            f"{self.api_base}/getUpdates",
            params={"offset": self._offset, "timeout": POLL_TIMEOUT},
            timeout=POLL_TIMEOUT + 10,  # httpx timeout > Telegram timeout
        )
        if resp.status_code != 200:
            log.warning("telegram_poll_error", status=resp.status_code)
            return []

        data = resp.json()
        updates = data.get("result", [])
        if updates:
            self._offset = updates[-1]["update_id"] + 1
        return updates

    async def _get_or_create_session(self, chat_id: int) -> dict:
        """Get or create a per-chat session with agent resources."""
        if chat_id not in self._sessions:
            def _noop_ask_user(question: str) -> str:
                return "Not available — this is a Telegram bot session."

            def _noop_ask_user_choice(question: str, options: list[dict], recommended: int) -> str:
                if options and recommended and 1 <= recommended <= len(options):
                    picked = options[recommended - 1]
                    return f"Auto-selected: {picked['label']}"
                return "Auto-selected first option"

            resources = await setup_agent_resources(
                mode="autonomous",
                llm_config=self._llm_config,
                project_root=self._project_root,
                ask_user_handler=_noop_ask_user,
                ask_user_choice_handler=_noop_ask_user_choice,
            )

            self._sessions[chat_id] = {
                "messages": [],
                "resources": resources,
            }

        return self._sessions[chat_id]

    async def _handle_message(self, chat_id: int, text: str) -> None:
        """Process a single inbound message through agent_loop and reply."""
        session = await self._get_or_create_session(chat_id)
        resources = session["resources"]
        messages = session["messages"]

        tagged = f"<external_message source=\"telegram\">\n{text}\n</external_message>"
        messages.append({"role": "user", "content": tagged})

        system = assemble_agent_prompt(
            soul=resources.soul,
            user_soul=resources.user_soul,
            skills=resources.skill_info if resources.skill_info else None,
            mode="autonomous",
            memory_context=resources.memory_context,
            user_profile=resources.user_profile,
            fliiq_email=resources.fliiq_email,
        )

        config = AgentConfig(mode="autonomous")

        try:
            result = await agent_loop(
                llm=resources.llm,
                messages=messages,
                tools=resources.tools,
                config=config,
                system=system,
            )
            session["messages"] = result.messages
            response = result.final_text or "(No response)"
        except Exception as e:
            log.error("telegram_agent_error", chat_id=chat_id, error=str(e))
            response = f"Error processing your message: {e}"

        await self.send_message(chat_id, response)

    async def run(self) -> None:
        """Main loop: long-poll → process → reply. Runs until stop() is called."""
        self._running = True
        backoff = 0
        log.info("telegram_listener_running")

        async with httpx.AsyncClient() as client:
            while self._running:
                try:
                    updates = await self._poll_updates(client)
                    backoff = 0  # Reset on success

                    for update in updates:
                        msg = update.get("message", {})
                        chat_id = msg.get("chat", {}).get("id")
                        text = msg.get("text")

                        if not chat_id or not text:
                            continue

                        if self._allowed_chat_ids and chat_id not in self._allowed_chat_ids:
                            log.debug("telegram_chat_blocked", chat_id=chat_id)
                            await self.send_message(
                                chat_id,
                                "This Fliiq instance is not configured to respond to this chat.",
                            )
                            continue

                        try:
                            await self._handle_message(chat_id, text)
                        except Exception as e:
                            log.error("telegram_message_handler_error", chat_id=chat_id, error=str(e))

                except httpx.TimeoutException:
                    continue  # Normal — long poll timeout, retry immediately
                except Exception as e:
                    backoff = min(BACKOFF_BASE ** (backoff + 1), BACKOFF_MAX)
                    log.warning("telegram_poll_exception", error=str(e), backoff=backoff)
                    await asyncio.sleep(backoff)

        log.info("telegram_listener_stopped")

    async def stop(self) -> None:
        """Signal the run loop to exit."""
        self._running = False


def _split_message(text: str) -> list[str]:
    """Split text into chunks of MAX_MESSAGE_LEN or fewer."""
    if len(text) <= MAX_MESSAGE_LEN:
        return [text]

    chunks = []
    while text:
        if len(text) <= MAX_MESSAGE_LEN:
            chunks.append(text)
            break
        # Try to split at newline
        split_at = text.rfind("\n", 0, MAX_MESSAGE_LEN)
        if split_at == -1:
            split_at = MAX_MESSAGE_LEN
        chunks.append(text[:split_at])
        text = text[split_at:].lstrip("\n")
    return chunks


def parse_allowed_chat_ids() -> set[int] | None:
    """Parse TELEGRAM_ALLOWED_CHAT_IDS from env. Returns None if not set."""
    raw = os.environ.get("TELEGRAM_ALLOWED_CHAT_IDS", "").strip()
    if not raw:
        return None
    try:
        return {int(cid.strip()) for cid in raw.split(",") if cid.strip()}
    except ValueError:
        log.warning("telegram_invalid_allowed_chat_ids", raw=raw)
        return None
