"""
DIY Security System — Phones as Cameras + Laptop as Brain
=========================================================
Turn one or more old Android phones into motion-detecting security
cameras. Each phone streams its camera; this script (on your laptop)
watches the streams, detects motion, and sends a snapshot to your
phone via the Telegram API.

How it works:
    Phone (IP Webcam app) --MJPEG--> Laptop (OpenCV motion detection)
                                          |
                                          +--snapshot--> Telegram bot

Set up the phones (no API key needed):
    1. On each Android phone, install the free "IP Webcam" app
       (by Pavel Khlebovich) from the Play Store.
    2. Open it, scroll down, tap "Start server".
    3. It shows a URL like http://192.168.1.50:8080 — the live snapshot
       is at  http://192.168.1.50:8080/shot.jpg
    4. Keep the phone on its charger, on the same Wi-Fi as the laptop.

Get a Telegram bot token (for the alerts — free):
    1. In Telegram, message @BotFather, send /newbot, copy the token.
    2. Message your new bot once, then open
       https://api.telegram.org/bot<TOKEN>/getUpdates and read
       result[].message.chat.id

Setup:
    pip install opencv-python requests numpy
    export TELEGRAM_BOT_TOKEN="123456:ABC..."
    export TELEGRAM_CHAT_ID="..."
    # Comma-separated phone snapshot URLs:
    export CAMERA_URLS="http://192.168.1.50:8080/shot.jpg,http://192.168.1.51:8080/shot.jpg"

Run:
    python phone_security_demo.py

Note: For full remote control of an Android phone (screen, taps, files)
use `adb` + `scrcpy` over USB/Wi-Fi instead — that's device control,
not a cloud API.
"""

import os
import time

import cv2
import numpy as np
import requests

MOTION_AREA = 5000        # min changed-pixel area to count as motion
COOLDOWN_SECONDS = 30     # don't spam alerts for the same event


def fetch_frame(url: str):
    """Grab one JPEG snapshot from a phone and decode it to an image."""
    response = requests.get(url, timeout=5)
    response.raise_for_status()
    buffer = np.frombuffer(response.content, dtype=np.uint8)
    return cv2.imdecode(buffer, cv2.IMREAD_COLOR)


def has_motion(prev_gray, gray) -> bool:
    """Return True if enough pixels changed between two grayscale frames."""
    delta = cv2.absdiff(prev_gray, gray)
    thresh = cv2.threshold(delta, 25, 255, cv2.THRESH_BINARY)[1]
    thresh = cv2.dilate(thresh, None, iterations=2)
    contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    return any(cv2.contourArea(c) > MOTION_AREA for c in contours)


def send_alert(image, camera: str) -> None:
    """Send a snapshot to Telegram when motion is detected."""
    token = os.environ.get("TELEGRAM_BOT_TOKEN")
    ok, jpg = cv2.imencode(".jpg", image)
    requests.post(
        f"https://api.telegram.org/bot{token}/sendPhoto",
        data={"chat_id": os.environ.get("TELEGRAM_CHAT_ID"),
              "caption": f"⚠️ Motion detected on {camera}"},
        files={"photo": ("alert.jpg", jpg.tobytes(), "image/jpeg")},
    )


def main() -> None:
    urls = [u.strip() for u in os.environ.get("CAMERA_URLS", "").split(",") if u.strip()]
    if not urls:
        raise SystemExit("Set CAMERA_URLS to your phone snapshot URLs.")

    prev = {}        # camera url -> last grayscale frame
    last_alert = {}  # camera url -> timestamp of last alert
    print(f"Watching {len(urls)} camera(s). Ctrl+C to stop.")

    while True:
        for url in urls:
            try:
                frame = fetch_frame(url)
            except requests.RequestException:
                continue  # phone briefly unreachable; try again next loop
            gray = cv2.GaussianBlur(cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY), (21, 21), 0)

            if url in prev and has_motion(prev[url], gray):
                if time.time() - last_alert.get(url, 0) > COOLDOWN_SECONDS:
                    send_alert(frame, url)
                    last_alert[url] = time.time()
                    print(f"Motion on {url} — alert sent.")
            prev[url] = gray
        time.sleep(0.5)


if __name__ == "__main__":
    main()
