# Familiar - Self-Hosted AI Companion Platform
# Copyright (c) 2026 George Scott Foley
# ORCID: 0009-0006-4957-0540
# Email: Georgescottfoley@proton.me
# Licensed under the MIT License - see LICENSE file for details

"""
Discord Channel

Run the Familiar as a Discord bot.

Features:
- Respond to DMs
- Respond to mentions in servers
- Slash commands
- Per-user conversation history
- Server-specific settings

Phase 1 (v1.6): Multi-user support via UserManager

Setup:
1. Create a Discord app at https://discord.com/developers/applications
2. Create a bot and get the token
3. Enable MESSAGE CONTENT INTENT in bot settings
4. Invite bot to server with appropriate permissions
5. Set DISCORD_BOT_TOKEN environment variable

Required Permissions:
- Send Messages
- Read Message History
- Use Slash Commands
- Add Reactions (optional, for feedback)

Invite URL format:
https://discord.com/api/oauth2/authorize?client_id=YOUR_CLIENT_ID&permissions=274877975552&scope=bot%20applications.commands
"""

import asyncio
import logging
import os
from typing import Optional

logger = logging.getLogger(__name__)


# Token read at class init time, not module import time
def _get_discord_token():
    return os.environ.get("DISCORD_BOT_TOKEN", "")


# Phase 1: Multi-user authentication
from .auth import ChannelType, ChannelUser, create_authenticator  # noqa: E402
from .connect_wizard import ConnectMixin  # noqa: E402
from .formatting import FormatMode  # noqa: E402


