from __future__ import annotations

from typing import Any
from collections.abc import Callable
import signal

from PySide6.QtWidgets import QApplication  # type: ignore
from PySide6.QtWidgets import QDialog
from PySide6.QtWidgets import QMainWindow
from PySide6.QtWidgets import QWidget
from PySide6.QtWidgets import QGraphicsScene
from PySide6.QtWidgets import QVBoxLayout
from PySide6.QtWidgets import QLabel
from PySide6.QtWidgets import QPushButton
from PySide6.QtWidgets import QHBoxLayout
from PySide6.QtWidgets import QScrollArea
from PySide6.QtWidgets import QComboBox
from PySide6.QtWidgets import QLayout
from PySide6.QtWidgets import QSizePolicy
from PySide6.QtWidgets import QMessageBox
from PySide6.QtWidgets import QLineEdit
from PySide6.QtGui import QShortcut  # type: ignore
from PySide6.QtGui import QKeySequence
from PySide6.QtGui import QFontDatabase
from PySide6.QtGui import QIcon
from PySide6.QtGui import QKeyEvent
from PySide6.QtGui import QMouseEvent
from PySide6.QtCore import Qt  # type: ignore
from PySide6.QtCore import QUrl
from PySide6.QtCore import Signal
from PySide6.QtMultimedia import QMediaPlayer  # type: ignore
from PySide6.QtMultimedia import QAudioOutput

from .config import Config
from .args import Args
from .utils import Utils


class SpecialButton(QPushButton):  # type: ignore
    middleClicked = Signal()

    def __init__(self, *args: Any, **kwargs: Any) -> None:
        super().__init__(*args, **kwargs)

    def mousePressEvent(self, e: QMouseEvent) -> None:
        if e.button() == Qt.MiddleButton:
            self.middleClicked.emit()
        else:
            super().mousePressEvent(e)


class SpecialComboBox(QComboBox):  # type: ignore
    middleClicked = Signal()

    def __init__(self, *args: Any, **kwargs: Any) -> None:
        super().__init__(*args, **kwargs)

    def mousePressEvent(self, e: QMouseEvent) -> None:
        if e.button() == Qt.MiddleButton:
            self.middleClicked.emit()
        else:
            super().mousePressEvent(e)


class FilterLineEdit(QLineEdit):  # type: ignore
    def keyPressEvent(self, e: QKeyEvent) -> None:
        if e.key() == Qt.Key_Escape:
            self.clear()
        else:
            super().keyPressEvent(e)


class RestartDialog(QDialog):  # type: ignore
    def __init__(self, sizes: list[str], defindex: int) -> None:
        super().__init__()
        self.setWindowTitle("Select Option")
        self.setFixedSize(300, 150)

        self.layout = QVBoxLayout()

        self.label = QLabel("Size of the population")
        self.layout.addWidget(self.label)

        self.size_combo = QComboBox()
        self.size_combo.addItems(sizes)
        self.size_combo.setCurrentIndex(defindex)
        self.layout.addWidget(self.size_combo)

        self.button_layout = QHBoxLayout()

        self.cancel_button = QPushButton("Cancel")
        self.cancel_button.clicked.connect(self.reject)
        self.button_layout.addWidget(self.cancel_button)

        self.ok_button = QPushButton("OK")
        self.ok_button.clicked.connect(self.accept)
        self.ok_button.setDefault(True)
        self.button_layout.addWidget(self.ok_button)

        self.layout.addLayout(self.button_layout)
        self.setLayout(self.layout)
        self.setWindowFlags(Qt.Popup)

    def get_data(self) -> dict[str, Any]:
        return {
            "size": str(self.size_combo.currentText()),
        }


