import cv2
import logging
import time
from threading import Thread
import requests
import os
import sys

if getattr(sys, 'frozen', False):
    MEDIA_DIR = os.path.join(sys._MEIPASS, "media") 
else: 
    MEDIA_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../media")


class Notifier:

    def __init__(self, telegram_token: str, get_frame: callable, camera_id: str) -> None:
        self._telegram_token = telegram_token
        self._camera_id = camera_id
        self._chat_ids = []
        self._images = []
        self._fps = 10
        self._last_detection = 0
        self._MAX_IMAGES = 3000 # 10 fps * 5 min * 60 sec
        self._get_frame = get_frame
        self._thread = Thread(target=self._start)
        self._finish = False
        self._thread.start()

        
    def add_chat(self, chat_id: str):
        if chat_id not in self._chat_ids:
            self._chat_ids.append(chat_id)


    def remove_chat(self, chat_id: str):
        if chat_id in self._chat_ids:
            self._chat_ids.remove(chat_id)


    def _start(self):
        while not self._finish:
            start = time.time()
            self._update_image()
            elapsed = time.time() - start
            time.sleep(max(0, 1/self._fps - elapsed))


    def _update_image(self):
        frame = self._get_frame()
        if frame is not None:
            self._images.append(frame)
            
            if time.time() - self._last_detection > 60:
                thread = Thread(target=self._detect_movement)
                thread.start()
            
            if len(self._images) > self._MAX_IMAGES:
                self._images.pop(0)


    def _detect_movement(self) -> bool:
        frames = self._images[-5:]
        if len(frames) < 5:
            return False

        gray_frames = [cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) for frame in frames] # Convert frames to grayscale
        diffs = [cv2.absdiff(gray_frames[i], gray_frames[i+1]) for i in range(len(gray_frames)-1)] # Calculate the absolute difference between consecutive frames
        thresh = [cv2.threshold(diff, 30, 255, cv2.THRESH_BINARY)[1] for diff in diffs] # Threshold the differences to get binary images
        contours = [cv2.findContours(thresh[i], cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[0] for i in range(len(thresh))] # Find contours in the thresholded images
        detected = any(len(cnt) > 0 for cnt in contours) # Check if any contours are found
        if detected:
            self._last_detection = time.time()
            time.sleep(10)
            images = self._images[self._fps*-20:] # Get the previous and next 10 seconds of detection
            logging.info(f"Movement detected at camera {self._camera_id}! Generating video of {len(images)} frames")
            # Generate video from images
            height, width, _ = images[0].shape
            fourcc = cv2.VideoWriter_fourcc(*'avc1')
            if not os.path.isdir(MEDIA_DIR):
                os.makedirs(MEDIA_DIR)
            file_path = os.path.join(MEDIA_DIR, f"movement_{int(time.time())}.mp4")
            out = cv2.VideoWriter(file_path, fourcc, self._fps, (width, height))
            for image in images:
                out.write(image)
            out.release()

            video_file = open(file_path, 'rb')
    
            for chat_id in self._chat_ids:
                # Send the image to the chat id
                logging.info(f"Movement detected at camera {self._camera_id}! Sending notification to chat id: {chat_id}")
                self._send_notification(chat_id, f"Movement detected at camera {self._camera_id}! Check the video.", video_file)

            video_file.close()
            os.remove(file_path)
        return detected
    
    def _send_notification(self, chat_id: str, message: str, video_file: bytearray):
        try:
            url = f"https://api.telegram.org/bot{self._telegram_token}/sendVideo"
            files = {"video": video_file}
            payload = {"chat_id": chat_id, "caption": message}
            response = requests.post(url, data=payload, files=files)
            response.raise_for_status()

            logging.info(f"Notification sent to chat id: {chat_id}")
        except Exception as e:
            logging.error(f"Failed to send notification to chat id: {chat_id}, error: {e}")

    def stop(self):
        self._finish = True
        self._thread.join()
        logging.info("Notifier stopped")

        