class DiscordChannel(ConnectMixin):
    """
    Discord bot integration for Familiar.

    Usage:
        channel = DiscordChannel(agent)
        channel.run()  # Blocking

        # Or async
        await channel.start()
    """

    # ConnectMixin adapter properties
    format_mode = FormatMode.MARKDOWN
    supports_buttons = True
    supports_message_deletion = False

    def __init__(
        self,
        agent,
        token: str = None,
        respond_to_dms: bool = True,
        respond_to_mentions: bool = True,
        command_prefix: str = "!agent",
        allowed_servers: list = None,
        allowed_channels: list = None,
        admin_users: list = None,
    ):
        """
        Initialize Discord channel.

        Args:
            agent: The Familiar instance
            token: Discord bot token (or use DISCORD_BOT_TOKEN env var)
            respond_to_dms: Whether to respond to direct messages
            respond_to_mentions: Whether to respond when mentioned
            command_prefix: Prefix for text commands (e.g., "!agent help")
            allowed_servers: List of server IDs to respond in (None = all)
            allowed_channels: List of channel IDs to respond in (None = all)
            admin_users: List of user IDs with admin privileges
        """
        self.agent = agent
        self.token = token or _get_discord_token()
        self.respond_to_dms = respond_to_dms
        self.respond_to_mentions = respond_to_mentions
        self.command_prefix = command_prefix
        self.allowed_servers = allowed_servers
        self.allowed_channels = allowed_channels
        self.admin_users = admin_users or []

        self.client = None
        self._ready = False

        # Phase 1: Multi-user authentication
        self.authenticator = create_authenticator(
            config=agent.config if hasattr(agent, "config") else None, allowed_ids=admin_users
        )

        # ConnectMixin: store Discord channel objects for wizard routing
        self._wizard_channels: dict[str, object] = {}

    # ── ConnectMixin adapter methods ────────────────────────────────

    async def wizard_send(self, recipient_id: str, text: str) -> None:
        channel = self._wizard_channels.get(recipient_id)
        if channel:
            await self._send_response(channel, text)

    async def wizard_send_menu(
        self, recipient_id: str, text: str, options: list[tuple[str, str]]
    ) -> None:
        try:
            import discord as _discord
        except ImportError:
            await self.wizard_send(recipient_id, text)
            return

        channel = self._wizard_channels.get(recipient_id)
        if not channel:
            return

        view = _discord.ui.View(timeout=300)
        for key, label in options[:25]:  # Discord max 25 components
            button = _discord.ui.Button(label=label[:80], custom_id=f"connect:{key}")

            async def callback(interaction, k=key):
                await interaction.response.defer()
                self._wizard_channels[recipient_id] = interaction.channel
                await self.handle_connect_menu_selection(recipient_id, k)

            button.callback = callback
            view.add_item(button)

        await channel.send(text, view=view)

    async def wizard_delete_message(self, recipient_id: str, message_ref) -> bool:
        try:
            await message_ref.delete()
            return True
        except Exception:
            return False

    def _get_channel_user(self, discord_user) -> Optional[ChannelUser]:
        """Get authenticated ChannelUser for Discord user."""
        return self.authenticator.authenticate(
            channel_type=ChannelType.DISCORD,
            channel_id=str(discord_user.id),
            display_name=discord_user.display_name or discord_user.name or "User",
        )

    def _setup_client(self):
        """Set up the Discord client."""
        try:
            import discord
            from discord import app_commands
        except ImportError:
            raise RuntimeError("discord.py not available. Check server logs.")

        # Set up intents
        intents = discord.Intents.default()
        intents.message_content = True
        intents.dm_messages = True

        self.client = discord.Client(intents=intents)
        self.tree = app_commands.CommandTree(self.client)

        # Register event handlers
        @self.client.event
        async def on_ready():
            logger.info(f"Discord bot logged in as {self.client.user}")
            self._ready = True

            # Sync slash commands
            try:
                synced = await self.tree.sync()
                logger.info(f"Synced {len(synced)} slash commands")
            except Exception as e:
                logger.error(f"Failed to sync commands: {e}")

            print(f"🤖 Discord bot ready: {self.client.user.name}")
            print(f"   Servers: {len(self.client.guilds)}")

        @self.client.event
        async def on_message(message):
            await self._handle_message(message)

        # Register slash commands
        @self.tree.command(name="ask", description="Ask the AI agent a question")
        async def ask_command(interaction: discord.Interaction, question: str):
            await self._handle_slash_command(interaction, question)

        @self.tree.command(name="help", description="Get help with the AI agent")
        async def help_command(interaction: discord.Interaction):
            await self._handle_help(interaction)

        @self.tree.command(name="status", description="Check agent status")
        async def status_command(interaction: discord.Interaction):
            await self._handle_status(interaction)

        @self.tree.command(name="clear", description="Clear your conversation history")
        async def clear_command(interaction: discord.Interaction):
            await self._handle_clear(interaction)

        # Phase 1: Multi-user authentication commands
        @self.tree.command(name="link", description="Link your Discord to your Familiar account")
        @app_commands.describe(email="Your email address")
        async def link_command(interaction: discord.Interaction, email: str):
            await self._handle_link(interaction, email)

        @self.tree.command(
            name="confirm", description="Confirm account link with verification code"
        )
        @app_commands.describe(code="6-digit verification code")
        async def confirm_command(interaction: discord.Interaction, code: str):
            await self._handle_confirm_link(interaction, code)

        @self.tree.command(name="unlink", description="Unlink your Discord from your account")
        async def unlink_command(interaction: discord.Interaction):
            await self._handle_unlink(interaction)

        @self.tree.command(name="whoami", description="Show your account information")
        async def whoami_command(interaction: discord.Interaction):
            await self._handle_whoami(interaction)

        @self.tree.command(name="trust", description="Show your trust level and progress")
        async def trust_command(interaction: discord.Interaction):
            await self._handle_trust(interaction)

        @self.tree.command(name="budget", description="Show your spending budget")
        async def budget_command(interaction: discord.Interaction):
            await self._handle_budget(interaction)

        @self.tree.command(name="caps", description="Show your capabilities")
        async def caps_command(interaction: discord.Interaction):
            await self._handle_caps(interaction)

        @self.tree.command(name="model", description="Show or switch the active LLM provider")
        @app_commands.describe(provider="Provider name (e.g. anthropic, ollama, openai)")
        async def model_command(interaction: discord.Interaction, provider: str = ""):
            await self._handle_model(interaction, provider)

        @self.tree.command(name="remember", description="Store a memory fact")
        @app_commands.describe(key="Memory key", value="Value to store")
        async def remember_command(interaction: discord.Interaction, key: str, value: str):
            await self._handle_remember(interaction, key, value)

        @self.tree.command(name="recall", description="Search your memories")
        @app_commands.describe(query="Search query")
        async def recall_command(interaction: discord.Interaction, query: str):
            await self._handle_recall(interaction, query)

        @self.tree.command(
            name="connect", description="Connect services (email, calendar, LLM, etc.)"
        )
        @app_commands.describe(
            service="Service to connect (e.g. email, calendar, anthropic, status)"
        )
        async def connect_command(interaction: discord.Interaction, service: str = ""):
            await interaction.response.defer(ephemeral=True)
            rid = str(interaction.user.id)
            self._wizard_channels[rid] = interaction.channel

            # Check OWNER trust
            from ..core.security import TrustLevel

            session = self.agent.sessions.get_or_create_session(rid, "discord")
            if session.trust_level != TrustLevel.OWNER:
                await interaction.followup.send(
                    "🔒 /connect requires OWNER trust level.", ephemeral=True
                )
                return

            args = service.split() if service else []
            await self.handle_connect_command(rid, args)

        @self.tree.command(name="browse", description="Browse data from connected services")
        async def browse_command(interaction: discord.Interaction):
            await interaction.response.defer(ephemeral=True)
            rid = str(interaction.user.id)
            self._wizard_channels[rid] = interaction.channel

            from ..core.security import TrustLevel

            session = self.agent.sessions.get_or_create_session(rid, "discord")
            if session.trust_level != TrustLevel.OWNER:
                await interaction.followup.send(
                    "\U0001f512 /browse requires OWNER trust level.", ephemeral=True
                )
                return

            await self.handle_browse_command(rid)

        @self.tree.command(name="search", description="Search a connected service")
        @app_commands.describe(
            service="Service to search (github, email, drive, etc.)",
            query="Search query",
        )
        async def search_command(interaction: discord.Interaction, service: str, query: str):
            await interaction.response.defer(ephemeral=True)
            rid = str(interaction.user.id)
            self._wizard_channels[rid] = interaction.channel

            from ..core.security import TrustLevel

            session = self.agent.sessions.get_or_create_session(rid, "discord")
            if session.trust_level != TrustLevel.OWNER:
                await interaction.followup.send(
                    "\U0001f512 /search requires OWNER trust level.", ephemeral=True
                )
                return

            await self.handle_search_command(rid, [service] + query.split())

    # ============================================================
    # PHASE 1: MULTI-USER AUTHENTICATION HANDLERS
    # ============================================================

    async def _handle_link(self, interaction, email: str):
        """Handle /link command."""

        discord_id = str(interaction.user.id)

        # Check if already linked
        channel_user = self._get_channel_user(interaction.user)
        if channel_user and channel_user.is_linked:
            await interaction.response.send_message(
                f"✅ Already linked to: {channel_user.user.email}\n"
                "Use /unlink to disconnect first.",
                ephemeral=True,
            )
            return

        # Start link process
        success, message = self.authenticator.start_link(
            channel_type=ChannelType.DISCORD, channel_id=discord_id, email=email.lower().strip()
        )

        if success:
            await interaction.response.send_message(
                f"📧 **Check Your Email**\n\n{message}\n\nUse `/confirm CODE` to complete linking.",
                ephemeral=True,
            )
        else:
            await interaction.response.send_message(f"❌ {message}", ephemeral=True)

    async def _handle_confirm_link(self, interaction, code: str):
        """Handle /confirm command."""
        discord_id = str(interaction.user.id)

        success, message = self.authenticator.confirm_link(
            channel_type=ChannelType.DISCORD, channel_id=discord_id, code=code.strip()
        )

        await interaction.response.send_message(message, ephemeral=True)

    async def _handle_unlink(self, interaction):
        """Handle /unlink command."""
        discord_id = str(interaction.user.id)

        channel_user = self._get_channel_user(interaction.user)
        if not channel_user or not channel_user.is_linked:
            await interaction.response.send_message(
                "This Discord is not linked to any account.", ephemeral=True
            )
            return

        success, message = self.authenticator.unlink(
            channel_type=ChannelType.DISCORD, channel_id=discord_id
        )

        await interaction.response.send_message(message, ephemeral=True)

    async def _handle_whoami(self, interaction):
        """Handle /whoami command."""
        channel_user = self._get_channel_user(interaction.user)

        if not channel_user:
            await interaction.response.send_message("⛔ Not authorized", ephemeral=True)
            return

        if channel_user.is_linked:
            user = channel_user.user
            await interaction.response.send_message(
                f"👤 **Your Account**\n\n"
                f"**Name:** {user.display_name}\n"
                f"**Email:** {user.email}\n"
                f"**Role:** {user.role.value}\n"
                f"**Discord:** Linked ✅\n\n"
                f"_Use /unlink to disconnect this Discord._",
                ephemeral=True,
            )
        else:
            await interaction.response.send_message(
                f"👤 **Guest Access**\n\n"
                f"**Name:** {channel_user.display_name}\n"
                f"**Discord ID:** {channel_user.channel_id}\n"
                f"**Linked:** No ❌\n\n"
                f"_Use /link email@org.org to connect your account._",
                ephemeral=True,
            )

    # ============================================================
    # END PHASE 1 HANDLERS
    # ============================================================

    async def _handle_message(self, message):
        """Handle incoming Discord messages."""
        import discord

        # Ignore own messages
        if message.author == self.client.user:
            return

        # Ignore bots
        if message.author.bot:
            return

        # ── Owner PIN verification (universal) ──
        owner_id = os.environ.get("OWNER_DISCORD_ID")
        pin_hash = os.environ.get("OWNER_PIN_HASH")

        if not owner_id and pin_hash and message.content:
            try:
                from ..core.security import (
                    check_pin_rate_limit,
                    claim_ownership,
                    record_pin_attempt,
                    verify_owner_pin,
                )

                candidate = message.content.strip()
                uid = str(message.author.id)

                allowed, lockout_msg = check_pin_rate_limit(uid)
                if not allowed:
                    await message.channel.send(lockout_msg)
                    return

                if verify_owner_pin(candidate):
                    record_pin_attempt(uid, success=True)
                    claim_ownership(uid, "discord", self.agent.sessions)
                    logger.info(f"Owner claimed via PIN: Discord user {message.author.id}")
                    await message.channel.send(
                        "🔐 **Owner verified.** You now have full access.\n\n"
                        "Your Discord ID has been saved. You won't need the PIN again.\n"
                        "Say hello to get started!"
                    )
                    return
                elif candidate.isdigit() and 4 <= len(candidate) <= 8:
                    record_pin_attempt(uid, success=False)
                    logger.warning(
                        f"Failed owner PIN attempt from Discord user {message.author.id}"
                    )
                    await message.channel.send(
                        "❌ Incorrect PIN. If you are the owner, check the PIN shown during installation."
                    )
                    return
                else:
                    await message.channel.send(
                        "🔒 This bot requires owner verification.\n"
                        "Please enter your owner PIN to claim this bot."
                    )
                    return
            except ImportError:
                pass  # Security module not available, skip PIN check

        # Check if this is a DM
        is_dm = isinstance(message.channel, discord.DMChannel)

        # Check if bot was mentioned
        is_mentioned = self.client.user in message.mentions

        # Check if message starts with command prefix
        is_command = message.content.startswith(self.command_prefix)

        # Determine if we should respond
        should_respond = False
        content = message.content

        if is_dm and self.respond_to_dms:
            should_respond = True

        elif is_mentioned and self.respond_to_mentions:
            should_respond = True
            # Remove mention from content
            content = content.replace(f"<@{self.client.user.id}>", "").strip()
            content = content.replace(f"<@!{self.client.user.id}>", "").strip()

        elif is_command:
            should_respond = True
            content = content[len(self.command_prefix) :].strip()

        if not should_respond:
            return

        # Check server/channel restrictions
        if not is_dm:
            if self.allowed_servers and message.guild.id not in self.allowed_servers:
                return
            if self.allowed_channels and message.channel.id not in self.allowed_channels:
                return

        # Skip empty messages
        if not content:
            return

        # Phase 1: Authenticate user
        channel_user = self._get_channel_user(message.author)
        if not channel_user:
            return  # Not authorized

        # Show typing indicator
        async with message.channel.typing():
            try:
                # Get UserContext for linked users
                user_context = channel_user.to_context() if channel_user.is_linked else None

                # Use authenticated user ID or fallback to Discord ID
                user_id = channel_user.user_id or f"discord:{message.author.id}"
                channel_id = f"discord:{message.channel.id}"

                # Auto-promote owner on Discord (like Telegram's _get_session)
                discord_owner = os.environ.get("OWNER_DISCORD_ID")
                if discord_owner and str(message.author.id) == str(discord_owner):
                    try:
                        from ..core.security import TrustLevel

                        session = self.agent.sessions.get_or_create_session(user_id, "discord")
                        if session.trust_level != TrustLevel.OWNER:
                            session.set_trust_level(TrustLevel.OWNER)
                            session.daily_budget = 50.0
                            self.agent.sessions.save_session(session)
                            logger.info(f"Auto-promoted Discord owner {message.author.id}")
                    except ImportError:
                        pass

                # Intercept messages during wizard flows (ConnectMixin)
                rid = str(message.author.id)
                self._wizard_channels[rid] = message.channel
                if await self.handle_wizard_message(rid, content, message):
                    return

                # Log with user info
                user_info = (
                    channel_user.user.email
                    if channel_user.is_linked
                    else f"guest:{channel_user.channel_id}"
                )
                logger.info(f"[Discord:{user_info}] {content[:50]}...")

                result = await asyncio.get_event_loop().run_in_executor(
                    None,
                    lambda: self.agent.chat_with_results(
                        message=content,
                        user_id=user_id,
                        channel=channel_id,
                        user_context=user_context,  # Phase 1: Pass user context
                    ),
                )
                response = result.text

                # Confirmation intercept
                from familiar.core.confirmations import SENTINEL_PREFIX

                if isinstance(response, str) and response.startswith(SENTINEL_PREFIX):
                    token = response[len(SENTINEL_PREFIX) :]
                    sess = self.agent.sessions.get_or_create_session(str(user_id), str(channel_id))
                    pending = sess.pending_confirmations.get(token)
                    if pending:
                        await self._send_confirmation_view(
                            channel=message.channel,
                            token=token,
                            preview=pending["preview"],
                            risk=pending.get("risk", "medium"),
                            user_id=user_id,
                            reference=message,
                        )
                    else:
                        await message.channel.send(
                            "Confirmation expired. Please try again.", reference=message
                        )
                    return

                # Send response (split if too long)
                await self._send_response(message.channel, response, reference=message)

            except Exception as e:
                logger.error(f"Error processing message: {e}")
                await message.channel.send(
                    "Sorry, I encountered an error processing your message.", reference=message
                )

    async def _handle_trust(self, interaction):
        """Handle /trust slash command."""
        try:
            from ..core.security import TrustLevel

            user_id = str(interaction.user.id)
            session = self.agent.sessions.get_or_create_session(user_id, "discord")
            levels = list(TrustLevel)
            lines = ["**🔐 Trust Level**", ""]
            for lvl in levels:
                if session.trust_level == lvl:
                    marker = "➡️"
                elif levels.index(session.trust_level) > levels.index(lvl):
                    marker = "✅"
                else:
                    marker = "⬜"
                lines.append(f"{marker} **{lvl.value.upper()}**")
            if session.trust_level == TrustLevel.STRANGER:
                progress = f"{session.positive_interactions}/10 to KNOWN"
            elif session.trust_level == TrustLevel.KNOWN:
                progress = f"{session.positive_interactions}/50 to TRUSTED"
            else:
                progress = "Maximum level ⭐"
            lines += ["", f"**Score:** {session.trust_score:.1f}", f"**Progress:** {progress}"]
            await interaction.response.send_message("\n".join(lines), ephemeral=True)
        except Exception as e:
            await interaction.response.send_message(f"Error: {e}", ephemeral=True)

    async def _handle_budget(self, interaction):
        """Handle /budget slash command."""
        try:
            user_id = str(interaction.user.id)
            session = self.agent.sessions.get_or_create_session(user_id, "discord")
            pct = (
                min(100, session.spent_today / session.daily_budget * 100)
                if session.daily_budget > 0
                else 0
            )
            bar = f"[{'█' * int(pct / 10)}{'░' * (10 - int(pct / 10))}]"
            msg = (
                f"**💰 Budget Status**\n\n"
                f"**Daily Limit:** ${session.daily_budget:.2f}\n"
                f"{bar} {pct:.1f}%\n\n"
                f"**Spent:** ${session.spent_today:.4f}\n"
                f"**Remaining:** ${session.remaining_budget:.4f}\n\n"
                f"*Resets at midnight UTC*"
            )
            await interaction.response.send_message(msg, ephemeral=True)
        except Exception as e:
            await interaction.response.send_message(f"Error: {e}", ephemeral=True)

    async def _handle_caps(self, interaction):
        """Handle /caps slash command."""
        try:
            user_id = str(interaction.user.id)
            session = self.agent.sessions.get_or_create_session(user_id, "discord")
            caps = sorted([c.value for c in session.capabilities])
            cap_list = "\n".join(f"✅ `{c}`" for c in caps) or "*(none yet)*"
            msg = f"**🔑 Your Capabilities ({len(caps)})**\n\n{cap_list}"
            await interaction.response.send_message(msg, ephemeral=True)
        except Exception as e:
            await interaction.response.send_message(f"Error: {e}", ephemeral=True)

    async def _handle_model(self, interaction, provider: str):
        """Handle /model slash command."""
        try:
            if not provider:
                from ..core.providers import get_available_providers

                providers = get_available_providers()
                msg = f"**Current:** {self.agent.provider.name}\n**Available:** {', '.join(providers)}\n\nUse `/model <provider>` to switch."
                await interaction.response.send_message(msg, ephemeral=True)
            else:
                result = self.agent.switch_provider(provider)
                await interaction.response.send_message(f"✓ {result}", ephemeral=True)
        except Exception as e:
            await interaction.response.send_message(f"Error: {e}", ephemeral=True)

    async def _handle_remember(self, interaction, key: str, value: str):
        """Handle /remember slash command."""
        try:
            from ..core.security import Capability

            user_id = str(interaction.user.id)
            session = self.agent.sessions.get_or_create_session(user_id, "discord")
            if not session.has_capability(Capability.WRITE_MEMORY):
                await interaction.response.send_message(
                    "⚠️ You don't have permission to write memory yet. Build trust first!",
                    ephemeral=True,
                )
                return
            self.agent.remember(key, value)
            await interaction.response.send_message(f"✓ Remembered: **{key}**", ephemeral=True)
        except Exception as e:
            await interaction.response.send_message(f"Error: {e}", ephemeral=True)

    async def _handle_recall(self, interaction, query: str):
        """Handle /recall slash command."""
        try:
            from ..core.security import Capability

            user_id = str(interaction.user.id)
            session = self.agent.sessions.get_or_create_session(user_id, "discord")
            if not session.has_capability(Capability.READ_MEMORY):
                await interaction.response.send_message(
                    "⚠️ You don't have read memory permission yet.", ephemeral=True
                )
                return
            results = self.agent.recall(query)
            if not results:
                await interaction.response.send_message(
                    f"No memories matching '{query}'", ephemeral=True
                )
                return
            lines = [f"• **{e.key}**: {e.value}" for e in results[:10]]
            await interaction.response.send_message("\n".join(lines), ephemeral=True)
        except Exception as e:
            await interaction.response.send_message(f"Error: {e}", ephemeral=True)

    async def _handle_slash_command(self, interaction, question: str):
        """Handle /ask slash command."""

        # Defer response (gives us more time)
        await interaction.response.defer(thinking=True)

        try:
            # Phase 1: Authenticate user
            channel_user = self._get_channel_user(interaction.user)
            user_context = (
                channel_user.to_context() if channel_user and channel_user.is_linked else None
            )

            user_id = (
                channel_user.user_id
                if channel_user and channel_user.is_linked
                else f"discord:{interaction.user.id}"
            )
            channel_id = f"discord:{interaction.channel_id}"

            result = await asyncio.get_event_loop().run_in_executor(
                None,
                lambda: self.agent.chat_with_results(
                    message=question,
                    user_id=user_id,
                    channel=channel_id,
                    user_context=user_context,  # Phase 1: Pass user context
                ),
            )
            response = result.text

            # Send response
            if len(response) > 2000:
                # Split long responses
                chunks = self._split_message(response)
                await interaction.followup.send(chunks[0])
                for chunk in chunks[1:]:
                    await interaction.channel.send(chunk)
            else:
                await interaction.followup.send(response)

        except Exception as e:
            logger.error(f"Slash command error: {e}")
            await interaction.followup.send("Sorry, I encountered an error. Please try again.")

    async def _handle_help(self, interaction):
        """Handle /help command."""
        import discord

        embed = discord.Embed(
            title="🤖 Familiar Help",
            description="I'm an AI assistant that can help with various tasks.",
            color=discord.Color.blue(),
        )

        embed.add_field(
            name="How to Use",
            value=(
                "• **DM me** directly\n"
                "• **Mention me** in a channel (@bot your question)\n"
                f"• Use **{self.command_prefix}** prefix\n"
                "• Use **/ask** slash command"
            ),
            inline=False,
        )

        embed.add_field(
            name="Slash Commands",
            value=(
                "**/ask** - Ask a question\n"
                "**/help** - Show this help\n"
                "**/status** - Check agent status\n"
                "**/clear** - Clear your chat history"
            ),
            inline=False,
        )

        embed.add_field(
            name="Example Questions",
            value=(
                "• What's on my calendar today?\n"
                "• Send an email to John about the meeting\n"
                "• Create a task to review the budget\n"
                "• What are my deadlines this week?"
            ),
            inline=False,
        )

        await interaction.response.send_message(embed=embed)

    async def _handle_status(self, interaction):
        """Handle /status command."""
        import discord

        try:
            status = self.agent.get_status()

            embed = discord.Embed(title="📊 Agent Status", color=discord.Color.green())

            embed.add_field(name="Provider", value=status.get("provider", "Unknown"), inline=True)

            embed.add_field(
                name="Memory", value=f"{status.get('memory_entries', 0)} entries", inline=True
            )

            embed.add_field(name="Skills", value=str(status.get("skills_loaded", 0)), inline=True)

            embed.add_field(name="Tools", value=str(status.get("tools_available", 0)), inline=True)

            await interaction.response.send_message(embed=embed)

        except Exception as e:
            await interaction.response.send_message(f"Error getting status: {e}")

    async def _handle_clear(self, interaction):
        """Handle /clear command to clear history."""
        user_id = f"discord:{interaction.user.id}"

        try:
            self.agent.clear_history(user_id)
            await interaction.response.send_message(
                "✅ Your conversation history has been cleared.", ephemeral=True
            )
        except Exception as e:
            await interaction.response.send_message(f"Error clearing history: {e}", ephemeral=True)

    async def _send_response(self, channel, text: str, reference=None):
        """Send a response, splitting if necessary."""
        if len(text) <= 2000:
            await channel.send(text, reference=reference)
        else:
            chunks = self._split_message(text)
            for i, chunk in enumerate(chunks):
                if i == 0:
                    await channel.send(chunk, reference=reference)
                else:
                    await channel.send(chunk)

    def _split_message(self, text: str, max_length: int = 2000) -> list:
        """Split a long message into chunks."""
        if len(text) <= max_length:
            return [text]

        chunks = []
        while text:
            if len(text) <= max_length:
                chunks.append(text)
                break

            # Find a good break point
            break_point = text.rfind("\n", 0, max_length)
            if break_point == -1 or break_point < max_length // 2:
                break_point = text.rfind(" ", 0, max_length)
            if break_point == -1:
                break_point = max_length

            chunks.append(text[:break_point])
            text = text[break_point:].lstrip()

        return chunks

    async def start(self):
        """Start the Discord bot (async)."""
        if not self.token:
            raise RuntimeError(
                "Discord token not set.\n"
                "Set DISCORD_BOT_TOKEN environment variable or pass token to constructor."
            )

        self._setup_client()
        await self.client.start(self.token)

    async def _send_confirmation_view(self, channel, token, preview, risk, user_id, reference=None):
        """Send a discord.ui.View confirmation for a write action."""
        try:
            import discord
        except ImportError:
            await channel.send(
                "Confirmation required — discord.py not available.", reference=reference
            )
            return

        risk_prefix = {"high": "⚠️", "medium": "📋", "low": "✅"}.get(risk, "📋")
        agent_ref = self.agent
        sess_ref = agent_ref.sessions.get_or_create_session(str(user_id), str(channel.id))

        class ConfirmView(discord.ui.View):
            def __init__(self_v):
                super().__init__(timeout=300)

            @discord.ui.button(label="Confirm", style=discord.ButtonStyle.green)
            async def confirm(self_v, interaction: discord.Interaction, button: discord.ui.Button):
                pending = sess_ref.pending_confirmations.pop(token, None)
                if not pending:
                    await interaction.response.edit_message(
                        content=interaction.message.content + "\n\nTimed out.", view=None
                    )
                    return
                agent_ref.sessions.save_session(sess_ref)
                await interaction.response.edit_message(
                    content=interaction.message.content + "\n\nConfirmed, executing...", view=None
                )
                tool_input = dict(pending["tool_input"])
                tool_input["_confirmed"] = True
                try:
                    result = await asyncio.get_event_loop().run_in_executor(
                        None,
                        lambda: agent_ref.tools.execute(
                            pending["tool_name"],
                            tool_input,
                            context={
                                "session": sess_ref,
                                "session_manager": agent_ref.sessions,
                                "agent": agent_ref,
                                "user_id": user_id,
                                "channel": str(channel.id),
                            },
                        ),
                    )
                    # Phase 4: capture context from confirmed tool result
                    try:
                        from familiar.core.agent import _capture_context_from_result

                        _capture_context_from_result(
                            pending["tool_name"],
                            pending.get("tool_input", {}),
                            result,
                            sess_ref,
                            agent_ref.sessions,
                        )
                    except Exception:
                        pass
                    await interaction.followup.send(result or "Done.")
                except Exception as e:
                    await interaction.followup.send("Action failed: " + str(e))
                self_v.stop()

            @discord.ui.button(label="Cancel", style=discord.ButtonStyle.red)
            async def cancel(self_v, interaction: discord.Interaction, button: discord.ui.Button):
                sess_ref.pending_confirmations.pop(token, None)
                agent_ref.sessions.save_session(sess_ref)
                await interaction.response.edit_message(
                    content=interaction.message.content + "\n\nCancelled.", view=None
                )
                self_v.stop()

        msg = risk_prefix + " **Confirm action**\n```\n" + preview + "\n```\n*Expires in 5 minutes*"
        await channel.send(msg, view=ConfirmView(), reference=reference)

    def run(self):
        """Start the Discord bot (blocking)."""
        if not self.token:
            print("❌ Discord token not set")
            print("   Set DISCORD_BOT_TOKEN environment variable")
            print("   Get one from https://discord.com/developers/applications")
            return

        self._setup_client()

        # Start scheduler (backup tasks, proactive briefings)
        if hasattr(self, "agent") and hasattr(self.agent, "start_scheduler"):
            self.agent.start_scheduler()

        # Wire scheduler delivery → Discord (DM to owner if OWNER_DISCORD_ID set)
        if self.agent.scheduler:
            owner_id = os.environ.get("OWNER_DISCORD_ID", "")
            if owner_id:
                _client_ref = self.client

                def _deliver_to_discord(message, channel, chat_id):
                    target_id = int(chat_id) if chat_id else int(owner_id)
                    import asyncio

                    async def _send():
                        try:
                            user = await _client_ref.fetch_user(target_id)
                            await user.send(f"📬 {message}")
                        except Exception as e:
                            logger.error(f"Discord delivery failed: {e}")

                    asyncio.run_coroutine_threadsafe(_send(), _client_ref.loop)

                self.agent.scheduler.set_delivery_callback(_deliver_to_discord)
                logger.info(f"Scheduler delivery → Discord DM (owner: {owner_id})")

        print("🚀 Starting Discord bot...")
        self.client.run(self.token)

    def run_async(self):
        """Start in a background thread."""
        from threading import Thread

        def _run():
            asyncio.run(self.start())

        thread = Thread(target=_run, daemon=True)
        thread.start()
        return thread

    async def stop(self):
        """Stop the Discord bot."""
        if self.client:
            await self.client.close()


