"""Send Slack notifications when tasks change status, complete, or fail.

Supports posting to task-specific threads when thread info is registered
via the bot module's ``register_task_thread`` / ``get_task_thread``.
"""
from __future__ import annotations

import logging
from typing import Any

logger = logging.getLogger(__name__)


class SlackNotifier:
    """Send formatted Slack notifications for task outcomes."""

    def __init__(
        self, slack_client: Any, channel_id: str, hub_url: str = "", report_channel_id: str = ""
    ) -> None:
        self._client = slack_client
        self._channel = channel_id
        self._report_channel = report_channel_id or channel_id
        self._hub_url = hub_url.rstrip("/") if hub_url else ""

    # ------------------------------------------------------------------
    # Public API
    # ------------------------------------------------------------------

    async def notify_queued(self, task: dict) -> None:
        """Post a 'queued' update to the task's thread (if one exists)."""
        task_id = task.get("id", "unknown")
        thread = self._get_task_thread(task_id)
        if thread is None:
            return
        channel, thread_ts = thread
        await self._post_message(
            channel=channel,
            thread_ts=thread_ts,
            text=f":hourglass_flowing_sand: Task `{task_id}` is queued and waiting to run.",
        )

    async def notify_running(self, task: dict) -> None:
        """Post a 'running' update with cancel button to the task's thread."""
        task_id = task.get("id", "unknown")
        thread = self._get_task_thread(task_id)
        if thread is None:
            return
        channel, thread_ts = thread
        blocks = [
            {"type": "section", "text": {
                "type": "mrkdwn", "text": f":runner: Task `{task_id}` is now running...",
            }},
            {
                "type": "actions",
                "elements": [
                    {
                        "type": "button",
                        "action_id": "dobby_cancel_task",
                        "text": {"type": "plain_text", "text": ":x: Cancel"},
                        "style": "danger",
                        "value": task_id,
                    },
                    *(
                        [{
                            "type": "button",
                            "action_id": "dobby_view_dashboard",
                            "text": {
                                "type": "plain_text",
                                "text": ":globe_with_meridians: View Dashboard",
                            },
                            "url": f"{self._hub_url}/dashboard",
                        }] if self._hub_url else []
                    ),
                ],
            },
        ]
        try:
            result = self._client.chat_postMessage(
                channel=channel, thread_ts=thread_ts, blocks=blocks,
                text=f"Task {task_id} is now running...",
            )
            if hasattr(result, "__await__"):
                await result
        except Exception as exc:
            logger.warning("Failed to post running notification: %s", exc)

    async def notify_completed(self, task: dict, result: dict) -> None:
        """Post a success notification for *task* with *result* details.

        For planned-mode tasks, posts the plan with approval reactions.
        Thread replies are always sent (even for batched tasks).
        """
        task_id = task.get("id", "unknown")
        is_planned = task.get("mode") == "planned"

        # Always post to the task's thread if one exists
        thread = self._get_task_thread(task_id)
        if thread is not None:
            channel, thread_ts = thread
            if is_planned:
                # Post plan for approval with reactions
                await self._post_plan_approval(task, result, channel, thread_ts)
            else:
                await self._post_message(
                    channel=channel,
                    thread_ts=thread_ts,
                    text=self._format_completed_thread_reply(task, result),
                )

        if task.get("batch_id"):
            return  # suppress top-level notification for batched tasks

        if is_planned:
            # Post plan approval to report channel and register for thread replies
            blocks = self._build_plan_approval_blocks(task, result)
            msg_ts = await self._post_and_capture_ts(blocks=blocks, channel=self._report_channel)
            if msg_ts:
                session_id = result.get("session_id") or task.get("session_id")
                try:
                    from dobby.slack.bot import register_plan_approval, register_plan_thread
                    register_plan_thread(msg_ts, task, session_id)
                    register_plan_approval(msg_ts, task, result, session_id)
                except ImportError:
                    pass
        else:
            blocks = self._build_completed_blocks(task, result)
            await self._post(blocks=blocks, channel=self._report_channel)

    async def _post_plan_approval(
        self, task: dict, result: dict, channel: str, thread_ts: str
    ) -> None:
        """Post plan summary with approval buttons and register plan thread."""
        task_id = task.get("id", "unknown")
        project = task.get("project", "unknown")
        summary = result.get("summary", "No plan summary available.")
        session_id = result.get("session_id") or task.get("session_id")

        # Register/update the plan thread so replies continue the conversation
        try:
            from dobby.slack.bot import register_plan_thread
            register_plan_thread(thread_ts, task, session_id)
        except ImportError:
            pass

        blocks = [
            {"type": "section", "text": {
                "type": "mrkdwn",
                "text": f":clipboard: *Plan ready for `{task_id}` ({project})*",
            }},
            {"type": "section", "text": {"type": "mrkdwn", "text": summary[:1500]}},
            {"type": "section", "text": {
                "type": "mrkdwn",
                "text": "_Reply in this thread to discuss the plan, or use buttons below._",
            }},
            {
                "type": "actions",
                "elements": [
                    {
                        "type": "button",
                        "action_id": "dobby_approve_plan",
                        "text": {
                            "type": "plain_text", "text": ":white_check_mark: Approve & Execute",
                        },
                        "style": "primary",
                    },
                    {
                        "type": "button",
                        "action_id": "dobby_edit_plan",
                        "text": {"type": "plain_text", "text": ":pencil2: Edit Plan"},
                    },
                    {
                        "type": "button",
                        "action_id": "dobby_cancel",
                        "text": {"type": "plain_text", "text": ":x: Cancel"},
                        "style": "danger",
                    },
                ],
            },
        ]

        fallback_text = (
            f"Plan ready for {task_id} ({project}): {summary[:200]}. "
            "Reply in this thread to discuss, or use buttons to approve/edit/cancel."
        )

        try:
            resp = self._client.chat_postMessage(
                channel=channel, thread_ts=thread_ts,
                blocks=blocks, text=fallback_text,
            )
            if hasattr(resp, "__await__"):
                resp = await resp

            msg_ts = (
                resp.get("ts") if isinstance(resp, dict) else getattr(resp, "data", {}).get("ts")
            )
            if msg_ts:
                try:
                    from dobby.slack.bot import register_plan_approval
                    register_plan_approval(msg_ts, task, result, session_id)
                except ImportError:
                    pass
        except Exception as exc:
            logger.warning("Failed to post plan approval: %s", exc)

    async def notify_failed(self, task: dict, result: dict) -> None:
        """Post a failure notification for *task* with *result* details.

        Thread replies are always sent. Top-level is suppressed for batched tasks.
        """
        task_id = task.get("id", "unknown")

        # Always post to the task's thread if one exists
        thread = self._get_task_thread(task_id)
        if thread is not None:
            channel, thread_ts = thread
            await self._post_message(
                channel=channel,
                thread_ts=thread_ts,
                text=self._format_failed_thread_reply(task, result),
            )

        if task.get("batch_id"):
            return  # suppress top-level notification for batched tasks

        blocks = self._build_failed_blocks(task, result)
        await self._post(blocks=blocks, channel=self._report_channel)

    async def post_daily_plan(self, plan: dict) -> str | None:
        """Post a formatted daily plan briefing to Slack. Returns message_ts."""
        blocks_data = plan.get("blocks", [])
        date = plan.get("date", "today")

        lines = [f":calendar: *Daily Plan — {date}*\n"]

        for b in blocks_data:
            emoji = {
                "focus": ":dart:",
                "meeting": ":spiral_calendar_pad:",
                "break": ":coffee:",
                "lunch": ":fork_and_knife:",
                "buffer": ":hourglass:",
            }.get(b["block_type"], ":pushpin:")

            start_str = b["start"].split("T")[1][:5] if "T" in b["start"] else ""
            end_str = b["end"].split("T")[1][:5] if "T" in b["end"] else ""
            proj = f" `{b['project']}`" if b.get("project") else ""
            lines.append(f"{emoji} *{start_str}–{end_str}* {b['title']}{proj}")

        focus_mins = sum(b["duration_minutes"] for b in blocks_data if b["block_type"] == "focus")
        meeting_mins = sum(
            b["duration_minutes"] for b in blocks_data if b["block_type"] == "meeting"
        )
        lines.append(
            f"\n:chart_with_upwards_trend: Focus: *{focus_mins // 60}h {focus_mins % 60}m*"
            f" | Meetings: *{meeting_mins // 60}h {meeting_mins % 60}m*"
        )

        text = "\n".join(lines)
        slack_blocks = [
            {"type": "section", "text": {"type": "mrkdwn", "text": text}},
        ]

        try:
            result = await self._post(blocks=slack_blocks, channel=self._report_channel)
            return result.get("ts") if result else None
        except Exception as exc:
            logger.warning("Failed to post daily plan: %s", exc)
            return None

    async def notify_batch_completed(self, batch_id: str, batch_summary: dict) -> None:
        """Post an aggregate notification when ALL tasks in a batch are done."""
        blocks = self._build_batch_completed_blocks(batch_id, batch_summary)
        await self._post(blocks=blocks, channel=self._report_channel)

    # ------------------------------------------------------------------
    # Thread reply formatters
    # ------------------------------------------------------------------

    @staticmethod
    def _format_completed_thread_reply(task: dict, result: dict) -> str:
        summary = result.get("summary", "")
        cost_info = result.get("cost", {})
        cost_usd = cost_info.get("cost_usd", 0.0)
        cost_str = f"${cost_usd:.4f}" if cost_usd else "—"

        pr_link = None
        for artifact in result.get("artifacts", []):
            url = artifact.get("url", "")
            if "pull" in url or "pr" in url.lower():
                pr_link = url
                break

        lines = [f":white_check_mark: *Task `{task.get('id', '?')}` completed!*"]
        if summary:
            lines.append(f"> {summary[:200]}")
        if pr_link:
            lines.append(f":link: <{pr_link}|View PR>")
        lines.append(f"*Cost:* {cost_str}")
        return "\n".join(lines)

    @staticmethod
    def _format_failed_thread_reply(task: dict, result: dict) -> str:
        summary = result.get("summary", "No error details.")
        task_id = task.get("id", "?")
        cost_info = result.get("cost", {})
        cost_usd = cost_info.get("cost_usd", 0.0)
        cost_str = f"${cost_usd:.4f}" if cost_usd else "—"
        lines = [
            f":x: *Task `{task_id}` failed.*",
            f"```{summary[:300]}```",
            f"*Cost:* {cost_str}",
            f"_Run `dobby logs {task_id}` for the full log._",
        ]
        return "\n".join(lines)

    # ------------------------------------------------------------------
    # Block builders (top-level notifications)
    # ------------------------------------------------------------------

    def _build_completed_blocks(self, task: dict, result: dict) -> list[dict]:
        task_id = task.get("id", "unknown")
        project = task.get("project", "unknown")
        description = task.get("description", "")
        summary = result.get("summary", "")

        cost_info = result.get("cost", {})
        cost_usd = cost_info.get("cost_usd", 0.0)
        cost_str = f"${cost_usd:.4f}" if cost_usd else "—"

        pr_link = None
        for artifact in result.get("artifacts", []):
            url = artifact.get("url", "")
            if "pull" in url or "pr" in url.lower():
                pr_link = url
                break

        # Title: short description (first line, truncated)
        short_desc = description.split("\n")[0][:120] if description else "No description"
        header_text = f":white_check_mark: *{project}* — {short_desc}"

        blocks: list[dict] = [
            {
                "type": "section",
                "text": {"type": "mrkdwn", "text": header_text},
            },
        ]

        # Lead with the RESULT — what the agent found/did (most important)
        if summary:
            blocks.append(
                {
                    "type": "section",
                    "text": {"type": "mrkdwn", "text": f"*Result:*\n{summary[:1500]}"},
                }
            )

        # Metrics as compact fields
        metrics = result.get("metrics", {})
        fields = [
            {"type": "mrkdwn", "text": f"*Task:* `{task_id}`"},
            {"type": "mrkdwn", "text": f"*Cost:* {cost_str}"},
        ]
        # Add key metrics inline if available
        for key in list(metrics.keys())[:4]:
            val = metrics[key]
            if key != "merge":
                fields.append({"type": "mrkdwn", "text": f"*{key}:* {val}"})
        blocks.append({"type": "section", "fields": fields[:8]})

        if pr_link:
            blocks.append(
                {
                    "type": "section",
                    "text": {"type": "mrkdwn", "text": f"*Pull Request:*\n<{pr_link}|View PR>"},
                }
            )

        # Action buttons for completed tasks
        action_elements: list[dict] = [
            {
                "type": "button",
                "action_id": "dobby_retry_task",
                "text": {"type": "plain_text", "text": ":arrows_counterclockwise: Run Again"},
                "value": task_id,
            },
        ]
        if pr_link:
            action_elements.append({
                "type": "button",
                "action_id": "dobby_view_pr",
                "text": {"type": "plain_text", "text": ":twisted_rightwards_arrows: View PR"},
                "url": pr_link,
            })
        if self._hub_url:
            action_elements.append({
                "type": "button",
                "action_id": "dobby_view_logs",
                "text": {"type": "plain_text", "text": ":page_facing_up: View Logs"},
                "url": f"{self._hub_url}/dashboard#task-{task_id}",
            })
        blocks.append({"type": "actions", "elements": action_elements})

        blocks.append({"type": "divider"})
        blocks.append(
            {
                "type": "context",
                "elements": [
                    {"type": "mrkdwn", "text": ":large_green_circle: Dobby — task succeeded"}
                ],
            }
        )

        return blocks

    @staticmethod
    def _build_plan_approval_blocks(task: dict, result: dict) -> list[dict]:
        task_id = task.get("id", "unknown")
        project = task.get("project", "unknown")
        summary = result.get("summary", "No plan summary.")

        return [
            {"type": "section", "text": {
                "type": "mrkdwn", "text": f":clipboard: *Plan ready — {project}*",
            }},
            {"type": "section", "text": {
                "type": "mrkdwn", "text": f"*Task:* `{task_id}`\n\n{summary[:1000]}",
            }},
            {
                "type": "actions",
                "elements": [
                    {
                        "type": "button",
                        "action_id": "dobby_approve_plan",
                        "text": {
                            "type": "plain_text", "text": ":white_check_mark: Approve & Execute",
                        },
                        "style": "primary",
                    },
                    {
                        "type": "button",
                        "action_id": "dobby_edit_plan",
                        "text": {"type": "plain_text", "text": ":pencil2: Edit Plan"},
                    },
                    {
                        "type": "button",
                        "action_id": "dobby_cancel",
                        "text": {"type": "plain_text", "text": ":x: Cancel"},
                        "style": "danger",
                    },
                ],
            },
            {"type": "divider"},
            {"type": "context", "elements": [{
                "type": "mrkdwn", "text": ":large_blue_circle: Dobby — awaiting approval",
            }]},
        ]

    def _build_failed_blocks(self, task: dict, result: dict) -> list[dict]:
        task_id = task.get("id", "unknown")
        project = task.get("project", "unknown")
        description = task.get("description", "")
        summary = result.get("summary", "No error details available.")

        cost_info = result.get("cost", {})
        cost_usd = cost_info.get("cost_usd", 0.0)
        cost_str = f"${cost_usd:.4f}" if cost_usd else "—"

        header_text = f":x: Task failed — *{project}*"

        blocks: list[dict] = [
            {
                "type": "section",
                "text": {"type": "mrkdwn", "text": header_text},
            },
            {
                "type": "section",
                "fields": [
                    {"type": "mrkdwn", "text": f"*Task ID:*\n`{task_id}`"},
                    {"type": "mrkdwn", "text": f"*Cost:*\n{cost_str}"},
                ],
            },
        ]

        if description:
            short_desc = description.split("\n")[0][:150]
            if len(description) > 150:
                short_desc += "..."
            blocks.append(
                {
                    "type": "section",
                    "text": {"type": "mrkdwn", "text": f"*Description:*\n{short_desc}"},
                }
            )

        blocks.append(
            {
                "type": "section",
                "text": {
                    "type": "mrkdwn",
                    "text": f"*Error:*\n```{summary}```",
                },
            }
        )

        action_elements = [
            {
                "type": "button",
                "action_id": "dobby_retry_task",
                "text": {"type": "plain_text", "text": ":arrows_counterclockwise: Retry"},
                "value": task_id,
            },
        ]
        if self._hub_url:
            action_elements.append({
                "type": "button",
                "action_id": "dobby_view_logs",
                "text": {"type": "plain_text", "text": ":page_facing_up: View Logs"},
                "url": f"{self._hub_url}/dashboard#task-{task_id}",
            })
        blocks.append({"type": "actions", "elements": action_elements})

        blocks.append({"type": "divider"})
        blocks.append(
            {
                "type": "context",
                "elements": [
                    {"type": "mrkdwn", "text": ":red_circle: Dobby — task failed"}
                ],
            }
        )

        return blocks

    @staticmethod
    def _build_batch_completed_blocks(batch_id: str, summary: dict) -> list[dict]:
        total = summary.get("total", 0)
        completed = summary.get("completed", 0)
        failed = summary.get("failed", 0)

        if failed == 0:
            emoji = ":white_check_mark:"
            status_text = "All tasks succeeded"
            footer_emoji = ":large_green_circle:"
        else:
            emoji = ":warning:"
            status_text = f"{completed} succeeded, {failed} failed"
            footer_emoji = ":large_yellow_circle:"

        header_text = f"{emoji} Batch completed — *{batch_id}*"

        blocks: list[dict] = [
            {
                "type": "section",
                "text": {"type": "mrkdwn", "text": header_text},
            },
            {
                "type": "section",
                "fields": [
                    {"type": "mrkdwn", "text": f"*Total Tasks:*\n{total}"},
                    {"type": "mrkdwn", "text": f"*Status:*\n{status_text}"},
                ],
            },
        ]

        task_summaries = summary.get("task_summaries", [])
        for ts in task_summaries[:10]:
            status_emoji = ":white_check_mark:" if ts.get("status") == "completed" else ":x:"
            blocks.append({
                "type": "section",
                "text": {
                    "type": "mrkdwn",
                    "text": (
                        f"{status_emoji} `{ts.get('task_id', '?')}` —"
                        f" {ts.get('summary', 'No summary')[:100]}"
                    ),
                },
            })

        total_cost = summary.get("total_cost_usd", 0.0)
        if total_cost:
            blocks.append({
                "type": "section",
                "text": {"type": "mrkdwn", "text": f"*Total Cost:* ${total_cost:.4f}"},
            })

        blocks.append({"type": "divider"})
        blocks.append({
            "type": "context",
            "elements": [
                {"type": "mrkdwn", "text": f"{footer_emoji} Dobby — batch completed"}
            ],
        })

        return blocks

    # ------------------------------------------------------------------
    # Internal helpers
    # ------------------------------------------------------------------

    @staticmethod
    def _get_task_thread(task_id: str) -> tuple[str, str] | None:
        """Look up the Slack thread for a task via the bot's thread registry."""
        try:
            from dobby.slack.bot import get_task_thread
            return get_task_thread(task_id)
        except ImportError:
            return None

    async def _post_message(self, channel: str, thread_ts: str, text: str) -> None:
        """Post a text message to a Slack thread."""
        try:
            result = self._client.chat_postMessage(
                channel=channel, thread_ts=thread_ts, text=text,
            )
            if hasattr(result, "__await__"):
                await result
        except Exception as exc:
            logger.warning("Failed to post thread message: %s", exc)

    async def _post_and_capture_ts(
        self, blocks: list[dict], channel: str | None = None
    ) -> str | None:
        """Post *blocks* to Slack and return the message ts."""
        target = channel or self._channel
        try:
            resp = self._client.chat_postMessage(channel=target, blocks=blocks)
            if hasattr(resp, "__await__"):
                resp = await resp
            if isinstance(resp, dict):
                return resp.get("ts")
            return getattr(resp, "data", {}).get("ts")
        except Exception as exc:
            logger.warning("Failed to post message: %s", exc)
            return None

    async def _post(self, blocks: list[dict], channel: str | None = None) -> None:
        """Post *blocks* to Slack. Handles both sync and async clients."""
        target = channel or self._channel
        result = self._client.chat_postMessage(channel=target, blocks=blocks)
        if hasattr(result, "__await__"):
            await result
