"""Heartbeat service for periodic agent wake-ups."""

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

from loguru import logger


def is_within_active_hours(start: str, end: str, *, test_hour: int | None = None) -> bool:
    """Return whether current time is inside active hours window."""
    if not start or not end:
        return True

    from datetime import datetime

    try:
        sh, sm = (int(x) for x in start.split(":", 1))
        eh, em = (int(x) for x in end.split(":", 1))
    except (TypeError, ValueError):
        return True

    now = datetime.now()
    if test_hour is None:
        now_min = now.hour * 60 + now.minute
    else:
        now_min = int(test_hour) * 60

    start_min = sh * 60 + sm
    end_min = eh * 60 + em

    if start_min <= end_min:
        return start_min <= now_min < end_min
    return now_min >= start_min or now_min < end_min


class HeartbeatService:
    DEFAULT_AUTOPILOT_PROMPT = (
        "Autopilot patrol: review recent context, pending schedules, and recent failures. "
        "Identify one highest bottleneck that blocks user outcomes. "
        "Execute at most one safe action to reduce it; otherwise respond with 'no_action'."
    )

    def __init__(
        self,
        workspace: Any = None,
        interval_s: int = 60,
        on_heartbeat: Callable[[], Coroutine[Any, Any, None]] | None = None,
        enabled: bool = True,
        active_hours_start: str = "",
        active_hours_end: str = "",
        max_tasks_per_beat: int = 5,
        startup_delay_s: int = 30,
        autopilot_enabled: bool = True,
        autopilot_prompt: str = "",
    ):
        self.workspace = workspace
        self.interval_ms = interval_s * 1000
        self.on_beat = on_heartbeat
        self._enabled = enabled
        self.active_hours_start = active_hours_start
        self.active_hours_end = active_hours_end
        self.max_tasks_per_beat = max(1, int(max_tasks_per_beat))
        self._startup_delay_s = startup_delay_s
        self.autopilot_enabled = bool(autopilot_enabled)
        self.autopilot_prompt = (autopilot_prompt or self.DEFAULT_AUTOPILOT_PROMPT).strip()
        self._running = False
        self._task: asyncio.Task | None = None

    async def start(self):
        if not self._enabled:
            return
        self._running = True
        self._task = asyncio.create_task(self._loop())
        logger.info(f"Heartbeat started (interval={self.interval_ms}ms)")

    def stop(self):
        self._running = False
        if self._task:
            self._task.cancel()

    async def _loop(self):
        # Allow channels to fully connect before first heartbeat
        if self._startup_delay_s > 0:
            await asyncio.sleep(self._startup_delay_s)
        while self._running:
            if not is_within_active_hours(self.active_hours_start, self.active_hours_end):
                await asyncio.sleep(self.interval_ms / 1000)
                continue
            if self.on_beat:
                try:
                    tasks = self._load_tasks()
                    if tasks:
                        for task in tasks[:self.max_tasks_per_beat]:
                            await self._dispatch_heartbeat(task)
                    elif not self.workspace:
                        # Simple callback mode (no workspace / tests)
                        await self.on_beat()
                except Exception as e:
                    logger.error(f"Heartbeat callback error: {e}")
            await asyncio.sleep(self.interval_ms / 1000)

    def _load_tasks(self) -> list[str]:
        if not self.workspace:
            return []
        path = Path(self.workspace) / "HEARTBEAT.md"
        content = ""
        if path.exists():
            try:
                content = path.read_text(encoding="utf-8")
            except Exception:
                content = ""
        in_active = False
        tasks: list[str] = []
        for line in content.splitlines():
            header = line.strip().lower()
            if header.startswith("## "):
                if "active tasks" in header:
                    in_active = True
                elif "completed" in header:
                    in_active = False
                continue
            if not in_active:
                continue
            match = re.match(r"\s*-\s*\[\s\]\s+(.*)", line)
            if match:
                task = match.group(1).strip()
                if task:
                    tasks.append(task)
        if not tasks and self.autopilot_enabled and self.autopilot_prompt:
            tasks.append(self.autopilot_prompt)
        return tasks

    async def _dispatch_heartbeat(self, payload: str) -> None:
        if self.on_beat.__code__.co_argcount > 0:
            await self.on_beat(f"Heartbeat task: {payload}")
        else:
            await self.on_beat()