# Standalone function to send a Discord message (for use as a tool)
async def _send_discord_message(channel_id: int, message: str, token: str = None) -> bool:
    """Send a message to a Discord channel."""
    try:
        import discord

        token = token or _get_discord_token()
        if not token:
            return False

        # Create a temporary client just for sending
        intents = discord.Intents.default()
        client = discord.Client(intents=intents)

        @client.event
        async def on_ready():
            try:
                channel = client.get_channel(channel_id)
                if channel:
                    await channel.send(message)
            finally:
                await client.close()

        await client.start(token)
        return True

    except Exception as e:
        logger.error(f"Discord send error: {e}")
        return False


def send_discord_message(data: dict) -> str:
    """Tool function to send a Discord message."""
    channel_id = data.get("channel_id")
    message = data.get("message", "")

    if not channel_id or not message:
        return "Please provide 'channel_id' and 'message'"

    if not _get_discord_token():
        return "❌ DISCORD_BOT_TOKEN not set"

    try:
        channel_id = int(channel_id)
    except (ValueError, TypeError):
        return "❌ Invalid channel_id (must be a number)"

    # Run async function
    try:
        loop = asyncio.get_event_loop()
    except RuntimeError:
        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)

    success = loop.run_until_complete(_send_discord_message(channel_id, message))

    if success:
        return f"✅ Message sent to Discord channel {channel_id}"
    else:
        return "❌ Failed to send Discord message"


# Tool definitions
TOOLS = [
    {
        "name": "send_discord",
        "description": "Send a message to a Discord channel",
        "input_schema": {
            "type": "object",
            "properties": {
                "channel_id": {"type": "string", "description": "Discord channel ID (number)"},
                "message": {"type": "string", "description": "Message to send"},
            },
            "required": ["channel_id", "message"],
        },
        "handler": send_discord_message,
        "category": "messaging",
    }
]
