import os
import sys
from pathlib import Path
from unittest.mock import Mock

import pytest
from PyQt6 import QtCore
from PyQt6.QtGui import QCloseEvent
from PyQt6.QtWidgets import QCheckBox, QFormLayout, QMessageBox

import vorta.store.models
from vorta.store.models import SettingsModel


def test_toggle_all_settings(qapp, qtbot):
    """Toggle each setting twice as a basic sanity test to ensure app does crash."""
    groups = (
        SettingsModel.select(SettingsModel.group)
        .distinct(True)
        .where(SettingsModel.group != '')
        .order_by(SettingsModel.group.asc())
    )

    settings = [
        setting
        for group in groups
        for setting in SettingsModel.select().where(
            SettingsModel.type == 'checkbox', SettingsModel.group == group.group
        )
    ]

    for setting in settings:
        for _ in range(2):
            _click_toggle_setting(setting.label, qapp, qtbot)


@pytest.mark.skipif(sys.platform != "linux", reason="testing autostart path for Linux only")
def test_autostart_linux(qapp, qtbot):
    """Checks that autostart path is added correctly on Linux when setting is enabled."""
    setting = "Automatically start Vorta at login"

    # ensure file is present when autostart is enabled
    _click_toggle_setting(setting, qapp, qtbot)
    autostart_path = (
        Path(os.environ.get("XDG_CONFIG_HOME", os.path.expanduser("~") + '/.config') + "/autostart") / "vorta.desktop"
    )
    qtbot.waitUntil(lambda: autostart_path.exists(), **pytest._wait_defaults)
    with open(autostart_path) as desktop_file:
        desktop_file_text = desktop_file.read()
    assert desktop_file_text.startswith("[Desktop Entry]")

    # ensure file is removed when autostart is disabled
    _click_toggle_setting(setting, qapp, qtbot)
    if sys.platform == 'linux':
        assert not os.path.exists(autostart_path)


def test_enable_background_question(qapp, monkeypatch, mocker):
    """Tests that 'enable background question' correctly prompts user."""
    main = qapp.main_window
    close_event = Mock(value=QCloseEvent())

    # disable system trey and enable setting to test
    monkeypatch.setattr("vorta.views.main_window.is_system_tray_available", lambda: False)
    mocker.patch.object(vorta.store.models.SettingsModel, "get", return_value=Mock(value=True))
    mocker.patch.object(QMessageBox, "exec")  # prevent QMessageBox from stopping test

    # Create a mock for QMessageBox and its setText method
    mock_msgbox = mocker.Mock(spec=QMessageBox)
    mocker.patch("vorta.views.main_window.QMessageBox", return_value=mock_msgbox)

    main.closeEvent(close_event)

    mock_msgbox.setText.assert_called_once_with("Should Vorta continue to run in the background?")
    close_event.accept.assert_called_once()


def test_enable_fixed_units(qapp, qtbot, mocker):
    """Tests the 'enable fixed units' setting to ensure the archive tab sizes are displayed correctly."""
    tab = qapp.main_window.archiveTab
    setting = "Use the same unit of measurement for archive sizes"

    # set mocks
    mock_setting = mocker.patch.object(vorta.views.archive_tab.SettingsModel, "get", return_value=Mock(value=True))
    mock_pretty_bytes = mocker.patch.object(vorta.views.archive_tab, "pretty_bytes")

    # with setting enabled, fixed units should be determined and passed to pretty_bytes as an 'int'
    tab.populate_from_profile()
    mock_pretty_bytes.assert_called()
    kwargs_list = mock_pretty_bytes.call_args_list[0].kwargs
    assert 'fixed_unit' in kwargs_list
    assert isinstance(kwargs_list['fixed_unit'], int)

    # disable setting and reset mock
    mock_setting.return_value = Mock(value=False)
    mock_pretty_bytes.reset_mock()

    # with setting disabled, pretty_bytes should be called with fixed units set to 'None'
    tab.populate_from_profile()
    mock_pretty_bytes.assert_called()
    kwargs_list = mock_pretty_bytes.call_args_list[0].kwargs
    assert 'fixed_unit' in kwargs_list
    assert kwargs_list['fixed_unit'] is None

    # use the qt bot to click the setting and see that the refresh_archive emit works as intended.
    with qtbot.waitSignal(qapp.main_window.miscTab.refresh_archive, **pytest._wait_defaults):
        _click_toggle_setting(setting, qapp, qtbot)


@pytest.mark.skipif(sys.platform != 'darwin', reason="Full Disk Access check only on Darwin")
def test_check_full_disk_access(qapp, mocker):
    """Tests if the full disk access warning is properly silenced with the setting enabled"""

    # Set mocks for setting enabled
    mocker.patch.object(vorta.store.models.SettingsModel, "get", return_value=Mock(value=True))
    mocker.patch('pathlib.Path.exists', return_value=True)
    mocker.patch('os.access', return_value=False)
    mock_qmessagebox = mocker.patch('vorta.application.QMessageBox')

    # See that pop-up occurs
    qapp.check_darwin_permissions()
    mock_qmessagebox.assert_called()

    # Reset mocks for setting disabled
    mock_qmessagebox.reset_mock()
    mocker.patch.object(vorta.store.models.SettingsModel, "get", return_value=Mock(value=False))

    # See that pop-up does not occur
    qapp.check_darwin_permissions()
    mock_qmessagebox.assert_not_called()


def _click_toggle_setting(setting, qapp, qtbot):
    """Toggle setting checkbox in the misc tab"""
    miscTab = qapp.main_window.miscTab

    for x in range(miscTab.checkboxLayout.count()):
        item = miscTab.checkboxLayout.itemAt(x, QFormLayout.ItemRole.FieldRole)
        if item is not None:
            checkbox = item.itemAt(0).widget()
            if checkbox.text() == setting and isinstance(checkbox, QCheckBox):
                # Have to use pos to click checkbox correctly
                # https://stackoverflow.com/questions/19418125/pysides-qtest-not-checking-box/24070484#24070484
                pos = QtCore.QPoint(2, int(checkbox.height() / 2))
                qtbot.mouseClick(checkbox, QtCore.Qt.MouseButton.LeftButton, pos=pos)
                break
