from __future__ import annotations
import sys
from PySide6.QtCore import Qt, QObject, QEvent
from PySide6.QtNetwork import QLocalServer, QLocalSocket
from PySide6.QtGui import QAction
from PySide6.QtWidgets import QApplication, QLabel, QPushButton, QMenu, QAbstractItemView
from mi100.paths import ensure_dirs
from mi100.db import connect, init_db
from mi100.ui.main_window import MainWindow
from mi100.ui.license_dialog import TrialStartDialog
from mi100.licensing.license_manager import LicenseManager
from mi100.styles import QSS

APP_TITLE = "Mi100 Studio Manager"


class _SingleInstanceGuard(QObject):
    def __init__(self, server_name: str, parent=None):
        super().__init__(parent)
        self.server_name = server_name
        self.server = None
        self.on_activate = None

    def start_or_activate_existing(self) -> bool:
        socket = QLocalSocket(self)
        socket.connectToServer(self.server_name)
        if socket.waitForConnected(150):
            try:
                socket.write(b'ACTIVATE')
                socket.flush()
                socket.waitForBytesWritten(150)
            except Exception:
                pass
            socket.disconnectFromServer()
            return False
        try:
            QLocalServer.removeServer(self.server_name)
        except Exception:
            pass
        self.server = QLocalServer(self)
        self.server.newConnection.connect(self._handle_new_connection)
        self.server.listen(self.server_name)
        return True

    def _handle_new_connection(self):
        if not self.server:
            return
        sock = self.server.nextPendingConnection()
        if not sock:
            return
        try:
            sock.waitForReadyRead(100)
        except Exception:
            pass
        if callable(self.on_activate):
            self.on_activate()
        try:
            sock.disconnectFromServer()
        except Exception:
            pass


class _CopySupportFilter(QObject):
    def eventFilter(self, obj, event):
        try:
            if isinstance(obj, QLabel) and event.type() == QEvent.Type.Polish:
                obj.setTextInteractionFlags(
                    obj.textInteractionFlags()
                    | Qt.TextSelectableByMouse
                    | Qt.TextSelectableByKeyboard
                )
            if isinstance(obj, QPushButton) and event.type() == QEvent.Type.Polish:
                if not obj.property("copy_menu_connected"):
                    obj.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
                    obj.customContextMenuRequested.connect(
                        lambda pos, b=obj: self._show_button_menu(b, pos)
                    )
                    obj.setProperty("copy_menu_connected", True)
            if isinstance(obj, QAbstractItemView) and event.type() == QEvent.Type.Polish:
                if not obj.property("copy_menu_connected"):
                    obj.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectItems)
                    obj.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection)
                    obj.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
                    obj.customContextMenuRequested.connect(
                        lambda pos, v=obj: self._show_view_menu(v, pos)
                    )
                    obj.setProperty("copy_menu_connected", True)
            if isinstance(obj, QAbstractItemView) and event.type() == QEvent.Type.KeyPress:
                if event.matches(event.StandardKey.Copy):
                    self._copy_view_selection(obj)
                    return True
        except Exception:
            pass
        return super().eventFilter(obj, event)

    def _show_button_menu(self, button: QPushButton, pos):
        text = button.text().strip()
        if not text:
            return
        menu = QMenu(button)
        act = QAction("Копіювати текст", menu)
        act.triggered.connect(lambda: QApplication.clipboard().setText(text))
        menu.addAction(act)
        menu.exec(button.mapToGlobal(pos))

    def _show_view_menu(self, view: QAbstractItemView, pos):
        clicked_index = view.indexAt(pos) if hasattr(view, "indexAt") else None
        selected = view.selectionModel().selectedIndexes() if view.selectionModel() else []
        has_multi = len(selected) > 1
        if clicked_index is not None and clicked_index.isValid():
            if not has_multi or clicked_index not in selected:
                view.setCurrentIndex(clicked_index)
                if view.selectionModel():
                    flags = (
                        view.selectionModel().SelectionFlag.ClearAndSelect
                        | view.selectionModel().SelectionFlag.Current
                    )
                    view.selectionModel().select(clicked_index, flags)
        menu = QMenu(view)
        act = QAction("Копіювати", menu)
        act.triggered.connect(lambda: self._copy_view_selection(view))
        menu.addAction(act)
        menu.exec(view.viewport().mapToGlobal(pos))

    def _copy_view_selection(self, view: QAbstractItemView):
        model = view.model()
        if model is None:
            return
        indexes = view.selectionModel().selectedIndexes() if view.selectionModel() else []
        if not indexes:
            current = view.currentIndex()
            indexes = [current] if current.isValid() else []
        if not indexes:
            return
        indexes = sorted(indexes, key=lambda i: (i.row(), i.column()))
        rows = {}
        for idx in indexes:
            rows.setdefault(idx.row(), {})[idx.column()] = str(idx.data() or "")
        lines = []
        for row in sorted(rows):
            cols = rows[row]
            ordered = [cols[c] for c in sorted(cols)]
            lines.append("\t".join(ordered))
        QApplication.clipboard().setText("\n".join(lines))


def run_app(argv: list[str]) -> int:
    paths = ensure_dirs()
    db_path = paths["db"] / "app.sqlite"

    conn = connect(db_path)
    init_db(conn)

    app = QApplication(argv)
    app.setApplicationName(APP_TITLE)
    guard = _SingleInstanceGuard('mi100_studio_manager_single_instance', app)
    if not guard.start_or_activate_existing():
        return 0
    app.setStyleSheet(QSS)
    copy_filter = _CopySupportFilter(app)
    app.installEventFilter(copy_filter)

    license_manager = LicenseManager(paths["license"])
    status = license_manager.current_status()
    if status.mode != 'full':
        start_dlg = TrialStartDialog(license_manager, status)
        if not start_dlg.exec() or not start_dlg.allow_continue:
            return 0

    win = MainWindow(conn=conn, paths=paths, license_manager=license_manager)
    def _activate_existing_window():
        try:
            win.showMaximized()
            win.showNormal() if win.isMinimized() else None
            win.showMaximized()
            win.raise_()
            win.activateWindow()
        except Exception:
            pass
    guard.on_activate = _activate_existing_window
    win.showMaximized()
    return app.exec()
