from __future__ import annotations

import importlib.util
import sys
from collections.abc import Awaitable, Callable
from pathlib import Path
from typing import Any

NotificationEventFactory = Callable[[dict[str, Any]], dict[str, Any] | None]
RunNanobotTurn = Callable[..., Awaitable[dict[str, Any]]]


class InboxService:
    def __init__(self, *, repo_dir: Path, inbox_dir: Path) -> None:
        self._repo_dir = repo_dir
        self._inbox_dir = inbox_dir
        self._module: Any | None = None

    def _load_module(self):
        if self._module is not None:
            return self._module

        script_path = self._repo_dir / "scripts" / "inbox_board.py"
        if not script_path.exists():
            raise RuntimeError(f"missing helper script: {script_path}")
        spec = importlib.util.spec_from_file_location("nanobot_web_inbox_board", script_path)
        if spec is None or spec.loader is None:
            raise RuntimeError(f"failed to load helper script: {script_path}")
        module = importlib.util.module_from_spec(spec)
        sys.modules.setdefault("nanobot_web_inbox_board", module)
        spec.loader.exec_module(module)
        module.ensure_inbox(self._inbox_dir)
        self._module = module
        return module

    def list_items(
        self,
        *,
        status_filter: str | None,
        kind_filter: str | None,
        include_closed: bool,
        limit: int | None,
        tags: list[str],
    ) -> list[dict[str, Any]]:
        inbox_board = self._load_module()
        items = inbox_board.filter_items(
            inbox_board.collect_items(self._inbox_dir),
            status=status_filter,
            kind=kind_filter,
            tags=tags,
            include_closed=include_closed,
        )
        if limit is not None:
            items = items[:limit]
        return [item.to_dict() for item in items]

    def open_items(self, limit: int = 4) -> list[dict[str, Any]]:
        return self.list_items(
            status_filter=None,
            kind_filter=None,
            include_closed=False,
            limit=limit,
            tags=[],
        )

    def capture_item(
        self,
        *,
        title: str,
        text: str,
        kind: str,
        source: str,
        due: str,
        body: str,
        session_id: str,
        tags: list[str],
        confidence: int | float | None,
    ) -> dict[str, Any]:
        inbox_board = self._load_module()
        created_path = inbox_board.create_item(
            self._inbox_dir,
            title=title.strip(),
            kind=kind,
            source=source,
            confidence=confidence,
            suggested_due=due.strip(),
            tags=tags,
            body=body.strip(),
            raw_text=text.strip(),
            metadata={"source_session": session_id.strip()} if session_id.strip() else None,
        )
        return inbox_board.parse_item(created_path).to_dict()

    def dismiss_item(self, item: str) -> dict[str, Any]:
        inbox_board = self._load_module()
        updated_path = inbox_board.dismiss_item(self._inbox_dir, item)
        return inbox_board.parse_item(updated_path).to_dict()

    async def accept_item_as_task(
        self,
        *,
        item: str,
        lane: str,
        title: str,
        due: str,
        body_text: str | None,
        tags: list[str] | None,
        run_nanobot_turn: RunNanobotTurn,
        notification_to_event: NotificationEventFactory,
    ) -> dict[str, Any]:
        inbox_board = self._load_module()
        parsed_item = inbox_board.parse_item(inbox_board.resolve_item_path(self._inbox_dir, item))
        item_path = str(parsed_item.path)

        instruction_lines = [
            "This is a UI inbox action. Groom the inbox item into a task if it is actionable.",
            f"Inbox item path: {item_path}",
            "Use the inbox_board tool and the task_helper_card tool.",
            "First inspect the item with action=show.",
            "Then improve the title, description, tags, or due date only if the capture supports it.",
            "If it is actionable, accept it into the task board.",
            "After accepting it, decide whether the task would be easier to execute immediately with a linked helper card.",
            "Create a helper card whenever it would clearly reduce friction for the user instead of making them search or prepare things manually.",
            "Use these patterns by default when they fit:",
            "- watch/learn -> create a watch helper card",
            "- read/research -> create a reading/reference helper card",
            "- go somewhere -> create a travel/map helper card",
            "- buy/order -> create a shopping/product helper card",
            "- call/email/reach out -> create an outreach draft helper card",
            "If you have enough information to search or enrich the helper card, do that first.",
            "If not, still create the fallback helper card from the task so the user has something actionable in the feed.",
            "Prefer backlog unless it is clearly committed or in-progress work.",
            "Do not invent details and do not ask follow-up questions.",
            "Reply with a short confirmation of what you did, including whether you created a helper card.",
        ]
        if title.strip():
            instruction_lines.append(f"Title hint from UI: {title.strip()}")
        if body_text is not None and body_text.rstrip():
            instruction_lines.append(f"Description hint from UI: {body_text.rstrip()}")
        if due.strip():
            instruction_lines.append(f"Due hint from UI: {due.strip()}")
        if tags:
            instruction_lines.append(f"Tag hint from UI: {', '.join(str(tag) for tag in tags)}")
        if lane and lane != "backlog":
            instruction_lines.append(f"Preferred destination lane: {lane}")

        return await run_nanobot_turn(
            "\n".join(instruction_lines),
            chat_id="inbox-groomer",
            metadata={
                "source": "web-inbox-card",
                "ui_action": "accept_task",
                "inbox_item": item_path,
            },
            timeout_seconds=90.0,
            notification_to_event=notification_to_event,
        )
