import json
import os
import time
from datetime import datetime
from platform import system
from queue import Queue

from pynput import keyboard, mouse
from pynput.keyboard import KeyCode
from PyQt6.QtCore import QThread, pyqtSignal

from .metadata import MetadataManager
from .obs_client import OBSClient
from .util import fix_windows_dpi_scaling, get_recordings_dir


class Recorder(QThread):
    """
    Makes recordings.
    """
    
    recording_stopped = pyqtSignal()

    def __init__(self, natural_scrolling: bool):
        super().__init__()
        
        if system() == "Windows":
            fix_windows_dpi_scaling()
            
        self.recording_path = self._get_recording_path()
        
        self._is_recording = False
        self._is_paused = False
        
        self.event_queue = Queue()
        self.events_file = open(os.path.join(self.recording_path, "events.jsonl"), "a")
        
        self.metadata_manager = MetadataManager(
            recording_path=self.recording_path, 
            natural_scrolling=natural_scrolling
        )
        self.obs_client = OBSClient(recording_path=self.recording_path, 
                                    metadata=self.metadata_manager.metadata)

        self.mouse_listener = mouse.Listener(
            on_move=self.on_move,
            on_click=self.on_click,
            on_scroll=self.on_scroll)
        
        self.keyboard_listener = keyboard.Listener(
            on_press=self.on_press, 
            on_release=self.on_release)
        
    def on_move(self, x, y):
        if not self._is_paused:
            self.event_queue.put({"time_stamp": time.perf_counter(), 
                                  "action": "move", 
                                  "x": x, 
                                  "y": y}, block=False)
        
    def on_click(self, x, y, button, pressed):
        if not self._is_paused:
            self.event_queue.put({"time_stamp": time.perf_counter(), 
                                  "action": "click", 
                                  "x": x, 
                                  "y": y, 
                                  "button": button.name, 
                                  "pressed": pressed}, block=False)
    
    def on_scroll(self, x, y, dx, dy):
        if not self._is_paused:
            self.event_queue.put({"time_stamp": time.perf_counter(), 
                                  "action": "scroll", 
                                  "x": x, 
                                  "y": y, 
                                  "dx": dx, 
                                  "dy": dy}, block=False)
    
    def on_press(self, key):
        if not self._is_paused:
            self.event_queue.put({"time_stamp": time.perf_counter(), 
                                  "action": "press", 
                                  "name": key.char if type(key) == KeyCode else key.name}, block=False)

    def on_release(self, key):
        if not self._is_paused:
            self.event_queue.put({"time_stamp": time.perf_counter(), 
                                  "action": "release", 
                                  "name": key.char if type(key) == KeyCode else key.name}, block=False)

    def run(self):
        self._is_recording = True
        
        self.metadata_manager.collect()
        self.obs_client.start_recording()
        
        self.mouse_listener.start()
        self.keyboard_listener.start()
                
        while self._is_recording:
            event = self.event_queue.get()
            self.events_file.write(json.dumps(event) + "\n")

    def stop_recording(self):
        if self._is_recording:
            self._is_recording = False

            self.metadata_manager.end_collect()
                        
            self.mouse_listener.stop()
            self.keyboard_listener.stop()
            
            self.obs_client.stop_recording()
            self.metadata_manager.add_obs_record_state_timings(self.obs_client.record_state_events)
            self.events_file.close()
            self.metadata_manager.save_metadata()
            
            self.recording_stopped.emit()
    
    def pause_recording(self):
        if not self._is_paused and self._is_recording:
            self._is_paused = True
            self.obs_client.pause_recording()
            self.event_queue.put({"time_stamp": time.perf_counter(),
                                  "action": "pause"}, block=False)

    def resume_recording(self):
        if self._is_paused and self._is_recording:
            self._is_paused = False
            self.obs_client.resume_recording()
            self.event_queue.put({"time_stamp": time.perf_counter(),
                                  "action": "resume"}, block=False)

    def _get_recording_path(self) -> str:
        recordings_dir = get_recordings_dir()

        if not os.path.exists(recordings_dir):
            os.mkdir(recordings_dir)

        current_time = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
        
        recording_path = os.path.join(recordings_dir, f"recording-{current_time}")
        os.mkdir(recording_path)

        return recording_path