"""Scheduled task models for Beaver cron.

Every trigger targets Beaver Task mode so scheduled work remains visible as a
normal Task instead of a detached agent turn.
"""

from __future__ import annotations

from dataclasses import dataclass, field
from typing import Any, Literal
from uuid import uuid4


CronScheduleKind = Literal["at", "every", "cron"]
CronPayloadKind = Literal["agent_turn", "system_event"]
CronPayloadMode = Literal["notification", "task"]


@dataclass(slots=True)
class CronSchedule:
    kind: CronScheduleKind
    at_ms: int | None = None
    every_ms: int | None = None
    expr: str | None = None
    tz: str | None = None
    display: str | None = None

    def to_dict(self) -> dict[str, Any]:
        return {
            "kind": self.kind,
            "at_ms": self.at_ms,
            "every_ms": self.every_ms,
            "expr": self.expr,
            "tz": self.tz,
            "display": self.display,
        }

    @classmethod
    def from_dict(cls, payload: dict[str, Any]) -> "CronSchedule":
        return cls(
            kind=str(payload.get("kind") or "every"),  # type: ignore[arg-type]
            at_ms=_optional_int(payload.get("at_ms") or payload.get("atMs")),
            every_ms=_optional_int(payload.get("every_ms") or payload.get("everyMs")),
            expr=_optional_str(payload.get("expr")),
            tz=_optional_str(payload.get("tz")),
            display=_optional_str(payload.get("display")),
        )


@dataclass(slots=True)
class CronPayload:
    kind: CronPayloadKind = "agent_turn"
    mode: CronPayloadMode = "notification"
    message: str = ""
    session_key: str | None = None
    requires_followup: bool = False
    deliver: bool = False
    channel: str | None = None
    to: str | None = None

    def to_dict(self) -> dict[str, Any]:
        return {
            "kind": self.kind,
            "mode": self.mode,
            "message": self.message,
            "session_key": self.session_key,
            "requires_followup": self.requires_followup,
            "deliver": self.deliver,
            "channel": self.channel,
            "to": self.to,
        }

    @classmethod
    def from_dict(cls, payload: dict[str, Any]) -> "CronPayload":
        return cls(
            kind=str(payload.get("kind") or "agent_turn"),  # type: ignore[arg-type]
            mode=_payload_mode(payload.get("mode"), default="task"),
            message=str(payload.get("message") or ""),
            session_key=_optional_str(payload.get("session_key") or payload.get("sessionKey")),
            requires_followup=bool(payload.get("requires_followup") or payload.get("requiresFollowup") or False),
            deliver=bool(payload.get("deliver", False)),
            channel=_optional_str(payload.get("channel")),
            to=_optional_str(payload.get("to")),
        )


@dataclass(slots=True)
class CronRunRecord:
    started_at_ms: int
    scheduled_run_id: str = field(default_factory=lambda: uuid4().hex)
    finished_at_ms: int | None = None
    status: Literal["running", "ok", "error", "skipped"] = "running"
    mode: CronPayloadMode = "notification"
    notification_session_id: str | None = None
    output: str | None = None
    task_id: str | None = None
    run_id: str | None = None
    error: str | None = None
    engaged: bool = False
    engaged_at_ms: int | None = None
    engage_intent: str | None = None

    def to_dict(self) -> dict[str, Any]:
        return {
            "scheduled_run_id": self.scheduled_run_id,
            "started_at_ms": self.started_at_ms,
            "finished_at_ms": self.finished_at_ms,
            "status": self.status,
            "mode": self.mode,
            "notification_session_id": self.notification_session_id,
            "output": self.output,
            "task_id": self.task_id,
            "run_id": self.run_id,
            "error": self.error,
            "engaged": self.engaged,
            "engaged_at_ms": self.engaged_at_ms,
            "engage_intent": self.engage_intent,
        }

    @classmethod
    def from_dict(cls, payload: dict[str, Any]) -> "CronRunRecord":
        return cls(
            scheduled_run_id=str(payload.get("scheduled_run_id") or payload.get("scheduledRunId") or uuid4().hex),
            started_at_ms=int(payload.get("started_at_ms") or payload.get("startedAtMs") or 0),
            finished_at_ms=_optional_int(payload.get("finished_at_ms") or payload.get("finishedAtMs")),
            status=str(payload.get("status") or "running"),  # type: ignore[arg-type]
            mode=_payload_mode(payload.get("mode"), default="notification"),
            notification_session_id=_optional_str(payload.get("notification_session_id") or payload.get("notificationSessionId")),
            output=_optional_str(payload.get("output")),
            task_id=_optional_str(payload.get("task_id") or payload.get("taskId")),
            run_id=_optional_str(payload.get("run_id") or payload.get("runId")),
            error=_optional_str(payload.get("error")),
            engaged=bool(payload.get("engaged", False)),
            engaged_at_ms=_optional_int(payload.get("engaged_at_ms") or payload.get("engagedAtMs")),
            engage_intent=_optional_str(payload.get("engage_intent") or payload.get("engageIntent")),
        )


