"""Discord interactive components — buttons, select menus, and modals.

Provides factory functions that return ``discord.ui.View`` (or
``discord.ui.Modal``) subclasses for common peon-mcp agent workflows.
Each factory returns ``None`` when discord.py is not installed so callers
can guard with a simple ``if view is not None:`` check.

Component custom_id format:
    ``<prefix>:<entity_id>``   e.g. ``task_approve:42``

Supported prefixes:
    task_approve, task_reject, task_skip
    pr_approve, pr_changes
    priority_select
    feature_status_select
    task_create_modal (modal)
"""

from __future__ import annotations

import logging
import os
from typing import Any

import httpx

logger = logging.getLogger(__name__)

PEON_API_BASE = os.environ.get("PEON_API_URL", "http://127.0.0.1:8420").rstrip("/")

import discord


# ---------------------------------------------------------------------------
# Internal — peon-mcp REST API helpers
# ---------------------------------------------------------------------------


def _api_headers() -> dict[str, str]:
    """Return headers dict, including X-API-Key when PEON_API_KEY is set."""
    key = os.environ.get("PEON_API_KEY", "")
    if key:
        return {"X-API-Key": key}
    return {}


async def _api_patch_task(task_id: int | str, payload: dict) -> tuple[bool, str]:
    """PUT a partial update on a task via the peon-mcp REST API."""
    try:
        async with httpx.AsyncClient(timeout=10.0, headers=_api_headers()) as client:
            resp = await client.put(
                f"{PEON_API_BASE}/api/tasks/{task_id}",
                json=payload,
            )
            if resp.status_code in (200, 201, 204):
                return True, "OK"
            return False, f"HTTP {resp.status_code}: {resp.text[:200]}"
    except Exception as exc:
        logger.warning("_api_patch_task(%s): %s", task_id, exc)
        return False, str(exc)


async def _api_create_task(payload: dict) -> tuple[bool, str]:
    """Create a new task via the peon-mcp REST API."""
    try:
        async with httpx.AsyncClient(timeout=10.0, headers=_api_headers()) as client:
            resp = await client.post(
                f"{PEON_API_BASE}/api/tasks",
                json=payload,
            )
            if resp.status_code in (200, 201):
                data = resp.json()
                return True, f"Task #{data.get('id', '?')} created"
            return False, f"HTTP {resp.status_code}: {resp.text[:200]}"
    except Exception as exc:
        logger.warning("_api_create_task: %s", exc)
        return False, str(exc)


async def _api_update_feature(feature_id: int | str, payload: dict) -> tuple[bool, str]:
    """Update a feature via the peon-mcp REST API."""
    try:
        async with httpx.AsyncClient(timeout=10.0, headers=_api_headers()) as client:
            resp = await client.put(
                f"{PEON_API_BASE}/api/features/{feature_id}",
                json=payload,
            )
            if resp.status_code in (200, 201, 204):
                return True, "OK"
            return False, f"HTTP {resp.status_code}: {resp.text[:200]}"
    except Exception as exc:
        logger.warning("_api_update_feature(%s): %s", feature_id, exc)
        return False, str(exc)


# ---------------------------------------------------------------------------
# Component factories
# ---------------------------------------------------------------------------