class Window:
    app: QApplication
    window: QMainWindow
    root: QVBoxLayout
    view: QVBoxLayout
    view_scene: QGraphicsScene
    speed: QComboBox
    scroll_area: QScrollArea
    info: QPushButton
    font: str
    emoji_font: str
    mono_font: str
    player: QMediaPlayer
    audio: QAudioOutput
    filter: QLineEdit

    @staticmethod
    def prepare() -> None:
        Window.make()
        Window.add_buttons()
        Window.add_view()
        Window.add_footer()
        Window.setup_keyboard()

    @staticmethod
    def make() -> None:
        Window.app = QApplication([])
        program = Args.program or Config.program
        Window.app.setApplicationName(program)

        Window.window = QMainWindow()
        title = Args.title or Config.title
        Window.window.setWindowTitle(title)
        width = Args.width or Config.width
        height = Args.height or Config.height
        Window.window.resize(width, height)

        central_widget = QWidget()
        Window.root = QVBoxLayout()
        central_widget.setLayout(Window.root)
        Window.root.setAlignment(Qt.AlignTop)
        Window.window.setCentralWidget(central_widget)
        Window.window.setWindowIcon(QIcon(str(Config.icon_path)))
        Window.root.setContentsMargins(0, 0, 0, 0)
        Window.set_style()

    @staticmethod
    def set_style() -> None:
        font_id = QFontDatabase.addApplicationFont(str(Config.font_path))
        emoji_font_id = QFontDatabase.addApplicationFont(str(Config.emoji_font_path))
        mono_font_id = QFontDatabase.addApplicationFont(str(Config.mono_font_path))

        if font_id != -1:
            Window.font = QFontDatabase.applicationFontFamilies(font_id)[0]

        if emoji_font_id != -1:
            Window.emoji_font = QFontDatabase.applicationFontFamilies(emoji_font_id)[0]

        if mono_font_id != -1:
            Window.mono_font = QFontDatabase.applicationFontFamilies(mono_font_id)[0]

        style = f"""

        QWidget {{
            background-color: {Config.background_color};
            color: {Config.text_color};
            font-size: {Config.font_size}px;
        }}

        QMenu {{
            background-color: {Config.alt_background_color};
            color: {Config.alt_text_color};
            border: 2px solid {Config.alt_border_color};
        }}

        QMenu::item:selected {{
            background-color: {Config.alt_hover_background_color};
            color: {Config.alt_hover_text_color};
        }}

        QDialog {{
            background-color: {Config.alt_background_color};
            color: {Config.alt_text_color};
            border: 2px solid {Config.alt_border_color};
        }}

        QDialog QLabel {{
            background-color: {Config.alt_background_color};
            color: {Config.alt_text_color};
        }}

        QDialog QPushButton {{
            background-color: {Config.alt_background_color};
            color: {Config.alt_text_color};
        }}

        QDialog QPushButton:hover {{
            background-color: {Config.message_box_button_hover_background_color};
            color: {Config.message_box_button_hover_text_color};
        }}

        QComboBox {{
            selection-background-color: {Config.alt_hover_background_color};
            selection-color: {Config.alt_hover_text_color};
        }}

        QComboBox QAbstractItemView {{
            background-color: {Config.alt_background_color};
            color: {Config.alt_text_color};
            border: 2px solid {Config.alt_border_color};
            padding: 6px;
        }}

        QScrollBar:vertical {{
            border: 0px solid transparent;
            background: {Config.background_color};
            width: 15px;
            margin: 0px 0px 0px 0px;
        }}

        QScrollBar::handle:vertical {{
            background: {Config.scrollbar_handle_color};
            min-height: 20px;
        }}

        QScrollBar::add-line:vertical {{
            border: none;
            background: none;
        }}

        QScrollBar::sub-line:vertical {{
            border: none;
            background: none;
        }}

        QLineEdit {{
            background-color: {Config.input_background_color};
            color: {Config.input_text_color};
            border: 1px solid {Config.input_border_color};
        }}

        QFrame#horizontal_line {{
            background-color: white;
            color: white;
        }}

        QLabel#menu_label:hover {{
            background-color: {Config.alt_hover_background_color};
        }}

        """.strip()

        Window.app.setStyleSheet(style)

        if Args.mono:
            Window.app.setFont(Window.mono_font)
        else:
            Window.app.setFont(Window.font)

    @staticmethod
    def add_buttons() -> None:
        from .game import Game
        from .filter import Filter

        root = QWidget()
        container = QHBoxLayout()

        btn_menu = SpecialButton("Menu")
        btn_menu.setToolTip("The main menu\nMiddle Click: Update")
        btn_menu.clicked.connect(Game.menu)
        btn_menu.middleClicked.connect(Game.force_update)

        Window.speed = SpecialComboBox()
        tooltip = "The speed of the updates\n"

        fast = (Args.fast_seconds or Config.fast_seconds) / 60
        tooltip += f"Fast: {Utils.get_timeword(fast)}\n"

        normal = Args.normal_minutes or Config.normal_minutes
        tooltip += f"Normal: {Utils.get_timeword(normal)}\n"

        slow = Args.slow_minutes or Config.slow_minutes
        tooltip += f"Slow: {Utils.get_timeword(slow)}\n"

        tooltip += "Middle Click: Slow"
        Window.speed.setToolTip(tooltip)
        Window.speed.addItems(["Fast", "Normal", "Slow", "Paused"])
        Window.speed.setCurrentIndex(1)
        Window.speed.currentIndexChanged.connect(Game.update_speed)
        Window.speed.middleClicked.connect(Game.slowdown)

        Window.filter = FilterLineEdit()
        Window.filter.setPlaceholderText("Filter")
        Window.filter.mousePressEvent = lambda e: Window.to_top()
        Window.filter.keyReleaseEvent = lambda e: Filter.filter(e)

        container.addWidget(btn_menu, 1)
        container.addWidget(Window.speed, 1)
        container.addWidget(Window.filter, 1)

        root.setLayout(container)

        if not Args.header:
            root.setVisible(False)

        Window.root.addWidget(root)

    @staticmethod
    def add_view() -> None:
        Window.scroll_area = QScrollArea()
        Window.scroll_area.setWidgetResizable(True)

        container = QWidget()
        parent = QVBoxLayout(container)
        Window.view = QVBoxLayout()
        parent.addLayout(Window.view)

        Window.view.setAlignment(Qt.AlignTop)
        Window.scroll_area.setWidget(container)
        Window.root.addWidget(Window.scroll_area)

    @staticmethod
    def start() -> None:
        signal.signal(signal.SIGINT, signal.SIG_DFL)
        Window.window.show()
        Window.app.exec()

    @staticmethod
    def close() -> None:
        Window.app.quit()

    @staticmethod
    def delete_layout(layout: QLayout) -> None:
        while layout.count():
            item = layout.takeAt(0)
            if item.widget():
                item.widget().deleteLater()
            elif item.layout():
                Window.delete_layout(item.layout())

        layout.deleteLater()

    @staticmethod
    def expand(widget: QWidget) -> None:
        widget.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred)

    @staticmethod
    def expand_2(widget: QWidget) -> None:
        widget.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)

    @staticmethod
    def clear_view() -> None:
        while Window.view.count():
            item = Window.view.takeAt(0)
            if item.widget():
                item.widget().deleteLater()
            elif item.layout():
                Window.delete_layout(item.layout())

    @staticmethod
    def to_top() -> None:
        Window.scroll_area.verticalScrollBar().setValue(0)

    @staticmethod
    def to_bottom() -> None:
        Window.scroll_area.verticalScrollBar().setValue(
            Window.scroll_area.verticalScrollBar().maximum()
        )

    @staticmethod
    def toggle_scroll() -> None:
        maxim = Window.scroll_area.verticalScrollBar().maximum()

        if Window.scroll_area.verticalScrollBar().value() == maxim:
            Window.to_top()
        else:
            Window.to_bottom()

    @staticmethod
    def add_footer() -> None:
        from .game import Game

        root = QWidget()
        root.setContentsMargins(0, 0, 0, 0)
        container = QHBoxLayout()
        Window.info = SpecialButton("---")

        Window.info.setToolTip(
            "Click to scroll to the bottom or top\nMiddle Click: Filter Top"
        )

        Window.info.clicked.connect(Window.toggle_scroll)
        Window.info.middleClicked.connect(Game.filter_top)
        Window.info.setMinimumSize(35, 35)
        container.addWidget(Window.info)
        root.setLayout(container)

        if not Args.footer:
            root.setVisible(False)

        Window.root.addWidget(root)

    @staticmethod
    def play_audio(path: str, on_stop: Callable[..., Any] | None = None) -> None:
        Window.player = QMediaPlayer()
        Window.audio = QAudioOutput()
        Window.player.setAudioOutput(Window.audio)
        Window.player.setSource(QUrl.fromLocalFile(path))
        Window.audio.setVolume(100)

        def handle_state_change(state: QMediaPlayer.State) -> None:
            if state == QMediaPlayer.StoppedState:
                if on_stop:
                    on_stop()

        Window.player.playbackStateChanged.connect(handle_state_change)
        Window.player.play()

    @staticmethod
    def stop_audio() -> None:
        Window.player.stop()

    @staticmethod
    def alert(message: str) -> None:
        msg_box = QMessageBox()
        msg_box.setWindowFlags(Qt.Popup)
        msg_box.setIcon(QMessageBox.Information)
        msg_box.setText(message)
        msg_box.setWindowTitle("Information")
        msg_box.setStandardButtons(QMessageBox.Ok)
        msg_box.exec()

    @staticmethod
    def setup_keyboard() -> None:
        on_enter = QShortcut(QKeySequence(Qt.Key_Return), Window.window)
        on_enter.activated.connect(Window.toggle_scroll)
