"""
============================================================
Follower Detection – Python Server  (server.py)
============================================================
Run this on your laptop / Raspberry Pi connected to the
same WiFi hotspot as the ESP32-CAM.

Requirements:
    pip install flask face_recognition requests numpy pillow

Usage:
    python server.py

The server listens on 0.0.0.0:5000 so the ESP32-CAM can
reach it over WiFi.
============================================================
"""

import os
import io
import time
import logging
import requests
import numpy as np
from flask import Flask, request, jsonify
from PIL import Image
import face_recognition

# ──────────────────────────────────────────────────────────
#  USER CONFIG  ← edit these
# ──────────────────────────────────────────────────────────
TELEGRAM_BOT_TOKEN  = "YOUR_BOT_TOKEN_HERE"      # from @BotFather
TELEGRAM_CHAT_ID    = "YOUR_CHAT_ID_HERE"         # your personal chat ID

# How many times the same face must appear before alerting
FOLLOW_THRESHOLD = 3

# Seconds a face must be absent before its counter resets
FORGET_AFTER_SECONDS = 120

# Face matching tolerance: lower = stricter (0.5 is a good start)
FACE_MATCH_TOLERANCE = 0.50

# Where to save the last photo that triggered an alert
ALERT_PHOTO_PATH = "alert_frame.jpg"
# ──────────────────────────────────────────────────────────

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s  %(levelname)s  %(message)s",
    datefmt="%H:%M:%S",
)
log = logging.getLogger(__name__)

app = Flask(__name__)

# ── State tracking ─────────────────────────────────────────
# Each entry: { "encoding": np.array, "count": int, "last_seen": float,
#               "alerted": bool }
tracked_faces: list[dict] = []

def send_telegram(message: str, photo_path: str | None = None):
    """Send a text message and optionally a photo via Telegram Bot API."""
    base = f"https://api.telegram.org/bot{TELEGRAM_BOT_TOKEN}"
    try:
        # Send text
        r = requests.post(
            f"{base}/sendMessage",
            json={"chat_id": TELEGRAM_CHAT_ID, "text": message},
            timeout=10,
        )
        r.raise_for_status()

        # Send photo if available
        if photo_path and os.path.exists(photo_path):
            with open(photo_path, "rb") as f:
                requests.post(
                    f"{base}/sendPhoto",
                    data={"chat_id": TELEGRAM_CHAT_ID, "caption": "Last seen frame"},
                    files={"photo": f},
                    timeout=15,
                )
        log.info("Telegram alert sent.")
    except Exception as e:
        log.error(f"Telegram send failed: {e}")


def find_matching_face(enc: np.ndarray) -> int | None:
    """Return index of a matching tracked face, or None."""
    if not tracked_faces:
        return None
    known = [f["encoding"] for f in tracked_faces]
    distances = face_recognition.face_distance(known, enc)
    best_idx = int(np.argmin(distances))
    if distances[best_idx] <= FACE_MATCH_TOLERANCE:
        return best_idx
    return None


def expire_old_faces():
    """Remove faces not seen recently."""
    now = time.time()
    before = len(tracked_faces)
    tracked_faces[:] = [
        f for f in tracked_faces
        if now - f["last_seen"] < FORGET_AFTER_SECONDS
    ]
    removed = before - len(tracked_faces)
    if removed:
        log.info(f"Expired {removed} face(s) not seen for >{FORGET_AFTER_SECONDS}s")


# ── Flask route ────────────────────────────────────────────
@app.route("/detect", methods=["POST"])
def detect():
    if "image" not in request.files:
        return "No image", 400

    raw = request.files["image"].read()

    # Decode JPEG → RGB numpy array
    try:
        pil_img = Image.open(io.BytesIO(raw)).convert("RGB")
        frame = np.array(pil_img)
    except Exception as e:
        log.error(f"Image decode error: {e}")
        return "Bad image", 400

    log.info(f"Frame received: {frame.shape[1]}×{frame.shape[0]}")

    # ── Face detection (HOG model is fast, cnn is more accurate) ──
    locations  = face_recognition.face_locations(frame, model="hog")
    encodings  = face_recognition.face_encodings(frame, locations)

    log.info(f"Faces found: {len(locations)}")
    if not locations:
        expire_old_faces()
        return "OK", 200

    # Save frame in case we need to send it as an alert photo
    pil_img.save(ALERT_PHOTO_PATH)

    expire_old_faces()

    alert_triggered = False

    for enc in encodings:
        idx = find_matching_face(enc)
        now = time.time()

        if idx is None:
            # New face – start tracking
            tracked_faces.append({
                "encoding": enc,
                "count":    1,
                "last_seen": now,
                "alerted":  False,
            })
            log.info(f"New face tracked. Total tracked: {len(tracked_faces)}")
        else:
            # Known face – increment counter
            face = tracked_faces[idx]
            face["encoding"]  = enc   # refresh with latest encoding
            face["count"]    += 1
            face["last_seen"] = now
            log.info(f"Known face #{idx} seen {face['count']} time(s)")

            if face["count"] >= FOLLOW_THRESHOLD and not face["alerted"]:
                face["alerted"] = True
                alert_triggered = True
                msg = (
                    f"⚠️  SAFETY ALERT ⚠️\n"
                    f"Someone has been following you!\n"
                    f"Same face detected {face['count']} times in the last "
                    f"{int(face['count'] * 8)} seconds.\n"
                    f"Stay aware of your surroundings."
                )
                log.warning("ALERT: Following detected!")
                send_telegram(msg, ALERT_PHOTO_PATH)

    return "ALERT" if alert_triggered else "OK", 200


@app.route("/status", methods=["GET"])
def status():
    """Quick health-check endpoint."""
    return jsonify({
        "tracked_faces": len(tracked_faces),
        "faces": [
            {"count": f["count"], "alerted": f["alerted"]}
            for f in tracked_faces
        ]
    })


# ──────────────────────────────────────────────────────────
if __name__ == "__main__":
    log.info("Follower-detection server starting on 0.0.0.0:5000")
    log.info(f"Alert threshold : {FOLLOW_THRESHOLD} appearances")
    log.info(f"Forget after    : {FORGET_AFTER_SECONDS}s")
    app.run(host="0.0.0.0", port=5000, debug=False)