def TaskApprovalButtons(task_id: int | str, task_title: str = "") -> Any:
    """Return a View with Approve / Reject / Skip buttons for a task notification.

    On approve: sets task status → ``in_progress``.
    On reject:  sets task status → ``cancelled``.
    On skip:    dismisses the notification without taking action.

    Returns:
        A ``discord.ui.View`` instance, or ``None`` if discord.py is unavailable.
    """

    _title = task_title or f"Task #{task_id}"

    class _View(discord.ui.View):
        def __init__(self) -> None:
            super().__init__(timeout=3600)

        def _disable_all(self) -> None:
            for item in self.children:
                if hasattr(item, "disabled"):
                    item.disabled = True

        @discord.ui.button(
            label="Approve",
            style=discord.ButtonStyle.success,
            custom_id=f"task_approve:{task_id}",
            emoji="✅",
        )
        async def approve(
            self, interaction: discord.Interaction, button: discord.ui.Button
        ) -> None:
            await interaction.response.defer()
            ok, msg = await _api_patch_task(task_id, {"status": "in_progress"})
            result = (
                f"✅ **{_title}** approved — status set to **in_progress**."
                if ok
                else f"❌ Failed to approve task: {msg}"
            )
            self._disable_all()
            await interaction.edit_original_response(content=result, view=self)

        @discord.ui.button(
            label="Reject",
            style=discord.ButtonStyle.danger,
            custom_id=f"task_reject:{task_id}",
            emoji="❌",
        )
        async def reject(
            self, interaction: discord.Interaction, button: discord.ui.Button
        ) -> None:
            await interaction.response.defer()
            ok, msg = await _api_patch_task(task_id, {"status": "cancelled"})
            result = (
                f"🚫 **{_title}** has been **cancelled**."
                if ok
                else f"❌ Failed to reject task: {msg}"
            )
            self._disable_all()
            await interaction.edit_original_response(content=result, view=self)

        @discord.ui.button(
            label="Skip",
            style=discord.ButtonStyle.secondary,
            custom_id=f"task_skip:{task_id}",
            emoji="⏭️",
        )
        async def skip(
            self, interaction: discord.Interaction, button: discord.ui.Button
        ) -> None:
            await interaction.response.defer()
            self._disable_all()
            await interaction.edit_original_response(content="⏭️ Skipped.", view=self)

    return _View()


def PRActionButtons(task_id: int | str, pr_url: str = "") -> Any:
    """Return a View with Approve / Request Changes buttons for a PR review embed.

    These buttons record user intent (approve or request changes).  If a PR URL
    is provided an extra link button to open the PR is added automatically.

    Returns:
        A ``discord.ui.View`` instance, or ``None`` if discord.py is unavailable.
    """

    class _View(discord.ui.View):
        def __init__(self) -> None:
            super().__init__(timeout=7200)
            if pr_url:
                self.add_item(
                    discord.ui.Button(
                        label="Open PR",
                        style=discord.ButtonStyle.link,
                        url=pr_url,
                        emoji="🔗",
                    )
                )

        def _disable_non_link(self) -> None:
            for item in self.children:
                if hasattr(item, "disabled") and not getattr(item, "url", None):
                    item.disabled = True

        @discord.ui.button(
            label="Approve",
            style=discord.ButtonStyle.success,
            custom_id=f"pr_approve:{task_id}",
            emoji="✅",
        )
        async def approve(
            self, interaction: discord.Interaction, button: discord.ui.Button
        ) -> None:
            await interaction.response.defer()
            self._disable_non_link()
            await interaction.edit_original_response(
                content=f"✅ {interaction.user.mention} approved PR for Task #{task_id}.",
                view=self,
            )

        @discord.ui.button(
            label="Request Changes",
            style=discord.ButtonStyle.danger,
            custom_id=f"pr_changes:{task_id}",
            emoji="📝",
        )
        async def request_changes(
            self, interaction: discord.Interaction, button: discord.ui.Button
        ) -> None:
            await interaction.response.defer()
            self._disable_non_link()
            await interaction.edit_original_response(
                content=f"📝 {interaction.user.mention} requested changes on Task #{task_id}.",
                view=self,
            )

    return _View()


