"""Discord interactive Views for Ralph — root list + per-project actions + propose modal.

Critical pattern: every button callback that does I/O MUST call
`await interaction.response.defer(ephemeral=True)` FIRST, then use
`interaction.followup.send(...)`. Otherwise Discord's 3s interaction
timeout kicks in and the user sees "Interaction failed".
"""

from __future__ import annotations

import json
import logging
from pathlib import Path

import discord

from src.router import (
    _load_approved_tasks,
    _ralph_approve,
    _ralph_propose,
    _ralph_status,
    _ralph_stop,
    planning_advance,
    planning_approve,
    planning_cancel,
    start_planning_session,
)
from src.planning_session import is_in_planning

log = logging.getLogger(__name__)

WORKSPACE_DIR = Path("/home/moltbot/workspace")

# Status → emoji prefix for project labels
_STATUS_EMOJI = {
    "pending": "📋",
    "approved": "⏳",
    "running": "🟢",
    "complete": "✅",
    "failed": "❌",
    "stopped": "⏹",
}

VIEW_TIMEOUT = 600  # 10 min — matches ralph_flow TTL


def _project_label(name: str, status: str | None) -> str:
    """Return a short button label like '🟢 roa2web' (max 80 chars per Discord)."""
    emoji = _STATUS_EMOJI.get(status or "", "·")
    label = f"{emoji} {name}"
    return label[:80]


def _list_workspace_projects() -> list[str]:
    """Return workspace folder names sorted, skipping hidden dirs."""
    if not WORKSPACE_DIR.exists():
        return []
    return sorted(
        p.name for p in WORKSPACE_DIR.iterdir()
        if p.is_dir() and not p.name.startswith(".")
    )


def _project_status(name: str) -> str | None:
    """Look up status of a project from approved-tasks.json (or None)."""
    try:
        data = _load_approved_tasks()
    except Exception:
        return None
    for p in data.get("projects", []):
        if p.get("name", "").lower() == name.lower():
            return p.get("status")
    return None


def _read_prd(name: str) -> dict | None:
    prd_path = WORKSPACE_DIR / name / "scripts" / "ralph" / "prd.json"
    if not prd_path.exists():
        return None
    try:
        return json.loads(prd_path.read_text(encoding="utf-8"))
    except (ValueError, OSError):
        return None


# ---------------------------------------------------------------------------
# RalphProposeModal — text input for new feature description
# ---------------------------------------------------------------------------


class RalphProposeModal(discord.ui.Modal, title="Propune feature Ralph"):
    """Modal asking the user for a feature description for a given project."""

    description: discord.ui.TextInput = discord.ui.TextInput(
        label="Descriere",
        placeholder="Ce trebuie făcut? (1-3 propoziții)",
        style=discord.TextStyle.paragraph,
        required=True,
        max_length=1000,
    )

    def __init__(self, slug: str) -> None:
        super().__init__(timeout=VIEW_TIMEOUT)
        self.slug = slug
        self.title = f"Propune feature: {slug}"[:45]

    async def on_submit(self, interaction: discord.Interaction) -> None:
        try:
            result = _ralph_propose(self.slug, str(self.description.value).strip())
        except Exception as e:
            log.exception("Ralph propose modal failed for %s", self.slug)
            await interaction.response.send_message(
                f"Eroare la propunere: {e}", ephemeral=True
            )
            return
        await interaction.response.send_message(result, ephemeral=True)