@dataclass(slots=True)
class CronJob:
    id: str
    name: str
    enabled: bool
    schedule: CronSchedule
    payload: CronPayload
    created_at_ms: int
    updated_at_ms: int
    next_run_at_ms: int | None = None
    last_run_at_ms: int | None = None
    last_status: Literal["ok", "error", "skipped"] | None = None
    last_error: str | None = None
    delete_after_run: bool = False
    history: list[CronRunRecord] = field(default_factory=list)

    def to_dict(self) -> dict[str, Any]:
        return {
            "id": self.id,
            "name": self.name,
            "enabled": self.enabled,
            "schedule": self.schedule.to_dict(),
            "payload": self.payload.to_dict(),
            "created_at_ms": self.created_at_ms,
            "updated_at_ms": self.updated_at_ms,
            "next_run_at_ms": self.next_run_at_ms,
            "last_run_at_ms": self.last_run_at_ms,
            "last_status": self.last_status,
            "last_error": self.last_error,
            "delete_after_run": self.delete_after_run,
            "history": [item.to_dict() for item in self.history],
        }

    def to_api_dict(self) -> dict[str, Any]:
        latest = self.history[-1] if self.history else None
        return {
            "id": self.id,
            "name": self.name,
            "enabled": self.enabled,
            "schedule_kind": self.schedule.kind,
            "schedule_display": self.schedule.display or _schedule_display(self.schedule),
            "schedule_expr": self.schedule.expr,
            "schedule_every_ms": self.schedule.every_ms,
            "message": self.payload.message,
            "mode": self.payload.mode,
            "requires_followup": self.payload.requires_followup,
            "deliver": self.payload.deliver,
            "channel": self.payload.channel,
            "to": self.payload.to,
            "session_key": self.payload.session_key,
            "next_run_at_ms": self.next_run_at_ms,
            "last_run_at_ms": self.last_run_at_ms,
            "last_status": self.last_status,
            "last_error": self.last_error,
            "last_scheduled_run_id": latest.scheduled_run_id if latest else None,
            "last_task_id": latest.task_id if latest else None,
            "last_run_id": latest.run_id if latest else None,
            "history": [item.to_dict() for item in self.history],
            "created_at_ms": self.created_at_ms,
            "updated_at_ms": self.updated_at_ms,
        }

    @classmethod
    def from_dict(cls, payload: dict[str, Any]) -> "CronJob":
        schedule_payload = payload.get("schedule") if isinstance(payload.get("schedule"), dict) else {}
        payload_payload = payload.get("payload") if isinstance(payload.get("payload"), dict) else {}
        return cls(
            id=str(payload["id"]),
            name=str(payload.get("name") or payload["id"]),
            enabled=bool(payload.get("enabled", True)),
            schedule=CronSchedule.from_dict(schedule_payload),
            payload=CronPayload.from_dict(payload_payload),
            created_at_ms=int(payload.get("created_at_ms") or payload.get("createdAtMs") or 0),
            updated_at_ms=int(payload.get("updated_at_ms") or payload.get("updatedAtMs") or 0),
            next_run_at_ms=_optional_int(payload.get("next_run_at_ms") or payload.get("nextRunAtMs")),
            last_run_at_ms=_optional_int(payload.get("last_run_at_ms") or payload.get("lastRunAtMs")),
            last_status=_optional_str(payload.get("last_status") or payload.get("lastStatus")),  # type: ignore[arg-type]
            last_error=_optional_str(payload.get("last_error") or payload.get("lastError")),
            delete_after_run=bool(payload.get("delete_after_run") or payload.get("deleteAfterRun") or False),
            history=[
                CronRunRecord.from_dict(item)
                for item in payload.get("history") or []
                if isinstance(item, dict)
            ],
        )


@dataclass(slots=True)
class CronExecutionResult:
    response: str | None = None
    task_id: str | None = None
    run_id: str | None = None
    notification_session_id: str | None = None
    mode: CronPayloadMode = "notification"


def _schedule_display(schedule: CronSchedule) -> str:
    if schedule.kind == "every":
        seconds = int((schedule.every_ms or 0) / 1000)
        return f"every {seconds}s"
    if schedule.kind == "cron":
        return schedule.expr or "cron"
    return "one-time"


def _optional_str(value: Any) -> str | None:
    if value in (None, ""):
        return None
    return str(value)


def _optional_int(value: Any) -> int | None:
    if value in (None, ""):
        return None


def _payload_mode(value: Any, *, default: CronPayloadMode = "notification") -> CronPayloadMode:
    if value in (None, ""):
        return default
    cleaned = str(value or "").strip().lower()
    if cleaned == "task":
        return "task"
    return "notification"
    try:
        return int(value)
    except (TypeError, ValueError):
        return None
