"""
Doit Agent - Telegram Interface
Full Telegram bot with network-loss tolerance, retry, and injection protection.
"""
from __future__ import annotations

import asyncio
import json
import logging
import time
from typing import Any, Callable, Optional

from telegram import Bot, Update
from telegram.ext import (
    Application, CommandHandler, MessageHandler,
    ContextTypes, filters
)
from telegram.error import NetworkError, TimedOut, TelegramError, RetryAfter

from core.config import TELEGRAM_RETRY_MAX
from security.security import AuthorizationManager, InjectionGuard
from task_engine.engine import get_engine, Task
from persistence.database import get_db

logger = logging.getLogger("doit.telegram")


def _fmt_result(result: Any, description: str = "") -> str:
    """Format a task result into a readable Telegram message."""
    if result is None:
        return f"✅ Done: {description}"

    if isinstance(result, dict):
        if "error" in result:
            return f"❌ Error: {result['error']}"

        # Format common results
        lines = [f"✅ {description}"] if description else ["✅ Done"]

        def _val(v):
            if isinstance(v, (dict, list)):
                return json.dumps(v, indent=2)[:300]
            return str(v)

        for k, v in result.items():
            if k in ("success",):
                continue
            lines.append(f"  • {k}: {_val(v)}")
        return "\n".join(lines[:30])  # Truncate for Telegram

    return f"✅ {description}\n{str(result)[:500]}"


