import logging
import sys
import time
from threading import Thread

from pynput import keyboard, mouse
from PySide6.QtCore import QObject, QRect, QTimer, Signal, Slot
from PySide6.QtGui import QClipboard, QGuiApplication

logger = logging.getLogger(__name__)


class SelectionMonitor(QObject):
    triggered = Signal(int, int)
    requestTrigger = Signal(int, int)
    cancelTrigger = Signal()

    def __init__(self, bridge, translation_service, app):
        super().__init__()
        self.bridge = bridge
        self.ts = translation_service
        self.app = app
        
        self.last_press_time = 0
        self.press_count = 0
        self.listener = None
        self.mouse_listener = None
        self.mouse_controller = mouse.Controller()
        self.keyboard_controller = keyboard.Controller()
        
        self._is_simulating = False
        self._key_is_down = False
        self._key_down_time = 0
        self._pending_pos = (0, 0)

        self.debounce_timer = QTimer()
        self.debounce_timer.setSingleShot(True)
        self.debounce_timer.setInterval(350)
        self.debounce_timer.timeout.connect(self._on_debounce_timeout)

        self.requestTrigger.connect(self._handle_trigger_request)
        self.cancelTrigger.connect(self.debounce_timer.stop)
        self.triggered.connect(self._trigger_on_main_thread)

    def start(self):
        try:
            self.listener = keyboard.Listener(
                on_press=self.on_press, on_release=self.on_release
            )
            self.listener.daemon = True
            self.listener.start()
            
            self.mouse_listener = mouse.Listener(on_click=self.on_click)
            self.mouse_listener.daemon = True
            self.mouse_listener.start()
            logger.info("Selection monitor started.")
        except Exception as e:
            logger.warning(
                f"Error starting pynput listeners: {e}. Selection translation might not work."
            )

    def stop(self):
        if self.listener:
            self.listener.stop()
        if self.mouse_listener:
            self.mouse_listener.stop()
        logger.info("Selection monitor stopped.")

    def on_press(self, key):
        if self._is_simulating:
            return
            
        trigger_key_type = self.bridge.triggerKey
        is_target = self._is_trigger_key(key, trigger_key_type)

        if is_target:
            if not self._key_is_down:
                self._key_is_down, self._key_down_time = True, time.time()
        else:
            self.press_count = 0

    def on_release(self, key):
        if self._is_simulating:
            return
            
        trigger_key_type = self.bridge.triggerKey
        is_target = self._is_trigger_key(key, trigger_key_type)

        if is_target:
            self._key_is_down = False
            now = time.time()
            if now - getattr(self, "_key_down_time", 0) < 0.3:
                if now - self.last_press_time < 0.4:
                    self.press_count += 1
                    if self.press_count == 2:
                        pos = self.mouse_controller.position
                        self.requestTrigger.emit(int(pos[0]), int(pos[1]))
                        self.press_count = 0
                else:
                    self.press_count = 1
                self.last_press_time = now
            else:
                self.press_count = 0

    def _is_trigger_key(self, key, trigger_key_type):
        try:
            if trigger_key_type == "Ctrl":
                return key in (keyboard.Key.ctrl, keyboard.Key.ctrl_r, keyboard.Key.ctrl_l)
            else:
                return key in (keyboard.Key.shift, keyboard.Key.shift_r, keyboard.Key.shift_l)
        except AttributeError:
            return False

    def on_click(self, x, y, button, pressed):
        if button == mouse.Button.left:
            if pressed:
                self.cancelTrigger.emit()
                if self.bridge.isShowingFromTray:
                    self.bridge.isShowingFromTray = False
            else:
                # Handle auto-hide when clicking outside
                self._handle_click_outside(x, y)

    def _handle_click_outside(self, x, y):
        if not hasattr(self.app, "main_window"):
            return
            
        win = self.app.main_window
        if not win.isVisible():
            return
            
        rect = QRect(win.x(), win.y(), win.width(), win.height())
        click_in_window = rect.contains(x, y)

        if not click_in_window:
            if self.bridge.isShowingFromTray:
                return
            if self.bridge.isPinned:
                # If pinned, maybe re-trigger translation or just keep it there
                # Original logic seems to re-request trigger position
                self.requestTrigger.emit(int(x), int(y))
            else:
                self.bridge.hideSignal.emit()

    @Slot(int, int)
    def _handle_trigger_request(self, x, y):
        self._pending_pos = (x, y)
        self.debounce_timer.start()

    @Slot()
    def _on_debounce_timeout(self):
        self.triggered.emit(self._pending_pos[0], self._pending_pos[1])

    @Slot(int, int)
    def _trigger_on_main_thread(self, mouse_x, mouse_y):
        x, y = mouse_x - 60, mouse_y + 20

        # Protect simulation keys
        self._is_simulating = True
        try:
            # 记录物理Ctrl键状态
            ctrl_was_down = self._key_is_down

            self.keyboard_controller.press(keyboard.Key.ctrl)
            self.keyboard_controller.press(keyboard.Key.insert)
            self.keyboard_controller.release(keyboard.Key.insert)

            # 只有物理Ctrl已释放时，才模拟release
            # 否则让物理按键的释放事件自然处理，避免状态不同步
            if not ctrl_was_down:
                self.keyboard_controller.release(keyboard.Key.ctrl)

            time.sleep(0.2)
        finally:
            QTimer.singleShot(100, lambda: setattr(self, "_is_simulating", False))

        text = self._get_clipboard_text().strip()

        if text:
            if self.bridge.last_text != text:
                self.bridge.last_text = text
                self.bridge.resetSignal.emit(x, y)
                request_id = self.bridge.incrementRequestId()
                Thread(
                    target=self.ts.translate, args=(text, request_id), daemon=True
                ).start()
            else:
                self.bridge.showWindowSignal.emit(x, y)

    def _get_clipboard_text(self):
        clipboard = QGuiApplication.clipboard()
        if sys.platform.startswith("linux"):
            return clipboard.text(QClipboard.Selection)
        return clipboard.text()