class RalphPlanModal(discord.ui.Modal, title="Planifică Ralph"):
    """Modal asking for a description and starting a planning session immediately."""

    description: discord.ui.TextInput = discord.ui.TextInput(
        label="Descriere",
        placeholder="Ce vrei să planificăm? (1-3 propoziții)",
        style=discord.TextStyle.paragraph,
        required=True,
        max_length=1000,
    )

    def __init__(self, slug: str) -> None:
        super().__init__(timeout=VIEW_TIMEOUT)
        self.slug = slug
        self.title = f"Planifică: {slug}"[:45]

    async def on_submit(self, interaction: discord.Interaction) -> None:
        from src.router import start_planning_session
        await interaction.response.defer(ephemeral=True)
        description = str(self.description.value).strip()
        await interaction.followup.send(
            f"🧠 Pornesc planning pentru `{self.slug}`… (durează ~60s)",
            ephemeral=True,
        )
        channel_id = str(interaction.channel_id)
        try:
            first = start_planning_session(self.slug, description, channel_id, "discord")
        except Exception as e:
            log.exception("start_planning_session via modal failed for %s", self.slug)
            await interaction.followup.send(f"Planning blocat: {e}", ephemeral=True)
            return
        for chunk in [first[i:i + 1900] for i in range(0, len(first), 1900)] or [first]:
            await interaction.followup.send(chunk, ephemeral=True)
        await interaction.followup.send(
            "Răspunde aici. Apasă **Continuă faza** când ești gata să trec la următoarea.",
            view=PlanningActiveView(),
            ephemeral=True,
        )


# ---------------------------------------------------------------------------
# RalphProjectView — per-project action buttons
# ---------------------------------------------------------------------------


class RalphProjectView(discord.ui.View):
    """Buttons: Propune feature / Vezi PRD / Start acum / Status / Stop / Înapoi."""

    def __init__(self, slug: str) -> None:
        super().__init__(timeout=VIEW_TIMEOUT)
        self.slug = slug

    @discord.ui.button(label="➕ Propune feature", style=discord.ButtonStyle.primary, row=0)
    async def propose(self, interaction: discord.Interaction, button: discord.ui.Button) -> None:
        await interaction.response.send_modal(RalphProposeModal(self.slug))

    @discord.ui.button(label="👁 Vezi PRD", style=discord.ButtonStyle.secondary, row=0)
    async def view_prd(self, interaction: discord.Interaction, button: discord.ui.Button) -> None:
        await interaction.response.defer(ephemeral=True)
        prd = _read_prd(self.slug)
        if prd is None:
            await interaction.followup.send(
                f"Nu există PRD pentru {self.slug}. Aprobă cu /a {self.slug} și night-execute îl generează.",
                ephemeral=True,
            )
            return
        stories = prd.get("userStories", [])
        done = sum(1 for s in stories if s.get("passes"))
        total = len(stories)
        lines = [f"**PRD pentru {self.slug}** — {done}/{total} stories"]
        for s in stories[:12]:
            mark = "✅" if s.get("passes") else "⏳"
            sid = s.get("id", "?")
            title = (s.get("title") or "")[:80]
            lines.append(f"{mark} `{sid}` {title}")
        if total > 12:
            lines.append(f"\n…și încă {total - 12} stories.")
        await interaction.followup.send("\n".join(lines), ephemeral=True)

    @discord.ui.button(label="📊 Status", style=discord.ButtonStyle.secondary, row=0)
    async def status(self, interaction: discord.Interaction, button: discord.ui.Button) -> None:
        await interaction.response.defer(ephemeral=True)
        text = _ralph_status(self.slug)
        await interaction.followup.send(text, ephemeral=True)

    @discord.ui.button(label="✅ Aprobă pentru tonight", style=discord.ButtonStyle.success, row=1)
    async def approve(self, interaction: discord.Interaction, button: discord.ui.Button) -> None:
        await interaction.response.defer(ephemeral=True)
        text = _ralph_approve([self.slug])
        await interaction.followup.send(text, ephemeral=True)

    @discord.ui.button(label="🛑 Stop", style=discord.ButtonStyle.danger, row=1)
    async def stop(self, interaction: discord.Interaction, button: discord.ui.Button) -> None:
        await interaction.response.defer(ephemeral=True)
        text = _ralph_stop(self.slug)
        await interaction.followup.send(text, ephemeral=True)

    @discord.ui.button(label="🧠 Planifică", style=discord.ButtonStyle.primary, row=2)
    async def plan(self, interaction: discord.Interaction, button: discord.ui.Button) -> None:
        # Look up description from approved-tasks.json
        description = ""
        try:
            data = _load_approved_tasks()
            for p in data.get("projects", []):
                if p.get("name", "").lower() == self.slug.lower():
                    description = p.get("description") or ""
                    break
        except Exception:
            log.exception("approved-tasks lookup failed")
        if not description:
            # No description yet — ask for one and start planning on submit.
            # Modal must be sent BEFORE response.defer (Discord constraint).
            await interaction.response.send_modal(RalphPlanModal(self.slug))
            return
        await interaction.response.defer(ephemeral=True)
        channel_id = str(interaction.channel_id)
        await interaction.followup.send(
            f"🧠 Pornesc planning pentru `{self.slug}`… (durează ~60s)",
            ephemeral=True,
        )
        try:
            first = start_planning_session(
                self.slug, description, channel_id, "discord",
            )
        except Exception as e:
            log.exception("start_planning_session failed for %s", self.slug)
            await interaction.followup.send(f"Planning blocat: {e}", ephemeral=True)
            return
        # Send first message of the planning agent + active keyboard
        for chunk in _split_chunks(first, 1900):
            await interaction.followup.send(chunk, ephemeral=True)
        await interaction.followup.send(
            "Răspunde aici. Apasă **Continuă faza** când ești gata să trec la următoarea.",
            view=PlanningActiveView(),
            ephemeral=True,
        )

    @discord.ui.button(label="🔙 Înapoi", style=discord.ButtonStyle.secondary, row=2)
    async def back(self, interaction: discord.Interaction, button: discord.ui.Button) -> None:
        await interaction.response.defer(ephemeral=True)
        view = RalphRootView()
        content = view.render_summary()
        await interaction.edit_original_response(content=content, view=view)


