"""
main.py — Tapo Camera Monitor

Entry point: polls cameras, downloads clips, analyzes via Gemini,
sends Telegram notifications. Handles graceful shutdown on SIGTERM/SIGINT.
"""

import asyncio
import logging
import os
import signal
import sys
import tempfile
from datetime import datetime
from pathlib import Path

from camera import CameraClient, EventStore, TapoEvent, parse_events
from config import load_config
from notifier import send_text, send_video
from video import get_duration, trim_clip
from vision import analyze_video

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
    datefmt="%Y-%m-%d %H:%M:%S",
    stream=sys.stdout,
)
logger = logging.getLogger("tapo-monitor")

# ── Globals ──────────────────────────────────────────────────────────────────
_shutdown = asyncio.Event()


def _handle_signal(sig, frame):
    logger.info("Signal %s received, shutting down…", sig)
    _shutdown.set()


# ── Core pipeline ─────────────────────────────────────────────────────────────

def process_event(
    event: TapoEvent,
    client: CameraClient,
    cfg: dict,
) -> None:
    """
    Full pipeline for a single new event:
    download → trim → analyze → notify → cleanup
    """
    temp_dir = cfg.get("temp_dir", "/tmp/tapo-monitor")
    clip_cfg = cfg.get("clip", {})
    before_sec = float(clip_cfg.get("before_sec", 3))
    after_sec = float(clip_cfg.get("after_sec", 7))

    raw_path = os.path.join(temp_dir, f"{event.event_id}_raw.mp4")
    trimmed_path = os.path.join(temp_dir, f"{event.event_id}_trimmed.mp4")

    try:
        # 1. Download
        logger.info("[%s] Downloading clip for event %s", event.camera_name, event.event_id)
        ok = client.download_clip(event.raw, raw_path)
        if not ok or not Path(raw_path).exists():
            logger.error("[%s] Download failed, skipping.", event.camera_name)
            return

        # 2. Determine event offset within clip
        # The event start_time vs clip start_time (from raw dict)
        clip_start_ts = int(event.raw.get("startTime") or event.raw.get("start_time", 0))
        event_ts = int(event.start_time.timestamp())
        offset = max(0.0, float(event_ts - clip_start_ts))
        logger.debug("Event offset within clip: %.1f sec", offset)

        # 3. Trim
        ok = trim_clip(raw_path, trimmed_path, offset, before_sec, after_sec)
        if not ok or not Path(trimmed_path).exists():
            logger.error("[%s] Trim failed, skipping.", event.camera_name)
            return

        # 4. Analyze with Gemini
        description = analyze_video(trimmed_path, cfg["gemini_api_key"])

        # 5. Send Telegram notification
        send_video(
            bot_token=cfg["telegram"]["bot_token"],
            chat_id=cfg["telegram"]["chat_id"],
            video_path=trimmed_path,
            camera_name=event.camera_name,
            event_type=event.event_type,
            event_time=event.start_time,
            description=description,
        )

    finally:
        # 6. Cleanup temp files
        for path in (raw_path, trimmed_path):
            try:
                if Path(path).exists():
                    os.unlink(path)
                    logger.debug("Deleted temp file: %s", path)
            except Exception as exc:
                logger.warning("Could not delete %s: %s", path, exc)


# ── Polling loop ──────────────────────────────────────────────────────────────

async def poll_camera(
    cam_cfg: dict,
    event_store: EventStore,
    cfg: dict,
) -> None:
    """Poll a single camera for new events."""
    client = CameraClient(
        name=cam_cfg["name"],
        ip=cam_cfg["ip"],
        password=cam_cfg["password"],
        username=cam_cfg.get("username", "admin"),
    )
    name = cam_cfg["name"]

    raw_events = await asyncio.get_event_loop().run_in_executor(
        None, lambda: client.get_events(count=20)
    )

    if not raw_events:
        logger.debug("[%s] No events returned.", name)
        return

    events = parse_events(name, cam_cfg["ip"], raw_events)
    new_events = [e for e in events if not event_store.is_seen(e.event_id)]

    if not new_events:
        logger.debug("[%s] No new events.", name)
        return

    logger.info("[%s] %d new event(s) found.", name, len(new_events))

    for event in new_events:
        # Mark seen BEFORE processing to avoid reprocessing on crash mid-flight
        event_store.mark_seen(event.event_id, event.camera_name)
        try:
            await asyncio.get_event_loop().run_in_executor(
                None, lambda e=event: process_event(e, client, cfg)
            )
        except Exception as exc:
            logger.exception("[%s] Unhandled error processing event %s: %s", name, event.event_id, exc)


async def main_loop(cfg: dict) -> None:
    interval = int(cfg.get("polling_interval", 10))
    event_store = EventStore(cfg.get("events_db", "/var/lib/tapo-monitor/events.db"))
    cameras = [c for c in cfg.get("cameras", []) if c.get("enabled", True)]

    if not cameras:
        logger.error("No cameras configured or all disabled.")
        return

    logger.info("Starting Tapo Monitor | %d camera(s) | poll every %ds", len(cameras), interval)

    # Startup notification
    try:
        names = ", ".join(c["name"] for c in cameras)
        send_text(
            cfg["telegram"]["bot_token"],
            cfg["telegram"]["chat_id"],
            f"🟢 Tapo Monitor запущен\nКамеры: {names}\nИнтервал: {interval}с",
        )
    except Exception:
        pass

    cleanup_counter = 0
    while not _shutdown.is_set():
        tasks = [
            poll_camera(cam, event_store, cfg)
            for cam in cameras
        ]
        await asyncio.gather(*tasks, return_exceptions=True)

        # Periodic DB cleanup (every ~100 cycles)
        cleanup_counter += 1
        if cleanup_counter >= 100:
            event_store.cleanup_old(keep_days=7)
            cleanup_counter = 0

        try:
            await asyncio.wait_for(_shutdown.wait(), timeout=interval)
        except asyncio.TimeoutError:
            pass  # Normal: timeout means keep polling

    logger.info("Shutdown complete.")
    try:
        send_text(
            cfg["telegram"]["bot_token"],
            cfg["telegram"]["chat_id"],
            "🔴 Tapo Monitor остановлен.",
        )
    except Exception:
        pass


# ── Entry point ───────────────────────────────────────────────────────────────

def main():
    signal.signal(signal.SIGTERM, _handle_signal)
    signal.signal(signal.SIGINT, _handle_signal)

    config_path = os.environ.get("TAPO_CONFIG", "config.yaml")
    try:
        cfg = load_config(config_path)
    except Exception as exc:
        logging.critical("Failed to load config: %s", exc)
        sys.exit(1)

    asyncio.run(main_loop(cfg))


if __name__ == "__main__":
    main()
