"""Heartbeat service - periodic agent wake-up to check for tasks."""

from __future__ import annotations

import asyncio
from pathlib import Path
from typing import TYPE_CHECKING, Any, Callable, Coroutine

from loguru import logger

if TYPE_CHECKING:
    from nanobot.session.manager import SessionManager

# Default interval: 30 minutes
DEFAULT_HEARTBEAT_INTERVAL_S = 30 * 60

# The prompt sent to agent during heartbeat
HEARTBEAT_PROMPT = """Read HEARTBEAT.md in your workspace (if it exists).
Follow any instructions or tasks listed there.
If nothing needs attention, reply with just: HEARTBEAT_OK"""

# Token that indicates "nothing to do"
HEARTBEAT_OK_TOKEN = "HEARTBEAT_OK"


def _is_heartbeat_empty(content: str | None) -> bool:
    """Check if HEARTBEAT.md has no actionable content."""
    if not content:
        return True

    # Lines to skip: empty, headers, HTML comments, empty checkboxes
    skip_patterns = {"- [ ]", "* [ ]", "- [x]", "* [x]"}

    for line in content.split("\n"):
        line = line.strip()
        if not line or line.startswith("#") or line.startswith("<!--") or line in skip_patterns:
            continue
        return False  # Found actionable content

    return True


class HeartbeatService:
    """
    Periodic heartbeat service that wakes the agent to check for tasks.

    The agent reads HEARTBEAT.md from the workspace and executes any
    tasks listed there. If nothing needs attention, it replies HEARTBEAT_OK.
    """

    def __init__(
        self,
        workspace: Path,
        on_heartbeat: Callable[[str, dict[str, Any] | None], Coroutine[Any, Any, str]] | None = None,
        interval_s: int = DEFAULT_HEARTBEAT_INTERVAL_S,
        enabled: bool = True,
        session_manager: SessionManager | None = None,
        target_session_key: str = "telegram:239824268",
        idle_threshold_s: int = 30 * 60,  # 30 minutes
    ):
        self.workspace = workspace
        self.on_heartbeat = on_heartbeat
        self.interval_s = interval_s
        self.enabled = enabled
        self.session_manager = session_manager
        self.target_session_key = target_session_key
        self.idle_threshold_s = idle_threshold_s
        self._running = False
        self._task: asyncio.Task | None = None

    @property
    def heartbeat_file(self) -> Path:
        return self.workspace / "HEARTBEAT.md"

    def _read_heartbeat_file(self) -> str | None:
        """Read HEARTBEAT.md content."""
        if self.heartbeat_file.exists():
            try:
                return self.heartbeat_file.read_text()
            except Exception:
                return None
        return None

    async def start(self) -> None:
        """Start the heartbeat service."""
        if not self.enabled:
            logger.info("Heartbeat disabled")
            return

        # Idempotent: don't create a new task if already running
        if self._task is not None and not self._task.done():
            return

        self._running = True
        self._task = asyncio.create_task(self._run_loop())
        logger.info(f"Heartbeat started (every {self.interval_s}s)")

    def stop(self) -> None:
        """Stop the heartbeat service."""
        self._running = False
        if self._task:
            self._task.cancel()
            self._task = None

    async def _run_loop(self) -> None:
        """Main heartbeat loop."""
        while self._running:
            try:
                await asyncio.sleep(self.interval_s)
                if self._running:
                    await self._tick()
            except asyncio.CancelledError:
                break
            except Exception as e:
                logger.error(f"Heartbeat error: {e}")

    async def _tick(self) -> None:
        """Execute a single heartbeat tick."""

        # Check if user is idle (if session manager provided)
        if self.session_manager and self.target_session_key:
            try:
                session = self.session_manager.get_or_create(self.target_session_key)

                # Find last real user message timestamp (exclude system-generated messages)
                # Real Telegram messages have sender_id like "239824268|username"
                # System messages (heartbeat, cron) created via process_direct have sender_id="user"
                # Old messages may not have sender_id field (backwards compat: treat as real user messages)
                last_user_timestamp = None
                for msg in reversed(session.messages):
                    if msg.get("role") == "user":
                        sender_id = msg.get("sender_id")
                        # Skip if explicitly marked as system-generated
                        if sender_id == "user":
                            continue
                        # Accept if no sender_id (old message) or if real user ID
                        last_user_timestamp = msg.get("timestamp")
                        break

                if last_user_timestamp:
                    from datetime import datetime
                    last_dt = datetime.fromisoformat(last_user_timestamp)
                    elapsed = (datetime.now() - last_dt).total_seconds()

                    if elapsed < self.idle_threshold_s:
                        logger.debug(f"Heartbeat: user active {int(elapsed)}s ago, skipping")
                        return  # User is active, don't trigger heartbeat
            except Exception as e:
                logger.warning(f"Heartbeat: error checking idle state: {e}")
                # Continue with heartbeat on error (fail open)

        # Original heartbeat logic
        content = self._read_heartbeat_file()

        # Skip if HEARTBEAT.md is empty or doesn't exist
        if _is_heartbeat_empty(content):
            logger.debug("Heartbeat: no tasks (HEARTBEAT.md empty)")
            return

        logger.info("Heartbeat: user idle, checking for tasks...")

        if self.on_heartbeat:
            try:
                # Call with suppress_output metadata
                await self.on_heartbeat(
                    HEARTBEAT_PROMPT,
                    metadata={"suppress_output": True}
                )

                # Note: HEARTBEAT_OK check removed - suppress mode makes it unnecessary
                logger.info("Heartbeat: completed")

            except Exception as e:
                logger.error(f"Heartbeat execution failed: {e}")

    async def trigger_now(self) -> str | None:
        """Manually trigger a heartbeat."""
        if self.on_heartbeat:
            return await self.on_heartbeat(HEARTBEAT_PROMPT, metadata={"suppress_output": True})
        return None