# ---------------------------------------------------------------------------
# RalphRootView — workspace + active projects landing
# ---------------------------------------------------------------------------


class _ProjectButton(discord.ui.Button):
    """Dynamic button representing a single project; opens RalphProjectView."""

    def __init__(self, slug: str, status: str | None, row: int) -> None:
        super().__init__(
            label=_project_label(slug, status),
            style=discord.ButtonStyle.secondary,
            row=row,
            custom_id=f"ralph_project:{slug}",
        )
        self.slug = slug

    async def callback(self, interaction: discord.Interaction) -> None:
        await interaction.response.defer(ephemeral=True)
        view = RalphProjectView(self.slug)
        content = f"**{self.slug}**\nAlege o acțiune:"
        await interaction.edit_original_response(content=content, view=view)


class _RefreshButton(discord.ui.Button):
    def __init__(self) -> None:
        super().__init__(
            label="🔄 Reîncarcă",
            style=discord.ButtonStyle.primary,
            row=4,
            custom_id="ralph_refresh",
        )

    async def callback(self, interaction: discord.Interaction) -> None:
        await interaction.response.defer(ephemeral=True)
        view = RalphRootView()
        await interaction.edit_original_response(
            content=view.render_summary(), view=view
        )


class _CloseButton(discord.ui.Button):
    def __init__(self) -> None:
        super().__init__(
            label="❌ Închide",
            style=discord.ButtonStyle.danger,
            row=4,
            custom_id="ralph_close",
        )

    async def callback(self, interaction: discord.Interaction) -> None:
        await interaction.response.defer(ephemeral=True)
        await interaction.edit_original_response(content="Închis.", view=None)


def _split_chunks(text: str, limit: int = 1900) -> list[str]:
    """Split a long message into Discord-safe chunks."""
    if len(text) <= limit:
        return [text]
    chunks: list[str] = []
    while text:
        if len(text) <= limit:
            chunks.append(text)
            break
        cut = text.rfind("\n", 0, limit)
        if cut == -1:
            cut = limit
        chunks.append(text[:cut])
        text = text[cut:].lstrip("\n")
    return chunks


# ---------------------------------------------------------------------------
# Planning views (W2) — buttons that drive the planning conversation
# ---------------------------------------------------------------------------


