import sys
import types
import unittest
from pathlib import Path


RUNNER_DIR = Path(__file__).resolve().parent
if str(RUNNER_DIR) not in sys.path:
    sys.path.insert(0, str(RUNNER_DIR))


class DummySignal:
    def connect(self, *args, **kwargs):
        pass

    def emit(self, *args, **kwargs):
        pass


class DummyTableModel:
    def __init__(self, *args, **kwargs):
        pass

    def createIndex(self, *args, **kwargs):
        return None


class DummyObject:
    def __init__(self, *args, **kwargs):
        pass


class DummyWidget:
    def __init__(self, *args, **kwargs):
        pass


def install_pyqt_compat_stub():
    pyqt_compat = types.ModuleType("pyqt_compat")
    qtcore = types.ModuleType("pyqt_compat.QtCore")
    qtgui = types.ModuleType("pyqt_compat.QtGui")
    qtwidgets = types.ModuleType("pyqt_compat.QtWidgets")

    qtcore.QAbstractTableModel = DummyTableModel
    qtcore.QModelIndex = DummyWidget
    qtcore.QObject = DummyObject
    qtcore.QProcess = DummyWidget
    qtcore.QProcessEnvironment = DummyWidget
    qtcore.QSettings = DummyWidget
    qtcore.Qt = DummyWidget
    qtcore.pyqtSignal = lambda *args, **kwargs: DummySignal()
    qtcore.pyqtSlot = lambda *args, **kwargs: lambda func: func

    qtgui.QBrush = DummyWidget
    qtgui.QColor = DummyWidget

    qtwidgets.QDialog = DummyWidget
    qtwidgets.QDialogButtonBox = DummyWidget
    qtwidgets.QFileDialog = DummyWidget
    qtwidgets.QFormLayout = DummyWidget
    qtwidgets.QGridLayout = DummyWidget
    qtwidgets.QGroupBox = DummyWidget
    qtwidgets.QLabel = DummyWidget
    qtwidgets.QMessageBox = DummyWidget
    qtwidgets.QSpinBox = DummyWidget
    qtwidgets.QTabWidget = DummyWidget
    qtwidgets.QVBoxLayout = DummyWidget
    qtwidgets.QWidget = DummyWidget

    pyqt_compat.QtCore = qtcore
    pyqt_compat.QtGui = qtgui
    pyqt_compat.QtWidgets = qtwidgets
    pyqt_compat.QT_ABSTRACT_ITEM_VIEW_SELECT_ROWS = 0
    pyqt_compat.QT_BUTTON_BOX_CANCEL = 0
    pyqt_compat.QT_BUTTON_BOX_OK = 0
    pyqt_compat.QT_CONTEXT_MENU_POLICY_CUSTOM = 0
    pyqt_compat.QT_HORIZONTAL = 1
    pyqt_compat.QT_ITEM_DATA_DISPLAY_ROLE = 0
    pyqt_compat.QT_ITEM_DATA_FOREGROUND_ROLE = 1
    pyqt_compat.QT_PROCESS_CRASH_EXIT = 1
    pyqt_compat.QT_VERTICAL = 2

    sys.modules["pyqt_compat"] = pyqt_compat
    sys.modules["pyqt_compat.QtCore"] = qtcore
    sys.modules["pyqt_compat.QtGui"] = qtgui
    sys.modules["pyqt_compat.QtWidgets"] = qtwidgets


install_pyqt_compat_stub()

import bit_functions
import queue_item
import run_queue
import settings


class FakeSettings:
    def __init__(self, values):
        self.values = values

    def value(self, key, default=None):
        return self.values.get(key, default)


class FakeRunItem:
    def __init__(self):
        self.status = queue_item.RunStatus.Waiting
        self.run_calls = []
        self.able_to_run_calls = []

    def resources(self):
        return set(), set()

    def able_to_run(self, gpus_avail, n_total_gpus):
        self.able_to_run_calls.append((set(gpus_avail), n_total_gpus))
        return True

    def run_simulation(self, gpus_avail, n_gpus_total):
        self.run_calls.append((set(gpus_avail), n_gpus_total))


class TestSettingsInt(unittest.TestCase):
    def test_settings_int_accepts_string_numbers(self):
        fake_settings = FakeSettings({"Resources/GPU Count": "3"})

        self.assertEqual(settings.settings_int(fake_settings, "Resources/GPU Count", 1), 3)

    def test_settings_int_defaults_for_missing_blank_and_invalid_values(self):
        fake_settings = FakeSettings({
            "blank": "",
            "invalid": "abc",
            "none": None,
        })

        self.assertEqual(settings.settings_int(fake_settings, "missing", 1), 1)
        self.assertEqual(settings.settings_int(fake_settings, "blank", 1), 1)
        self.assertEqual(settings.settings_int(fake_settings, "invalid", 1), 1)
        self.assertEqual(settings.settings_int(fake_settings, "none", 1), 1)


class TestBitFunctions(unittest.TestCase):
    def test_bit_functions_accept_string_bitmasks(self):
        self.assertTrue(bit_functions.test_bit("3", 0))
        self.assertTrue(bit_functions.test_bit("3", 1))
        self.assertEqual(bit_functions.toggle_bit("0", 2), 4)
        self.assertEqual(bit_functions.clear_bit("3", 0), 2)

    def test_bit_functions_default_invalid_bitmasks_to_zero(self):
        self.assertEqual(bit_functions.test_bit("", 0), 0)
        self.assertEqual(bit_functions.set_bit("", 1), 2)


class TestRunReadyModels(unittest.TestCase):
    def test_run_ready_models_accepts_string_gpu_count(self):
        model = run_queue.RunQueueModel()
        item = FakeRunItem()
        model.run_queue.append(item)

        model.run_ready_models("2")

        self.assertEqual(item.able_to_run_calls, [({0, 1}, 2)])
        self.assertEqual(item.run_calls, [({0, 1}, 2)])

    def test_run_ready_models_defaults_invalid_gpu_count_to_one(self):
        model = run_queue.RunQueueModel()
        item = FakeRunItem()
        model.run_queue.append(item)

        model.run_ready_models("")

        self.assertEqual(item.able_to_run_calls, [({0}, 1)])
        self.assertEqual(item.run_calls, [({0}, 1)])


if __name__ == "__main__":
    unittest.main()
