import cv2
from datetime import datetime, timedelta
from utils.preprocessing import fix_singular_shape, norm_by_imagenet
from dotenv import load_dotenv
import numpy as np
import requests
import json
import time
import os

from cameras import restaurants
from db import models
from db.database import SessionLocal
from routers.websocket import manager

load_dotenv()
host = os.getenv('MODEL_HOST')
port = os.getenv('MODEL_PORT')
model = os.getenv('MODEL_NAME')


def make_prediction(instances):
    url = f"http://{host}:{port}/v1/models/{model}:predict"
    data = json.dumps({"signature_name": "serving_default", "instances": instances.tolist()})
    headers = {"content-type": "application/json"}
    json_response = requests.post(url, data=data, headers=headers)
    try:
        predictions = json.loads(json_response.text)['predictions']
    except BaseException:
        print("prediction failed")
    return predictions


async def handle_cameras():

    db = SessionLocal()
    for restaurant in restaurants:
        for camera in restaurant["cameras"]:
            # each camera masked is constructed based on the given coordiantes and saved
            mask = np.zeros((720, 1280, 3), dtype=np.float32)
            cv2.fillPoly(mask, np.array(camera["mask_points"]), (255, 255, 255))
            camera["mask"] = mask

    while True:

        current_date = datetime.now()
        weekday, current_time = current_date.weekday(), current_date.time()

        for restaurant in restaurants:

            # we check whether or not the restaurant is opened
            is_open = db.query(
                models.OpeningHours).filter(
                models.OpeningHours.place == restaurant["restaurant"],
                models.OpeningHours.day == weekday,
                models.OpeningHours.open_time <= current_time,
                models.OpeningHours.close_time >= current_time).first() is not None

            if is_open:
                count_prediction = 0  # estimated number of people in the restaurant
                open_checkouts = 0  # estimated number of open checkouts in the restaurant
                cams_working = True  # boolean indicating whether or not all cams are working in the restaurant

                for camera in restaurant["cameras"]:
                    # connection to the rtsp stream
                    cap = cv2.VideoCapture(f'rtsp://{camera["user"]}:{camera["password"]}@{camera["IP"]}/{camera["stream"]}')
                    if cams_working and cap.isOpened():
                        # extraction and preoprocessing of the first frame
                        _, frame = cap.read()
                        masked_img = cv2.bitwise_and(
                            frame.astype(np.float32), camera["mask"])
                        treated_img = fix_singular_shape(masked_img, 16)
                        input_image = np.expand_dims(
                            np.squeeze(
                                norm_by_imagenet(
                                    np.array(
                                        [treated_img]))),
                            axis=0)
                        # getting the density map from the model and the number of people
                        pred_map = np.squeeze(make_prediction(input_image))
                        count_prediction += np.sum(pred_map)
                        for checkout in camera["checkouts"]:
                            # we check whether or not the density in the checkout area is high enough to determine if it is open or not
                            if np.sum(pred_map[checkout["x1"] // 2:checkout["x2"] // 2, checkout["y1"] // 2:checkout["y2"] // 2]) > 0.5:
                                open_checkouts += 1
                    else:
                        cams_working = False
                    cap.release()

                if cams_working and open_checkouts:
                    # the estimated waiting time is calculated and put in the database
                    waiting_time = timedelta(
                        seconds=restaurant['b_factor'] +
                        int(count_prediction *
                            restaurant['a_factor'] / open_checkouts))
                    db_record = models.Records(
                        place=restaurant['restaurant'],
                        date=current_date,
                        density=int(count_prediction),
                        waiting_time=waiting_time)
                    db.add(db_record)
                    db.commit()
                    await manager.broadcast(json.dumps({"type": "data"}))
        # sleeping enough time so that there is a 60 second delay between the start of two consecutive loops
        time.sleep(max(0, 60 - (datetime.now() - current_date).total_seconds()))