class PlanningActiveView(discord.ui.View):
    """Buttons shown DURING an active planning session: advance phase / cancel."""

    def __init__(self) -> None:
        super().__init__(timeout=VIEW_TIMEOUT)

    @discord.ui.button(label="▶️ Continuă faza", style=discord.ButtonStyle.primary, row=0)
    async def advance(self, interaction: discord.Interaction, button: discord.ui.Button) -> None:
        await interaction.response.defer(ephemeral=True)
        channel_id = str(interaction.channel_id)
        try:
            text, completed = planning_advance(channel_id, "discord")
        except Exception as e:
            log.exception("planning advance failed")
            await interaction.followup.send(f"Eroare: {e}", ephemeral=True)
            return
        for chunk in _split_chunks(text):
            await interaction.followup.send(chunk, ephemeral=True)
        view: discord.ui.View = (
            PlanningFinalView() if completed else PlanningActiveView()
        )
        await interaction.followup.send(
            ("Plan gata. Confirmi?" if completed else "Continuăm?"),
            view=view, ephemeral=True,
        )

    @discord.ui.button(label="🛑 Anulează", style=discord.ButtonStyle.danger, row=0)
    async def cancel(self, interaction: discord.Interaction, button: discord.ui.Button) -> None:
        await interaction.response.defer(ephemeral=True)
        text = planning_cancel(str(interaction.channel_id), "discord")
        await interaction.followup.send(text, ephemeral=True)


class PlanningFinalView(discord.ui.View):
    """Buttons shown when ALL planning phases finished — Dau drumul / Anulează."""

    def __init__(self) -> None:
        super().__init__(timeout=VIEW_TIMEOUT)

    @discord.ui.button(label="✅ Dau drumul tonight", style=discord.ButtonStyle.success, row=0)
    async def approve(self, interaction: discord.Interaction, button: discord.ui.Button) -> None:
        await interaction.response.defer(ephemeral=True)
        text = planning_approve(str(interaction.channel_id), "discord")
        await interaction.followup.send(text, ephemeral=True)

    @discord.ui.button(label="🛑 Anulează", style=discord.ButtonStyle.danger, row=0)
    async def cancel(self, interaction: discord.Interaction, button: discord.ui.Button) -> None:
        await interaction.response.defer(ephemeral=True)
        text = planning_cancel(str(interaction.channel_id), "discord")
        await interaction.followup.send(text, ephemeral=True)


class RalphRootView(discord.ui.View):
    """Landing view: workspace projects with status emoji + refresh + close."""

    def __init__(self) -> None:
        super().__init__(timeout=VIEW_TIMEOUT)
        self._build_buttons()

    def _build_buttons(self) -> None:
        projects = _list_workspace_projects()
        # Discord limit: 5 buttons per row × 5 rows. Reserve last row for refresh/close.
        # Project buttons go on rows 0..3 (max 20 projects).
        for idx, slug in enumerate(projects[:20]):
            row = idx // 5
            status = _project_status(slug)
            self.add_item(_ProjectButton(slug, status, row=row))
        self.add_item(_RefreshButton())
        self.add_item(_CloseButton())

    def render_summary(self) -> str:
        """Build the message text shown above the buttons."""
        try:
            data = _load_approved_tasks()
        except Exception:
            data = {"projects": []}
        active = [
            p for p in data.get("projects", [])
            if p.get("status") in ("pending", "approved", "running")
        ]
        lines = ["📋 **Proiecte Ralph**"]
        if active:
            lines.append("")
            lines.append("**Active:**")
            for p in active[:10]:
                emoji = _STATUS_EMOJI.get(p.get("status", ""), "·")
                desc = (p.get("description") or "")[:60]
                lines.append(f"{emoji} `{p.get('name')}` — {desc}")
        lines.append("")
        lines.append("Apasă pe un proiect pentru acțiuni, sau Reîncarcă pentru status fresh.")
        return "\n".join(lines)
