"""SendFile tool — send local files to the user as IM attachments."""

from pathlib import Path
from typing import Any, Awaitable, Callable

from agno.tools import Toolkit
from loguru import logger
from asedclaw.bus.events import OutboundMessage


class SendFileTool(Toolkit):
    """Send one or more local file attachments to the user.

    Design rationale
    ----------------
    Text responses are delivered automatically via streaming (on_progress →
    message.stream).  This tool exists purely for *file delivery*.

    To prevent the LLM from calling the tool twice (once per file) or looping
    after receiving the confirmation, the tool supports a **list of paths** so
    all files can be sent in a single call, and uses ``stop_after_tool_call``
    so the agent terminates immediately after the files are queued.

    Typical pattern
    ---------------
    1. LLM streams an explanation via natural text output.
    2. LLM calls ``send_file(paths=["a.pdf", "b.md"])`` to deliver files.
    3. Agent stops — no further output is generated.
    """

    def __init__(
        self,
        send_callback: Callable[[OutboundMessage], Awaitable[None]] | None = None,
        default_channel: str = "",
        default_chat_id: str = "",
        workspace: Path | None = None,
    ):
        self._send_callback = send_callback
        self._default_channel = default_channel
        self._default_chat_id = default_chat_id
        self._workspace = workspace
        self._sent_paths: set[str] = set()
        self._calls_in_turn: int = 0
        # stop_after_tool_call prevents the LLM from calling this tool twice
        # or looping after receiving the confirmation message.
        super().__init__(
            name="send_file",
            tools=[self.send_file],
            # stop_after_tool_call_tools=["send_file"],
        )

    # ── Context injection ────────────────────────────────────────────────

    def set_context(self, channel: str, chat_id: str) -> None:
        """Set the current conversation context."""
        self._default_channel = channel
        self._default_chat_id = chat_id

    def set_send_callback(
        self, callback: Callable[[OutboundMessage], Awaitable[None]]
    ) -> None:
        """Set or replace the outbound-message callback."""
        self._send_callback = callback

    def start_turn(self) -> None:
        """Reset per-turn tracking."""
        self._calls_in_turn = 0
        self._sent_paths.clear()

    # ── Tool function ────────────────────────────────────────────────────

    async def send_file(
        self,
        paths: list[str],
        channel: str | None = None,
        chat_id: str | None = None,
    ) -> str:
        """Send one or more local files to the user as attachments.

        Use this tool to share generated or existing files (PDFs, images,
        spreadsheets, archives, code files, etc.) with the user.

        **Important behaviour**:
        - Calling this tool **ends the current agent turn** immediately.
          Any text you want to say must be output *before* calling this tool
          via the normal streaming output, not via another tool.
        - Pass **all files in a single call** using the ``paths`` list.
          Do NOT call this tool multiple times in one turn.
        - Your text response is already being delivered to the user via
          streaming.  This tool only handles file delivery.

        Args:
            paths (list[str]): List of local file paths to send.
                               Relative paths are resolved from the workspace root.
                               Only existing files are sent; missing ones are skipped
                               with a warning in the return value.
                               Example: ["output/report.pdf", "data/analysis.csv"]
            channel (str | None): Target channel name.
                                  Leave unset — the system fills this automatically.
            chat_id (str | None): Target chat/conversation ID.
                                  Leave unset — the system fills this automatically.

        Returns:
            str: Summary of what was sent / skipped, or an error message.

        Examples:
            send_file(paths=["output/report.pdf"])
            send_file(paths=["charts/plot.png", "data/results.csv"])
            send_file(paths=["skills/office/SKILL.md"])
        """
        self._calls_in_turn += 1
        channel = channel or self._default_channel
        chat_id = chat_id or self._default_chat_id

        if not channel or not chat_id:
            return "Error: No target channel/chat specified."
        if not self._send_callback:
            return "Error: File sending not configured."
        if not paths:
            return "Error: No file paths provided."

        sent: list[str] = []
        skipped: list[str] = []

        for raw_path in paths:
            # ── Resolve path ─────────────────────────────────────────────
            p = Path(raw_path).expanduser()
            if not p.is_absolute() and self._workspace is not None:
                p = self._workspace / p
            try:
                resolved = p.resolve()
            except Exception:
                resolved = p

            if not resolved.is_file():
                skipped.append(f"{raw_path} (not found)")
                continue

            str_path = str(resolved)
            if str_path in self._sent_paths:
                skipped.append(f"{resolved.name} (already sent this turn)")
                continue

            # ── Queue for delivery ───────────────────────────────────────
            msg = OutboundMessage(
                channel=channel,
                chat_id=chat_id,
                content="",
                media=[str_path],
            )
            try:
                await self._send_callback(msg)
                self._sent_paths.add(str_path)
                sent.append(resolved.name)
                logger.debug("send_file queued: {} → {}", resolved.name, channel)
            except Exception as e:
                skipped.append(f"{resolved.name} (error: {e})")

        parts: list[str] = []
        if sent:
            parts.append(f"Sent: {', '.join(sent)}")
        if skipped:
            parts.append(f"Skipped: {', '.join(skipped)}")
        return "; ".join(parts) if parts else "No files sent."
