import cv2
import time
import os
import threading
from datetime import datetime
from feat import Detector
import torch
import pandas as pd

# Import your helper functions
# from db_helper import connect_db, disconnect_db, insert_rows_into_table, create_table
import db_helper as db 


# Konfiguration
DB_PATH = "action_units.db" # TODO
CAMERA_INDEX = 0
OUTPUT_DIR = "recordings"
VIDEO_DURATION = 50  # Sekunden
START_INTERVAL = 5   # Sekunden bis zum nächsten Start
FPS = 25.0           # Feste FPS

if not os.path.exists(OUTPUT_DIR):
    os.makedirs(OUTPUT_DIR)

# Globaler Detector, um ihn nicht bei jedem Video neu laden zu müssen (spart massiv Zeit/Speicher)
print("Initialisiere AU-Detector (bitte warten)...")
detector = Detector(au_model="xgb") 

def extract_aus(path, skip_frames):

    # torch.no_grad() deaktiviert die Gradientenberechnung.
    # Das löst den "Can't call numpy() on Tensor that requires grad" Fehler.
    with torch.no_grad():
        video_prediction = detector.detect_video(
            path, 
            skip_frames=skip_frames, 
            face_detection_threshold=0.95
        )
        
        # Falls video_prediction oder .aus noch Tensoren sind, 
        # stellen wir sicher, dass sie korrekt summiert werden.
        try:
            # Wir nehmen die Summe der Action Units über alle detektierten Frames
            res = video_prediction.aus.sum()
            return res
        except Exception as e:
            print(f"Fehler bei der Summenbildung: {e}")
            return None

def startAU_creation(video_path, db_path):
    """Diese Funktion läuft nun in einem eigenen Thread."""
    try:
        print(f"\n[THREAD START] Analyse läuft für: {video_path}")
        # skip_frames berechnen (z.B. alle 5 Sekunden bei 25 FPS = 125)
        output = extract_aus(video_path, skip_frames=int(FPS*5))
        
        print(f"\n--- Ergebnis für {os.path.basename(video_path)} ---")
        print(output)
        print("--------------------------------------------------\n")
        if output is not None:
            # Verbindung für diesen Thread öffnen (SQLite Sicherheit)
            conn, cursor = db.connect_db(db_path)
            
            # Daten vorbereiten: Timestamp + AU Ergebnisse
            # Wir wandeln die Series/Dataframe in ein Dictionary um
            data_to_insert = output.to_dict()
            data_to_insert['timestamp'] = [datetime.now().strftime("%Y-%m-%d %H:%M:%S")]
            
            # Da die AU-Spaltennamen dynamisch sind, stellen wir sicher, dass sie Listen sind
            # (insert_rows_into_table erwartet Listen für jeden Key)
            final_payload = {k: [v] if not isinstance(v, list) else v for k, v in data_to_insert.items()}


            db.insert_rows_into_table(conn, cursor, "actionUnits", final_payload)
            
            db.disconnect_db(conn, cursor)
            print(f"--- Ergebnis für {os.path.basename(video_path)} in DB gespeichert ---")
    except Exception as e:
        print(f"Fehler bei der Analyse von {video_path}: {e}")

class VideoRecorder:
    def __init__(self, filename, width, height, db_path):
        self.filename = filename
        self.db_path = db_path
        fourcc = cv2.VideoWriter_fourcc(*'XVID')
        self.out = cv2.VideoWriter(filename, fourcc, FPS, (width, height))
        self.frames_to_record = int(VIDEO_DURATION * FPS)
        self.frames_count = 0
        self.is_finished = False

    def write_frame(self, frame):
        if self.frames_count < self.frames_to_record:
            self.out.write(frame)
            self.frames_count += 1
        else:
            self.finish()

    def finish(self):
        if not self.is_finished:
            self.out.release()
            self.is_finished = True
            abs_path = os.path.abspath(self.filename)
            print(f"Video fertig gespeichert: {self.filename}")

            # --- MULTITHREADING HIER ---
            # Wir starten die Analyse in einem neuen Thread, damit main() sofort weiter frames lesen kann
            analysis_thread = threading.Thread(target=startAU_creation, args=(abs_path, self.db_path))
            analysis_thread.daemon = True  # Beendet sich, wenn das Hauptprogramm schließt
            analysis_thread.start()

def main():
    cap = cv2.VideoCapture(CAMERA_INDEX)
    if not cap.isOpened():
        print("Fehler: Kamera konnte nicht geöffnet werden.")
        return

    width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
    height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
    
    active_recorders = []
    last_start_time = 0
    
    print("Aufnahme läuft. Drücke 'q' zum Beenden.")

    try:
        while True:
            ret, frame = cap.read()
            if not ret:
                break

            current_time = time.time()

            if current_time - last_start_time >= START_INTERVAL:
                timestamp = datetime.now().strftime("%H%M%S")
                filename = os.path.join(OUTPUT_DIR, f"rec_{timestamp}.avi")
                new_recorder = VideoRecorder(filename, width, height, DB_PATH)
                active_recorders.append(new_recorder)
                last_start_time = current_time

            for rec in active_recorders[:]:
                rec.write_frame(frame)
                if rec.is_finished:
                    active_recorders.remove(rec)

            cv2.imshow('Kamera Livestream', frame)
            if cv2.waitKey(1) & 0xFF == ord('q'):
                break

            time.sleep(1/FPS)

    finally:
        cap.release()
        cv2.destroyAllWindows()
        print("Programm beendet. Warte ggf. auf laufende Analysen...")

if __name__ == "__main__":
    main()