"""ScreenshotScheduler — periodic capture + annotate + send.

Runs as an asyncio task. capture() and cv2 work execute in asyncio.to_thread
to avoid blocking the event loop. Decision 13: scheduler calls capture()
directly, NOT via Detector.
"""
from __future__ import annotations

import asyncio
import logging
import time
from pathlib import Path
from typing import Callable

from .notifier import Alert

logger = logging.getLogger(__name__)


class ScreenshotScheduler:
    """Periodic screenshot sender.

    Constructor params are explicit (decision 11 outside-voice finding).
    """

    def __init__(
        self,
        capture: Callable,               # () -> ndarray | None
        save_fn: Callable,               # (frame, label, now) -> Path | None
        notifier,                        # _NotifierLike
        audit,                           # _AuditLike
        interval_s: int | None = None,
    ) -> None:
        self._capture = capture
        self._save_fn = save_fn
        self._notifier = notifier
        self._audit = audit
        self._interval_s = interval_s
        self._is_running = False
        self._next_due: float | None = None  # monotonic

    # ------------------------------------------------------------------
    # Public state
    # ------------------------------------------------------------------

    @property
    def is_running(self) -> bool:
        return self._is_running

    @property
    def interval_s(self) -> int | None:
        return self._interval_s

    @property
    def next_due(self) -> float | None:
        return self._next_due

    # ------------------------------------------------------------------
    # Control (called from async event loop)
    # ------------------------------------------------------------------

    def start(self, interval_s: int) -> None:
        self._interval_s = interval_s
        self._is_running = True
        self._next_due = time.monotonic() + interval_s

    def stop(self) -> None:
        self._is_running = False
        self._next_due = None

    # ------------------------------------------------------------------
    # Task body
    # ------------------------------------------------------------------

    async def run(self) -> None:
        """Runs until cancelled."""
        while True:
            await asyncio.sleep(1)
            if not self._is_running or self._next_due is None:
                continue
            if time.monotonic() >= self._next_due:
                await self._take_screenshot()
                if self._is_running and self._interval_s is not None:
                    self._next_due = time.monotonic() + self._interval_s

    async def _take_screenshot(self) -> None:
        now = time.time()
        try:
            frame = await asyncio.to_thread(self._capture)
        except Exception as exc:
            logger.warning("ScreenshotScheduler capture failed: %s", exc)
            self._audit.log({"ts": now, "event": "screenshot_sent", "status": "capture_failed", "error": str(exc)})
            self._notifier.send(Alert(
                kind="warn",
                title="Captură eșuată — verificați fereastra TradeStation",
                body="",
                silent=True,
            ))
            return

        if frame is None:
            self._notifier.send(Alert(
                kind="warn",
                title="Captură eșuată — verificați fereastra TradeStation",
                body="",
                silent=True,
            ))
            return

        path = await asyncio.to_thread(self._save_fn, frame, "poll", now)
        self._audit.log({"ts": now, "event": "screenshot_sent", "path": str(path) if path else None})
        self._notifier.send(Alert(
            kind="screenshot",
            title="Screenshot periodic",
            body="",
            image_path=path,
            silent=True,
        ))