def PrioritySelect(task_id: int | str) -> Any:
    """Return a View with a select menu to change a task's priority inline.

    Returns:
        A ``discord.ui.View`` instance, or ``None`` if discord.py is unavailable.
    """

    _BADGES = {
        "critical": "🔴 Critical",
        "high": "🟠 High",
        "medium": "🟡 Medium",
        "low": "🟢 Low",
    }

    class _View(discord.ui.View):
        def __init__(self) -> None:
            super().__init__(timeout=3600)

        @discord.ui.select(
            placeholder="Change task priority…",
            custom_id=f"priority_select:{task_id}",
            options=[
                discord.SelectOption(label="Critical", value="critical", emoji="🔴"),
                discord.SelectOption(label="High", value="high", emoji="🟠"),
                discord.SelectOption(label="Medium", value="medium", emoji="🟡"),
                discord.SelectOption(label="Low", value="low", emoji="🟢"),
            ],
        )
        async def select_priority(
            self,
            interaction: discord.Interaction,
            select: discord.ui.Select,
        ) -> None:
            priority = select.values[0]
            await interaction.response.defer()
            ok, msg = await _api_patch_task(task_id, {"priority": priority})
            badge = _BADGES.get(priority, priority)
            result = (
                f"Priority updated to **{badge}**."
                if ok
                else f"❌ Failed to update priority: {msg}"
            )
            select.disabled = True
            await interaction.edit_original_response(content=result, view=self)

    return _View()


def FeatureStatusSelect(feature_id: int | str) -> Any:
    """Return a View with a select menu to change a feature's status inline.

    Returns:
        A ``discord.ui.View`` instance, or ``None`` if discord.py is unavailable.
    """

    class _View(discord.ui.View):
        def __init__(self) -> None:
            super().__init__(timeout=3600)

        @discord.ui.select(
            placeholder="Change feature status…",
            custom_id=f"feature_status_select:{feature_id}",
            options=[
                discord.SelectOption(label="Planned", value="planned", emoji="📋"),
                discord.SelectOption(label="In Progress", value="in_progress", emoji="🔨"),
                discord.SelectOption(label="Done", value="done", emoji="✅"),
                discord.SelectOption(label="Cancelled", value="cancelled", emoji="🚫"),
            ],
        )
        async def select_status(
            self,
            interaction: discord.Interaction,
            select: discord.ui.Select,
        ) -> None:
            status = select.values[0]
            await interaction.response.defer()
            ok, msg = await _api_update_feature(feature_id, {"status": status})
            result = (
                f"Feature status updated to **{status}**."
                if ok
                else f"❌ Failed to update feature status: {msg}"
            )
            select.disabled = True
            await interaction.edit_original_response(content=result, view=self)

    return _View()


def TaskCreateModal(project_id: str, feature_id: int | str | None = None) -> Any:
    """Return a Modal form for creating a new task.

    Fields: title (required), description (optional), priority (optional).
    On submit: calls ``POST /api/tasks`` with the collected values.

    Returns:
        A ``discord.ui.Modal`` instance, or ``None`` if discord.py is unavailable.
    """

    class _Modal(discord.ui.Modal, title="Create Task"):
        task_title = discord.ui.TextInput(
            label="Title",
            placeholder="Short summary of the task",
            max_length=200,
            required=True,
        )
        description = discord.ui.TextInput(
            label="Description",
            placeholder="Detailed description (optional)",
            style=discord.TextStyle.paragraph,
            max_length=2000,
            required=False,
        )
        priority = discord.ui.TextInput(
            label="Priority",
            placeholder="critical / high / medium / low",
            default="medium",
            max_length=20,
            required=False,
        )

        def __init__(self) -> None:
            super().__init__()

        async def on_submit(self, interaction: discord.Interaction) -> None:
            await interaction.response.defer(ephemeral=True)
            priority_val = (self.priority.value or "medium").lower().strip()
            if priority_val not in ("critical", "high", "medium", "low"):
                priority_val = "medium"
            payload: dict = {
                "project_id": project_id,
                "title": self.task_title.value,
                "description": self.description.value or "",
                "priority": priority_val,
                "status": "todo",
            }
            if feature_id is not None:
                payload["feature_id"] = int(feature_id)
            ok, msg = await _api_create_task(payload)
            result = f"✅ {msg}" if ok else f"❌ Failed to create task: {msg}"
            await interaction.followup.send(result, ephemeral=True)

        async def on_error(
            self, interaction: discord.Interaction, error: Exception
        ) -> None:
            logger.error("TaskCreateModal error: %s", error)
            try:
                await interaction.followup.send(
                    "⚠️ An error occurred while creating the task.", ephemeral=True
                )
            except Exception:
                pass

    return _Modal()
