"""Telegram bot service for LinkedIn post creation via chat."""
import asyncio
from datetime import datetime, timezone
from typing import Optional
from uuid import UUID

import httpx
from loguru import logger

from src.config import settings
from src.services.redis_client import get_redis

# Conversation state TTL: 24 hours
CONV_TTL = 86400
# Rate limit: max requests per hour per user
RATE_LIMIT_MAX = 10
RATE_LIMIT_TTL = 3600


class TelegramService:
    """Handles Telegram bot interactions for LinkedIn post creation."""

    def __init__(self):
        self._base_url = f"https://api.telegram.org/bot{settings.telegram_bot_token}"

    # ==================== TELEGRAM API HELPERS ====================

    async def send_message(self, chat_id: str, text: str, reply_markup: Optional[dict] = None) -> dict:
        """Send a text message to a Telegram chat."""
        payload: dict = {"chat_id": chat_id, "text": text, "parse_mode": "HTML"}
        if reply_markup:
            payload["reply_markup"] = reply_markup
        async with httpx.AsyncClient(timeout=15.0) as client:
            resp = await client.post(f"{self._base_url}/sendMessage", json=payload)
            return resp.json()

    async def answer_callback_query(self, callback_query_id: str, text: str = "") -> None:
        """Answer a callback query to dismiss the loading indicator."""
        async with httpx.AsyncClient(timeout=10.0) as client:
            await client.post(f"{self._base_url}/answerCallbackQuery", json={
                "callback_query_id": callback_query_id,
                "text": text
            })

    async def edit_message(self, chat_id: str, message_id: int, text: str, reply_markup: Optional[dict] = None) -> dict:
        """Edit an existing message."""
        payload: dict = {"chat_id": chat_id, "message_id": message_id, "text": text, "parse_mode": "HTML"}
        if reply_markup:
            payload["reply_markup"] = reply_markup
        async with httpx.AsyncClient(timeout=15.0) as client:
            resp = await client.post(f"{self._base_url}/editMessageText", json=payload)
            return resp.json()

    async def register_webhook(self, url: str, secret: str) -> None:
        """Register webhook URL with Telegram."""
        async with httpx.AsyncClient(timeout=15.0) as client:
            resp = await client.post(f"{self._base_url}/setWebhook", json={
                "url": url,
                "secret_token": secret,
                "allowed_updates": ["message", "callback_query"]
            })
            data = resp.json()
            if data.get("ok"):
                logger.info(f"Telegram webhook registered: {url}")
            else:
                logger.error(f"Failed to register Telegram webhook: {data}")

    # ==================== CONVERSATION STATE ====================

    def _conv_key(self, chat_id: str) -> str:
        return f"telegram_conv:{chat_id}"

    async def _get_conv(self, chat_id: str) -> dict:
        """Get conversation state from Redis."""
        try:
            redis = await get_redis()
            raw = await redis.get(self._conv_key(chat_id))
            if raw:
                import json
                return json.loads(raw)
        except Exception as e:
            logger.warning(f"Failed to get telegram conv state: {e}")
        return {"state": "idle"}

    async def _set_conv(self, chat_id: str, data: dict) -> None:
        """Save conversation state to Redis with 24h TTL."""
        try:
            redis = await get_redis()
            import json
            await redis.setex(self._conv_key(chat_id), CONV_TTL, json.dumps(data))
        except Exception as e:
            logger.warning(f"Failed to set telegram conv state: {e}")

    async def _clear_conv(self, chat_id: str) -> None:
        """Delete conversation state from Redis."""
        try:
            redis = await get_redis()
            await redis.delete(self._conv_key(chat_id))
        except Exception as e:
            logger.warning(f"Failed to clear telegram conv state: {e}")

    # ==================== RATE LIMITING ====================

    async def _check_rate_limit(self, user_id: str) -> bool:
        """Return True if request is allowed, False if rate limit exceeded."""
        try:
            redis = await get_redis()
            now = datetime.now(timezone.utc)
            key = f"telegram_rate:{user_id}:{now.strftime('%Y%m%d%H')}"
            count = await redis.incr(key)
            if count == 1:
                await redis.expire(key, RATE_LIMIT_TTL)
            return count <= RATE_LIMIT_MAX
        except Exception as e:
            logger.warning(f"Rate limit check failed (allowing): {e}")
            return True

    # ==================== KEYBOARD BUILDERS ====================

    def _post_type_keyboard(self, post_types: list) -> dict:
        """Build inline keyboard for post type selection."""
        buttons = []
        for pt in post_types:
            buttons.append([{"text": pt.name, "callback_data": f"posttype:{pt.id}"}])
        return {"inline_keyboard": buttons}

    def _action_keyboard(self) -> dict:
        """Build inline keyboard for post actions."""
        return {"inline_keyboard": [[
            {"text": "✏️ Überarbeiten", "callback_data": "revise"},
            {"text": "🔄 Neuer Post", "callback_data": "newpost"}
        ]]}

    # ==================== MAIN DISPATCHER ====================

    async def handle_update(self, update: dict, db) -> None:
        """Route incoming Telegram update to the right handler."""
        try:
            if "message" in update:
                msg = update["message"]
                chat_id = str(msg["chat"]["id"])
                text = msg.get("text", "")
                await self._handle_message(chat_id, text, db)

            elif "callback_query" in update:
                cq = update["callback_query"]
                chat_id = str(cq["message"]["chat"]["id"])
                cb_id = cq["id"]
                data = cq.get("data", "")
                message_id = cq["message"]["message_id"]
                await self.answer_callback_query(cb_id)
                await self._handle_callback_query(chat_id, cb_id, data, message_id, db)

        except Exception as e:
            logger.exception(f"Error handling Telegram update: {e}")

    # ==================== MESSAGE HANDLER ====================

    async def _handle_message(self, chat_id: str, text: str, db) -> None:
        """Handle incoming text messages."""
        # Look up linked account
        tg_account = await db.get_telegram_account_by_chat_id(chat_id)

        # Handle /start {token} — account linking flow
        if text.startswith("/start"):
            parts = text.split(maxsplit=1)
            token = parts[1].strip() if len(parts) > 1 else ""
            await self._handle_start(chat_id, token, db)
            return

        # All other messages require a linked account
        if not tg_account:
            await self.send_message(
                chat_id,
                "Dein Telegram-Konto ist noch nicht verknüpft.\n"
                "Öffne die App unter <b>Einstellungen → Telegram verbinden</b> und folge den Anweisungen."
            )
            return

        user_id = str(tg_account.user_id)

        # Rate limiting
        if not await self._check_rate_limit(user_id):
            await self.send_message(chat_id, "⚠️ Zu viele Anfragen. Bitte warte eine Stunde und versuche es erneut.")
            return

        conv = await self._get_conv(chat_id)
        state = conv.get("state", "idle")

        if state == "waiting_feedback":
            await self._handle_feedback(chat_id, user_id, text, conv, db)
        else:
            # Treat any other text as a new topic
            await self._handle_new_topic(chat_id, user_id, text, db)

    # ==================== START / ACCOUNT LINKING ====================

    async def _handle_start(self, chat_id: str, token: str, db) -> None:
        """Handle /start command — link account if token provided."""
        if not token:
            tg_account = await db.get_telegram_account_by_chat_id(chat_id)
            if tg_account:
                await self.send_message(chat_id, "Du bist bereits verbunden! Schreib mir ein Thema für deinen nächsten LinkedIn-Post.")
            else:
                await self.send_message(
                    chat_id,
                    "Willkommen! 👋\n\nUm diesen Bot zu nutzen, verknüpfe zuerst dein Konto in der App unter "
                    "<b>Einstellungen → Telegram verbinden</b>."
                )
            return

        # Look up one-time token in Redis
        try:
            redis = await get_redis()
            link_key = f"telegram_link:{token}"
            raw_user_id = await redis.get(link_key)
            if not raw_user_id:
                await self.send_message(chat_id, "❌ Dieser Link ist ungültig oder abgelaufen. Bitte erstelle einen neuen Link in der App.")
                return

            user_id_str = raw_user_id.decode() if isinstance(raw_user_id, bytes) else raw_user_id
            await redis.delete(link_key)
        except Exception as e:
            logger.error(f"Failed to look up telegram link token: {e}")
            await self.send_message(chat_id, "❌ Ein Fehler ist aufgetreten. Bitte versuche es erneut.")
            return

        # Save or update the telegram account
        from src.database.models import TelegramAccount
        account = TelegramAccount(
            user_id=UUID(user_id_str),
            telegram_chat_id=chat_id,
            telegram_user_id=chat_id,  # chat_id is sufficient as unique identifier
            is_active=True
        )
        await db.save_telegram_account(account)
        await self._clear_conv(chat_id)

        await self.send_message(
            chat_id,
            "✅ <b>Verbunden!</b>\n\nSchreib mir ein Thema für deinen nächsten LinkedIn-Post und ich erstelle ihn für dich."
        )

    # ==================== TOPIC HANDLING ====================

    async def _handle_new_topic(self, chat_id: str, user_id: str, topic_text: str, db) -> None:
        """Handle a new post topic — show post type selection."""
        post_types = await db.get_post_types(UUID(user_id))
        active_types = [pt for pt in post_types if pt.is_active]

        if not active_types:
            await self.send_message(chat_id, "⚠️ Keine Post-Typen gefunden. Bitte richte zuerst deinen Account in der App ein.")
            return

        conv = {
            "state": "waiting_post_type",
            "user_id": user_id,
            "topic": topic_text
        }
        await self._set_conv(chat_id, conv)

        await self.send_message(
            chat_id,
            f"📝 Thema: <b>{topic_text}</b>\n\nWähle einen Post-Typ:",
            reply_markup=self._post_type_keyboard(active_types)
        )

    # ==================== TOKEN LIMIT CHECK ====================

    async def _check_token_limit(self, user_id: str, db) -> tuple[bool, str]:
        """Check company token limit for the user. Returns (can_proceed, error_msg)."""
        try:
            profile = await db.get_profile(UUID(user_id))
            if profile and profile.company_id:
                can_proceed, error_msg, _, _ = await db.check_company_token_limit(profile.company_id)
                return can_proceed, error_msg
        except Exception as e:
            logger.warning(f"Token limit check failed (allowing): {e}")
        return True, ""

    # ==================== FEEDBACK HANDLING ====================

    async def _handle_feedback(self, chat_id: str, user_id: str, feedback: str, conv: dict, db) -> None:
        """Handle user feedback to revise a post."""
        post_id = conv.get("post_id")
        post_content = conv.get("post_content", "")

        if not post_content:
            await self.send_message(chat_id, "❌ Kein Post gefunden. Bitte starte von vorne mit einem neuen Thema.")
            await self._clear_conv(chat_id)
            return

        # Check token limit before calling the LLM
        can_proceed, limit_msg = await self._check_token_limit(user_id, db)
        if not can_proceed:
            await self.send_message(chat_id, f"⚠️ {limit_msg}")
            return

        await self.send_message(chat_id, "⏳ Überarbeite deinen Post...")

        try:
            from src.orchestrator import orchestrator
            improved = await orchestrator.apply_suggestion_to_post(
                user_id=UUID(user_id),
                post_content=post_content,
                suggestion=feedback
            )

            # Update post in DB: append new version, increment iteration counter
            if post_id:
                existing = await db.get_generated_post(UUID(post_id))
                if existing:
                    new_versions = list(existing.writer_versions) + [improved]
                    await db.update_generated_post(UUID(post_id), {
                        "post_content": improved,
                        "writer_versions": new_versions,
                        "iterations": existing.iterations + 1,
                    })

            # Update conversation state
            conv["post_content"] = improved
            conv["state"] = "waiting_feedback"
            await self._set_conv(chat_id, conv)

            await self.send_message(
                chat_id,
                f"✨ <b>Überarbeiteter Post:</b>\n\n{improved}",
                reply_markup=self._action_keyboard()
            )

        except Exception as e:
            logger.error(f"Failed to apply feedback for user {user_id}: {e}")
            await self.send_message(chat_id, "❌ Fehler beim Überarbeiten. Bitte versuche es erneut.")

    # ==================== CALLBACK HANDLER ====================

    async def _handle_callback_query(self, chat_id: str, cb_id: str, data: str, message_id: int, db) -> None:
        """Handle inline keyboard button presses."""
        # approve/reject callbacks are token-authenticated — no account lookup needed
        if data.startswith("approve:") or data.startswith("reject:"):
            action, token = data.split(":", 1)
            await self._handle_approval_callback(chat_id, message_id, action, token, db)
            return

        # All other callbacks require a linked account
        tg_account = await db.get_telegram_account_by_chat_id(chat_id)
        if not tg_account:
            return

        user_id = str(tg_account.user_id)
        conv = await self._get_conv(chat_id)

        if data.startswith("posttype:"):
            post_type_id_str = data.split(":", 1)[1]
            await self._handle_post_type_selected(chat_id, user_id, post_type_id_str, conv, message_id, db)

        elif data == "revise":
            conv["state"] = "waiting_feedback"
            await self._set_conv(chat_id, conv)
            await self.send_message(chat_id, "✏️ Alles klar, was soll ich verbessern?")

        elif data == "newpost":
            await self._clear_conv(chat_id)
            await self.send_message(chat_id, "🔄 Alles klar! Schreib mir dein neues Thema.")

    async def _handle_approval_callback(
        self, chat_id: str, message_id: int, action: str, token: str, db
    ) -> None:
        """Handle approve/reject callback from inline keyboard buttons."""
        from src.services.email_service import validate_token
        from uuid import UUID

        token_data = await validate_token(token)
        if not token_data:
            await self.edit_message(
                chat_id, message_id,
                "❌ Dieser Link ist ungültig oder wurde bereits verwendet."
            )
            return

        post_id = UUID(token_data["post_id"])
        post = await db.get_generated_post(post_id)
        if not post:
            await self.edit_message(chat_id, message_id, "❌ Post nicht gefunden.")
            return

        if action == "approve":
            new_status = "ready"
            result_text = (
                f"✅ <b>Post freigegeben!</b>\n\n"
                f"<i>{post.topic_title or 'Post'}</i>\n\n"
                f"Der Post kann jetzt eingeplant werden."
            )
        else:
            new_status = "draft"
            result_text = (
                f"❌ <b>Post abgelehnt.</b>\n\n"
                f"<i>{post.topic_title or 'Post'}</i>\n\n"
                f"Der Post wurde zurück in die Entwürfe verschoben."
            )

        await db.update_generated_post(post_id, {"status": new_status})
        # Invalidate both approve + reject tokens for this post
        await db.mark_all_post_tokens_used(post_id)

        # Edit the original message to show the result (removes buttons)
        await self.edit_message(chat_id, message_id, result_text)

    async def _handle_post_type_selected(
        self, chat_id: str, user_id: str, post_type_id_str: str, conv: dict, message_id: int, db
    ) -> None:
        """Generate a post after the user selects a post type (Writer-only, no critic loop)."""
        topic_text = conv.get("topic", "")
        if not topic_text:
            await self.send_message(chat_id, "❌ Kein Thema gefunden. Bitte starte von vorne.")
            await self._clear_conv(chat_id)
            return

        # Check token limit before calling the LLM
        can_proceed, limit_msg = await self._check_token_limit(user_id, db)
        if not can_proceed:
            await self.edit_message(chat_id, message_id, f"⚠️ {limit_msg}")
            await self._clear_conv(chat_id)
            return

        # Edit the post-type selection message to show progress
        await self.edit_message(chat_id, message_id, "⏳ Erstelle deinen Post...")

        try:
            user_uuid = UUID(user_id)

            # Load everything the writer needs
            post_type = await db.get_post_type(UUID(post_type_id_str))
            if not post_type:
                await self.send_message(chat_id, "❌ Post-Typ nicht gefunden.")
                return

            profile_analysis = await db.get_profile_analysis(user_uuid)
            if not profile_analysis:
                await self.send_message(chat_id, "❌ Profil-Analyse nicht gefunden. Bitte richte zuerst deinen Account in der App ein.")
                return

            # Style examples — prefer type-specific, fall back to all
            linkedin_posts = await db.get_posts_by_type(user_uuid, UUID(post_type_id_str))
            if len(linkedin_posts) < 3:
                linkedin_posts = await db.get_linkedin_posts(user_uuid)
            example_post_texts = [
                p.post_text for p in linkedin_posts
                if p.post_text and len(p.post_text) > 100
            ][:10]

            # Company strategy if available
            company_strategy = None
            profile = await db.get_profile(user_uuid)
            if profile and profile.company_id:
                company = await db.get_company(profile.company_id)
                if company and company.company_strategy:
                    company_strategy = company.company_strategy

            # Single writer pass — no critic loop
            from src.agents.writer import WriterAgent
            writer = WriterAgent()
            writer.set_tracking_context(
                operation="post_creation",
                user_id=user_id,
                company_id=str(profile.company_id) if profile and profile.company_id else None,
            )
            post_content = await writer.process(
                topic={"title": topic_text, "fact": topic_text, "relevance": "User-specified topic"},
                profile_analysis=profile_analysis.full_analysis,
                example_posts=example_post_texts,
                post_type=post_type,
                user_thoughts=topic_text,
                company_strategy=company_strategy,
                strategy_weight=post_type.strategy_weight,
            )

            # Use first sentence of generated post as title
            first_sentence = post_content.split("\n")[0].strip()
            if not first_sentence:
                first_sentence = post_content[:100].strip()

            # Save the post to DB (status=draft, no critic data)
            from src.database.models import GeneratedPost
            saved = await db.save_generated_post(GeneratedPost(
                user_id=user_uuid,
                topic_title=first_sentence[:200],
                post_content=post_content,
                iterations=1,
                writer_versions=[post_content],
                status="draft",
                post_type_id=UUID(post_type_id_str),
            ))

            # Update conversation state
            await self._set_conv(chat_id, {
                "state": "waiting_feedback",
                "user_id": user_id,
                "topic": topic_text,
                "post_id": str(saved.id),
                "post_content": post_content,
            })

            await self.send_message(
                chat_id,
                f"✨ <b>Generierter Post:</b>\n\n{post_content}",
                reply_markup=self._action_keyboard()
            )

        except Exception as e:
            logger.error(f"Failed to create post for user {user_id}: {e}")
            await self.send_message(chat_id, "❌ Fehler beim Erstellen des Posts. Bitte versuche es erneut.")
            await self._clear_conv(chat_id)


# Module-level singleton — only created when Telegram is enabled
telegram_service: Optional[TelegramService] = None
if settings.telegram_enabled:
    telegram_service = TelegramService()
