# 中文注释：
# 文件：echobot/agent/tools/cron.py
# 说明：智能体工具层，实现文件、Shell、网络与消息等工具调用能力。

"""Cron tools for scheduling and managing reminders/tasks."""

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

from echobot.agent.tools.base import Tool
from echobot.cron.service import CronService
from echobot.cron.types import CronSchedule


class _BaseCronTool(Tool):
    """Shared logic for cron-related tools."""

    def __init__(
        self,
        store_path: Path,
        service_getter: Callable[[], CronService | None] | None = None,
    ):
        self.store_path = store_path
        self._service_getter = service_getter

    def _get_service(self) -> CronService:
        # If gateway injected a running cron service, use it for immediate effect.
        if self._service_getter:
            service = self._service_getter()
            if service:
                return service

        # Fallback for non-gateway contexts (e.g. direct agent command).
        self.store_path.parent.mkdir(parents=True, exist_ok=True)
        return CronService(self.store_path)

    @staticmethod
    def _format_ts(ts_ms: int | None) -> str:
        if not ts_ms:
            return "-"
        return datetime.fromtimestamp(ts_ms / 1000).strftime("%Y-%m-%d %H:%M:%S")

    @staticmethod
    def _format_schedule(job_kind: str, every_ms: int | None, expr: str | None, at_ms: int | None) -> str:
        if job_kind == "every":
            return f"every {(every_ms or 0) // 1000}s"
        if job_kind == "cron":
            return expr or ""
        return f"at {datetime.fromtimestamp((at_ms or 0) / 1000).strftime('%Y-%m-%d %H:%M:%S')}"


class CreateCronJobTool(_BaseCronTool):
    """Create a scheduled cron job for reminders/tasks."""

    def __init__(
        self,
        store_path: Path,
        service_getter: Callable[[], CronService | None] | None = None,
    ):
        super().__init__(store_path=store_path, service_getter=service_getter)
        self._default_channel = ""
        self._default_chat_id = ""

    def set_context(self, channel: str, chat_id: str) -> None:
        self._default_channel = channel
        self._default_chat_id = chat_id

    @property
    def name(self) -> str:
        return "create_cron_job"

    @property
    def description(self) -> str:
        return (
            "Create a scheduled reminder/task. "
            "Supports every_seconds, cron_expr, or at (ISO datetime). "
            "Use this when users ask for future proactive reminders/messages. "
            "For proactive content delivery (e.g. daily news), set `message` as the exact task "
            "to execute at runtime and send directly, not a reminder asking user to reply."
        )

    @property
    def parameters(self) -> dict[str, Any]:
        return {
            "type": "object",
            "properties": {
                "name": {"type": "string", "description": "Human-readable job name"},
                "message": {
                    "type": "string",
                    "description": (
                        "Exact runtime task for the future agent turn. "
                        "Example: '搜索今天的新闻并直接发送摘要给用户'。"
                    ),
                },
                "every_seconds": {
                    "type": "integer",
                    "minimum": 1,
                    "description": "Run every N seconds (choose one schedule mode)",
                },
                "cron_expr": {
                    "type": "string",
                    "description": "Cron expression, e.g. '0 8 * * *' (choose one schedule mode)",
                },
                "timezone": {
                    "type": "string",
                    "description": "Optional IANA timezone for cron_expr, e.g. 'Asia/Shanghai'",
                },
                "at": {
                    "type": "string",
                    "description": "Run once at ISO datetime, e.g. '2026-02-10T08:00:00'",
                },
                "deliver": {
                    "type": "boolean",
                    "description": "Whether to deliver execution result back to chat",
                    "default": True,
                },
                "channel": {
                    "type": "string",
                    "description": "Optional target channel override (defaults to current chat channel)",
                },
                "chat_id": {
                    "type": "string",
                    "description": "Optional target chat_id override (defaults to current chat)",
                },
                "delete_after_run": {
                    "type": "boolean",
                    "description": "Delete one-shot job after successful run",
                    "default": False,
                },
            },
            "required": ["name", "message"],
        }

    async def execute(
        self,
        name: str,
        message: str,
        every_seconds: int | None = None,
        cron_expr: str | None = None,
        timezone: str | None = None,
        at: str | None = None,
        deliver: bool = True,
        channel: str | None = None,
        chat_id: str | None = None,
        delete_after_run: bool = False,
        **kwargs: Any,
    ) -> str:
        schedule_modes = [every_seconds is not None, bool(cron_expr), bool(at)]
        if sum(schedule_modes) != 1:
            return "Error: Exactly one of every_seconds, cron_expr, or at must be provided."

        try:
            if every_seconds is not None:
                schedule = CronSchedule(kind="every", every_ms=every_seconds * 1000)
            elif cron_expr:
                schedule = CronSchedule(kind="cron", expr=cron_expr, tz=timezone)
            else:
                dt = datetime.fromisoformat(at or "")
                schedule = CronSchedule(kind="at", at_ms=int(dt.timestamp() * 1000))
        except Exception as e:
            return f"Error: Invalid schedule parameters: {e}"

        target_channel = channel or self._default_channel
        target_chat_id = chat_id or self._default_chat_id

        if deliver and (not target_channel or not target_chat_id):
            return "Error: deliver=true requires channel/chat target (not available in current context)."

        service = self._get_service()
        job = await service.add_job(
            name=name,
            schedule=schedule,
            message=message,
            deliver=deliver,
            channel=target_channel if deliver else channel,
            to=target_chat_id if deliver else chat_id,
            delete_after_run=delete_after_run,
        )

        next_run = self._format_ts(job.state.next_run_at_ms)
        return f"Created cron job '{job.name}' (id: {job.id}). Next run: {next_run}"