class TelegramBot:
    """
    Single-user Telegram bot with network resilience.
    - Exponential backoff on network errors
    - Authorization enforcement on every message
    - Injection guard on every message
    - Task result delivery
    """

    def __init__(self, token: str, auth: AuthorizationManager, ai_engine, safe_mode_callback):
        self._token = token
        self._auth = auth
        self._ai = ai_engine
        self._safe_mode_cb = safe_mode_callback
        self._app: Optional[Application] = None
        self._bot: Optional[Bot] = None
        self._safe_mode = False
        self._result_queue: asyncio.Queue = asyncio.Queue()

    async def start(self) -> None:
        """Start the bot with retry on network failure."""
        while True:
            try:
                await self._run()
            except (NetworkError, TimedOut) as e:
                logger.warning("Network error: %s — retrying...", e)
                await self._backoff_sleep()
            except Exception as e:
                logger.error("Bot crashed: %s — restarting in 10s", e)
                await asyncio.sleep(10)

    async def _run(self) -> None:
        self._app = (
            Application.builder()
            .token(self._token)
            .build()
        )
        self._bot = self._app.bot

        # Register handlers
        self._app.add_handler(CommandHandler("start", self._cmd_start))
        self._app.add_handler(CommandHandler("status", self._cmd_status))
        self._app.add_handler(CommandHandler("tasks", self._cmd_tasks))
        self._app.add_handler(CommandHandler("logs", self._cmd_logs))
        self._app.add_handler(CommandHandler("health", self._cmd_health))
        self._app.add_handler(CommandHandler("export", self._cmd_export))
        self._app.add_handler(CommandHandler("help", self._cmd_help))
        self._app.add_handler(
            MessageHandler(filters.TEXT & ~filters.COMMAND, self._handle_message)
        )

        # Register task result callback
        engine = get_engine()
        engine.on_result(self._on_task_complete)

        logger.info("Telegram bot starting")
        async with self._app:
            await self._app.start()
            await self._app.updater.start_polling(
                drop_pending_updates=True,
                timeout=30,
                allowed_updates=["message"],
            )
            # Keep running
            while True:
                await asyncio.sleep(1)

    async def _backoff_sleep(self, attempt: int = 1) -> None:
        wait = min(2 ** attempt, TELEGRAM_RETRY_MAX)
        logger.info("Backoff: sleeping %ds", wait)
        await asyncio.sleep(wait)

    def _is_authorized(self, update: Update) -> bool:
        if not update.effective_user:
            return False
        return self._auth.is_authorized(update.effective_user.id)

    async def _deny(self, update: Update) -> None:
        try:
            await update.message.reply_text(self._auth.deny_message())
        except Exception:
            pass

    async def _cmd_start(self, update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
        if not self._is_authorized(update):
            await self._deny(update)
            return
        await update.message.reply_text(
            "🏋️ Doit is active and ready.\n\n"
            "Just tell me what to do in plain language. Examples:\n"
            "  • list files in ~/Downloads\n"
            "  • download https://example.com/file.pdf to ~/Desktop\n"
            "  • monitor https://mysite.com every 5 minutes\n"
            "  • show system health\n"
            "  • backup ~/Documents to ~/Backups\n\n"
            "Type /help for commands."
        )

    async def _cmd_help(self, update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
        if not self._is_authorized(update):
            await self._deny(update)
            return
        await update.message.reply_text(
            "📖 Doit Commands:\n\n"
            "/status — Agent status\n"
            "/tasks — Recent tasks\n"
            "/health — System health\n"
            "/logs — Recent logs\n"
            "/export json|csv — Export audit log\n"
            "/help — This message\n\n"
            "Or just type naturally:\n"
            "  'safe mode' — Pause all execution\n"
            "  'resume' — Resume from safe mode\n"
            "  'every day at 9am backup ~/Documents to ~/Backups'"
        )

    async def _cmd_status(self, update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
        if not self._is_authorized(update):
            await self._deny(update)
            return
        engine = get_engine()
        s = engine.status()
        mode = "🔴 SAFE MODE" if s["safe_mode"] else "🟢 Active"
        msg = (
            f"🏋️ Doit Status: {mode}\n"
            f"Queue: {s['queue_size']} pending\n"
            f"Running: {s['running']} tasks\n"
            f"Workers: {s['workers']}"
        )
        if s["running_tasks"]:
            running = "\n".join(f"  • {t['type']} ({t['id'][:8]})"
                                for t in s["running_tasks"])
            msg += f"\nActive:\n{running}"
        await update.message.reply_text(msg)

    async def _cmd_tasks(self, update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
        if not self._is_authorized(update):
            await self._deny(update)
            return
        db = await get_db()
        tasks = await db.tasks_list(limit=10)
        if not tasks:
            await update.message.reply_text("No tasks found.")
            return
        lines = ["📋 Recent Tasks:"]
        for t in tasks:
            status_icon = {"completed": "✅", "failed": "❌", "running": "⚙️", "pending": "⏳"}.get(t["status"], "❓")
            lines.append(f"{status_icon} {t['type']} [{t['status']}] ({t['id'][:8]})")
        await update.message.reply_text("\n".join(lines))

    async def _cmd_health(self, update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
        if not self._is_authorized(update):
            await self._deny(update)
            return
        import psutil
        cpu = psutil.cpu_percent(interval=1)
        mem = psutil.virtual_memory()
        disk = psutil.disk_usage("/")
        msg = (
            f"💻 System Health:\n"
            f"CPU: {cpu:.1f}%\n"
            f"RAM: {mem.percent:.1f}% ({mem.used//1e9:.1f}/{mem.total//1e9:.1f} GB)\n"
            f"Disk: {disk.percent:.1f}% ({disk.used//1e9:.1f}/{disk.total//1e9:.1f} GB)"
        )
        await update.message.reply_text(msg)

    async def _cmd_logs(self, update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
        if not self._is_authorized(update):
            await self._deny(update)
            return
        db = await get_db()
        logs = await db.logs_recent(10)
        if not logs:
            await update.message.reply_text("No logs.")
            return
        lines = ["📄 Recent Logs:"]
        for log in reversed(logs):
            lines.append(f"[{log['level']}] {log['module']}: {log['message'][:80]}")
        await update.message.reply_text("\n".join(lines))

    async def _cmd_export(self, update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
        if not self._is_authorized(update):
            await self._deny(update)
            return
        args = ctx.args
        fmt = args[0].lower() if args else "json"
        if fmt not in ("json", "csv"):
            await update.message.reply_text("Usage: /export json|csv")
            return
        db = await get_db()
        data = await db.audit_export(fmt)
        ext = fmt
        filename = f"doit_audit.{ext}"
        # Send as file
        import io
        bio = io.BytesIO(data.encode())
        bio.name = filename
        await update.message.reply_document(bio, filename=filename)

    async def _handle_message(self, update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
        if not self._is_authorized(update):
            await self._deny(update)
            return

        text = (update.message.text or "").strip()
        if not text:
            return

        # Log the incoming message
        db = await get_db()
        await db.audit("user_message", actor=str(update.effective_user.id),
                       details={"text": text[:200]})

        # Injection guard
        safe, reason = InjectionGuard.check_prompt(text)
        if not safe:
            logger.warning("Injection attempt detected: %s", reason)
            await update.message.reply_text(
                "⚠️ That message looks like an attempt to override my instructions. "
                "Please describe what you actually want to do."
            )
            return

        # Safe mode control
        lower = text.lower()
        if lower in ("safe mode", "safemode", "pause"):
            self._safe_mode = True
            get_engine().enter_safe_mode()
            await update.message.reply_text("🔴 Safe mode ON. All task execution suspended.\nType 'resume' to continue.")
            return

        if lower in ("resume", "continue", "exit safe mode"):
            self._safe_mode = False
            get_engine().exit_safe_mode()
            await update.message.reply_text("🟢 Resuming normal operation.")
            return

        if self._safe_mode:
            await update.message.reply_text("🔴 Safe mode is ON. Type 'resume' to allow execution.")
            return

        # Parse intent via AI
        await update.message.reply_text("⚙️ Processing...")
        try:
            action = await self._ai.parse_intent(text)
        except Exception as e:
            logger.error("AI parse error: %s", e)
            await update.message.reply_text(f"❌ AI error: {e}")
            return

        tool = action.get("tool")
        description = action.get("description", "")

        # Handle special non-execution actions
        if tool == "clarify":
            await update.message.reply_text(f"❓ {action.get('args', {}).get('message', 'Please clarify.')}")
            return

        if tool == "error":
            await update.message.reply_text(f"❌ {action.get('args', {}).get('message', 'An error occurred.')}")
            return

        # Handle schedule actions inline
        if tool == "schedule_add":
            from scheduler.scheduler import get_scheduler
            args = action.get("args", {})
            scheduler = get_scheduler()
            # natural_language should only be a string expression, not an int
            nl_expr = args.get("natural_language") or args.get("cron")
            if isinstance(nl_expr, int):
                nl_expr = None
            # interval_s must be int or None
            interval_s = args.get("interval_s")
            if isinstance(interval_s, str):
                try:
                    interval_s = int(interval_s)
                except (ValueError, TypeError):
                    interval_s = None
            result = await scheduler.add_schedule(
                name=args.get("name", "Unnamed"),
                task_type="tool_call",
                task_payload={"tool": args.get("task_type", ""), "args": args.get("task_args", {})},
                description=args.get("description", ""),
                natural_language=nl_expr,
                cron_expr=args.get("cron_expr"),
                interval_s=interval_s,
            )
            if "error" in result:
                await update.message.reply_text(f"❌ {result['error']}")
            else:
                await update.message.reply_text(
                    f"📅 Schedule created: {result['name']}\nNext run: {result.get('next_run', 'N/A')}"
                )
            return

        if tool == "schedule_list":
            from scheduler.scheduler import get_scheduler
            schedules = await get_scheduler().list_schedules()
            if not schedules:
                await update.message.reply_text("No schedules.")
            else:
                lines = ["📅 Schedules:"]
                for s in schedules:
                    status = "✅" if s["enabled"] else "⏸️"
                    lines.append(f"{status} {s['name']} — next: {s.get('next_run', 'N/A')}")
                await update.message.reply_text("\n".join(lines))
            return

        if tool == "schedule_remove":
            from scheduler.scheduler import get_scheduler
            sid = action.get("args", {}).get("id")
            ok = await get_scheduler().remove_schedule(sid)
            await update.message.reply_text("✅ Schedule removed." if ok else "❌ Schedule not found.")
            return

        if tool == "status":
            # Inline status
            engine = get_engine()
            s = engine.status()
            mode = "🔴 SAFE MODE" if s["safe_mode"] else "🟢 Active"
            await update.message.reply_text(
                f"🏋️ Doit: {mode}\nQueue: {s['queue_size']}\nRunning: {s['running']}"
            )
            return

        if tool == "tasks_list":
            args = action.get("args", {})
            db_conn = await get_db()
            tasks = await db_conn.tasks_list(status=args.get("status"), limit=10)
            lines = ["📋 Tasks:"] + [
                f"{'✅' if t['status']=='completed' else '❌' if t['status']=='failed' else '⚙️'} "
                f"{t['type']} [{t['status']}]"
                for t in tasks
            ]
            await update.message.reply_text("\n".join(lines) if len(lines) > 1 else "No tasks.")
            return

        if tool == "logs_show":
            db_conn = await get_db()
            limit = action.get("args", {}).get("limit", 10)
            logs = await db_conn.logs_recent(limit)
            lines = ["📄 Logs:"] + [f"[{l['level']}] {l['message'][:80]}" for l in logs]
            await update.message.reply_text("\n".join(lines[:20]))
            return

        if tool == "export_audit":
            fmt = action.get("args", {}).get("format", "json")
            db_conn = await get_db()
            data = await db_conn.audit_export(fmt)
            import io
            bio = io.BytesIO(data.encode())
            bio.name = f"audit.{fmt}"
            await update.message.reply_document(bio, filename=f"doit_audit.{fmt}")
            return

        if tool == "safe_mode":
            get_engine().enter_safe_mode()
            self._safe_mode = True
            await update.message.reply_text("🔴 Safe mode ON.")
            return

        if tool == "resume":
            get_engine().exit_safe_mode()
            self._safe_mode = False
            await update.message.reply_text("🟢 Resumed.")
            return

        # Submit task to engine
        engine = get_engine()
        task = await engine.submit_action(action)
        if task:
            await update.message.reply_text(
                f"⚙️ Task queued: {description or task.type} (ID: {task.id[:8]})"
            )
        else:
            # Direct execution for simple queries
            await update.message.reply_text("✅ Done.")

    async def _on_task_complete(self, task: Task) -> None:
        """Called when a task completes. Sends result to user."""
        uid = self._auth.authorized_user_id()
        if uid is None or self._bot is None:
            return

        result = task.result
        description = ""
        if isinstance(task.payload, dict):
            description = task.payload.get("description", task.type)

        if task.status == "completed":
            msg = _fmt_result(result, description)
        elif task.status == "failed":
            msg = f"❌ Task failed: {task.error or 'Unknown error'}\n(ID: {task.id[:8]})"
        else:
            return

        # Retry sending notification with backoff
        for attempt in range(5):
            try:
                await self._bot.send_message(chat_id=uid, text=msg[:4000])
                return
            except (NetworkError, TimedOut):
                await asyncio.sleep(2 ** attempt)
            except RetryAfter as e:
                await asyncio.sleep(e.retry_after)
            except TelegramError as e:
                logger.warning("Failed to send result: %s", e)
                return

    async def send_message(self, text: str) -> bool:
        """Send a message to the authorized user."""
        uid = self._auth.authorized_user_id()
        if uid is None:
            return False
        for attempt in range(5):
            try:
                await self._bot.send_message(chat_id=uid, text=text[:4000])
                return True
            except (NetworkError, TimedOut):
                await asyncio.sleep(2 ** attempt)
            except Exception as e:
                logger.warning("Send failed: %s", e)
                return False
        return False


async def validate_bot_token(token: str) -> tuple[bool, str]:
    """Validate a Telegram bot token."""
    try:
        bot = Bot(token=token)
        info = await bot.get_me()
        return True, f"@{info.username}"
    except Exception as e:
        return False, str(e)


async def send_test_message(token: str, user_id: int) -> tuple[bool, str]:
    """Send a test message to confirm the user ID."""
    try:
        bot = Bot(token=token)
        await bot.send_message(
            chat_id=user_id,
            text="🏋️ Doit test message — if you see this, your setup is working!"
        )
        return True, "Test message sent"
    except Exception as e:
        return False, str(e)