class ListCronJobsTool(_BaseCronTool):
    """List existing cron jobs."""

    @property
    def name(self) -> str:
        return "list_cron_jobs"

    @property
    def description(self) -> str:
        return "List scheduled cron jobs with id, status, schedule, and next run time."

    @property
    def parameters(self) -> dict[str, Any]:
        return {
            "type": "object",
            "properties": {
                "include_disabled": {
                    "type": "boolean",
                    "default": True,
                    "description": "Whether to include disabled jobs",
                },
                "limit": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 100,
                    "default": 20,
                    "description": "Maximum number of jobs to return",
                },
            },
            "required": [],
        }

    async def execute(
        self,
        include_disabled: bool = True,
        limit: int = 20,
        **kwargs: Any,
    ) -> str:
        service = self._get_service()
        jobs = service.list_jobs(include_disabled=include_disabled)[:limit]
        if not jobs:
            return "No scheduled jobs."

        lines = ["Scheduled jobs:"]
        for job in jobs:
            schedule_text = self._format_schedule(
                job_kind=job.schedule.kind,
                every_ms=job.schedule.every_ms,
                expr=job.schedule.expr,
                at_ms=job.schedule.at_ms,
            )
            next_run = self._format_ts(job.state.next_run_at_ms)
            status = "enabled" if job.enabled else "disabled"
            lines.append(f"- {job.id} | {job.name} | {status} | {schedule_text} | next: {next_run}")

        return "\n".join(lines)


class RemoveCronJobTool(_BaseCronTool):
    """Remove a cron job by id."""

    @property
    def name(self) -> str:
        return "remove_cron_job"

    @property
    def description(self) -> str:
        return "Remove a scheduled cron job by job id."

    @property
    def parameters(self) -> dict[str, Any]:
        return {
            "type": "object",
            "properties": {
                "job_id": {"type": "string", "description": "Cron job id"},
            },
            "required": ["job_id"],
        }

    async def execute(self, job_id: str, **kwargs: Any) -> str:
        service = self._get_service()
        removed = await service.remove_job(job_id)
        if removed:
            return f"Removed cron job {job_id}"
        return f"Cron job {job_id} not found"
