"""
novelWriter – Preferences Dialog Class Tester
=============================================

This file is a part of novelWriter
Copyright (C) 2020 Veronica Berglyd Olsen and novelWriter contributors

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
from __future__ import annotations

import pytest

from PyQt6.QtCore import QEvent, Qt
from PyQt6.QtGui import QAction, QFont, QFontDatabase, QKeyEvent
from PyQt6.QtWidgets import QFileDialog, QFontDialog

from novelwriter import CONFIG, SHARED
from novelwriter.config import DEF_GUI_DARK, DEF_GUI_LIGHT, DEF_TREECOL
from novelwriter.constants import nwUnicode
from novelwriter.dialogs.preferences import GuiPreferences
from novelwriter.dialogs.quotes import GuiQuoteSelect
from novelwriter.gui.theme import ThemeEntry
from novelwriter.types import QtDialogCancel, QtDialogSave, QtModNone

KEY_DELAY = 1


@pytest.mark.gui
def testDlgPreferences_Main(qtbot, monkeypatch, nwGUI, tstPaths):
    """Test the preferences dialog loading."""
    monkeypatch.setattr(SHARED._spelling, "listDictionaries", lambda: [("en", "English [en]")])
    monkeypatch.setattr(GuiPreferences, "exec", lambda *a: None)

    # Load GUI with standard values
    nwGUI.mainMenu.aPreferences.activate(QAction.ActionEvent.Trigger)
    qtbot.waitUntil(lambda: SHARED.findTopLevelWidget(GuiPreferences) is not None, timeout=1000)
    prefs = SHARED.findTopLevelWidget(GuiPreferences)
    assert isinstance(prefs, GuiPreferences)
    prefs.show()

    # Check Languages
    languages = [prefs.guiLocale.itemData(i) for i in range(prefs.guiLocale.count())]
    assert len(languages) > 0
    assert "en_GB" in languages

    # Check GUI Themes
    themes = [prefs.lightTheme.itemData(i) for i in range(prefs.lightTheme.count())]
    assert DEF_GUI_LIGHT in themes

    themes = [prefs.darkTheme.itemData(i) for i in range(prefs.darkTheme.count())]
    assert DEF_GUI_DARK in themes

    # Check Spell Checking
    spelling = [prefs.spellLanguage.itemData(i) for i in range(prefs.spellLanguage.count())]
    assert len(spelling) == 1
    assert spelling == ["en"]

    prefs.close()

    # Check Fallback Values
    CONFIG.hasEnchant = False
    prefs = GuiPreferences(nwGUI)
    prefs.show()

    # Check Spell Checking
    spelling = [prefs.spellLanguage.itemData(i) for i in range(prefs.spellLanguage.count())]
    assert len(spelling) == 1
    assert spelling == [""]

    prefs.close()

    # qtbot.stop()


@pytest.mark.gui
def testDlgPreferences_Actions(qtbot, monkeypatch, nwGUI):
    """Test the preferences dialog actions."""
    monkeypatch.setattr(SHARED._spelling, "listDictionaries", lambda: [("en", "English [en]")])
    prefs = GuiPreferences(nwGUI)
    with qtbot.waitExposed(prefs):
        prefs.show()

    # Check Navigation
    vBar = prefs.mainForm.verticalScrollBar()
    assert vBar is not None
    old = -1
    with qtbot.waitSignal(vBar.valueChanged) as value:
        prefs.sidebar.button(1).click()
        assert value.args[0] > old
        old = value.args[0]
    with qtbot.waitSignal(vBar.valueChanged) as value:
        prefs.sidebar.button(2).click()
        assert value.args[0] > old
        old = value.args[0]
    with qtbot.waitSignal(vBar.valueChanged) as value:
        prefs.sidebar.button(3).click()
        assert value.args[0] > old
        old = value.args[0]

    # Check Search
    prefs.searchText.setText("Display language")
    with qtbot.waitSignal(vBar.valueChanged) as value:
        prefs._gotoSearch()
        assert value.args[0] < old

    # Check Save Button
    prefs.show()
    with qtbot.waitSignal(prefs.newPreferencesReady) as signal:
        button = prefs.buttonBox.button(QtDialogSave)
        assert button is not None
        button.click()
        assert len(signal.args) == 4

    # Check Close Button
    prefs.show()
    button = prefs.buttonBox.button(QtDialogCancel)
    assert button is not None
    button.click()
    assert prefs.isHidden() is True

    # Close Using Escape Key
    prefs.show()
    event = QKeyEvent(QEvent.Type.KeyPress, Qt.Key.Key_Escape, QtModNone)
    prefs.keyPressEvent(event)
    assert prefs.isHidden() is True

    # qtbot.stop()


@pytest.mark.gui
def testDlgPreferences_Settings(qtbot, monkeypatch, nwGUI, fncPath, tstPaths):
    """Test the preferences dialog settings."""
    spelling = [("en", "English [en]"), ("de", "Deutch [de]")]
    monkeypatch.setattr(SHARED._spelling, "listDictionaries", lambda: spelling)

    (fncPath / "nw_en_US.qm").touch()
    (fncPath / "project_en_US.json").touch()
    CONFIG._nwLangPath = fncPath
    SHARED.theme._allThemes = {
        "theme1": ThemeEntry(name="Theme 1", dark=False, path=fncPath),
        "theme2": ThemeEntry(name="Theme 2", dark=False, path=fncPath),
        "theme3": ThemeEntry(name="Theme 3", dark=True, path=fncPath),
        "theme4": ThemeEntry(name="Theme 4", dark=True, path=fncPath),
    }

    prefs = GuiPreferences(nwGUI)
    with qtbot.waitExposed(prefs):
        prefs.show()

    # Appearance
    prefs.guiLocale.setCurrentIndex(prefs.guiLocale.findData("en_US"))
    prefs.lightTheme.setCurrentIndex(prefs.lightTheme.findData("theme1"))
    prefs.darkTheme.setCurrentIndex(prefs.darkTheme.findData("theme3"))
    with monkeypatch.context() as mp:
        mp.setattr(QFontDialog, "getFont", lambda *a, **k: (QFont(), True))
        prefs.nativeFont.setChecked(True)  # Use OS font dialog
        prefs.guiFontButton.click()

    prefs.hideVScroll.setChecked(True)
    prefs.hideHScroll.setChecked(True)
    prefs.useCharCount.setChecked(True)

    assert CONFIG.guiLocale != "en_US"
    assert CONFIG.lightTheme == "default_light"
    assert CONFIG.darkTheme == "default_dark"
    assert CONFIG.guiFont.family() != ""
    assert CONFIG.hideVScroll is False
    assert CONFIG.hideHScroll is False
    assert CONFIG.useCharCount is False

    # Document Style
    with monkeypatch.context() as mp:
        mp.setattr(QFontDialog, "getFont", lambda *a, **k: (QFont(), True))
        prefs.nativeFont.setChecked(False)  # Use Qt font dialog
        prefs.textFontButton.click()

    prefs.showFullPath.setChecked(False)
    prefs.incNotesWCount.setChecked(False)

    assert CONFIG.textFont.family() != ""
    assert CONFIG.showFullPath is True
    assert CONFIG.incNotesWCount is True

    # Project View
    prefs.iconColTree.setCurrentData("faded", "default")
    prefs.iconColDocs.setChecked(True)
    prefs.emphLabels.setChecked(False)

    assert CONFIG.iconColTree == DEF_TREECOL
    assert CONFIG.iconColDocs is False
    assert CONFIG.emphLabels is True

    # Behaviour
    prefs.autoSaveDoc.stepUp()
    prefs.autoSaveProj.stepUp()
    prefs.askBeforeExit.setChecked(False)

    assert CONFIG.autoSaveDoc == 30
    assert CONFIG.autoSaveProj == 60
    assert CONFIG.askBeforeExit is True

    # Project Backup
    with monkeypatch.context() as mp:
        mp.setattr(QFileDialog, "getExistingDirectory", lambda *a, **k: str(tstPaths.testDir))
        prefs.backupGetPath.click()
        assert prefs.backupPath == str(tstPaths.testDir)
    prefs.backupOnClose.setChecked(True)
    assert prefs.askBeforeBackup.isEnabled() is True
    prefs.askBeforeBackup.setChecked(False)

    assert CONFIG._backupPath != tstPaths.testDir
    assert CONFIG.backupOnClose is False
    assert CONFIG.askBeforeBackup is True

    # Session Timer
    prefs.stopWhenIdle.setChecked(False)
    prefs.userIdleTime.stepUp()

    assert CONFIG.stopWhenIdle is True
    assert CONFIG.userIdleTime == 300

    # Text Flow
    prefs.textWidth.stepDown()
    prefs.focusWidth.stepDown()
    prefs.hideFocusFooter.setChecked(True)
    prefs.doJustify.setChecked(True)
    prefs.textMargin.stepUp()
    prefs.tabWidth.stepUp()

    assert CONFIG.textWidth == 700
    assert CONFIG.focusWidth == 800
    assert CONFIG.hideFocusFooter is False
    assert CONFIG.doJustify is False
    assert CONFIG.textMargin == 40
    assert CONFIG.tabWidth == 40

    # Text Editing
    prefs.spellLanguage.setCurrentIndex(prefs.spellLanguage.findData("de"))
    prefs.autoSelect.setChecked(False)
    prefs.cursorWidth.setValue(5)
    prefs.lineHighlight.setChecked(False)
    prefs.showTabsNSpaces.setChecked(True)
    prefs.showLineEndings.setChecked(True)

    assert CONFIG.spellLanguage != "de"
    assert CONFIG.autoSelect is True
    assert CONFIG.cursorWidth == 1
    assert CONFIG.lineHighlight is True
    assert CONFIG.showTabsNSpaces is False
    assert CONFIG.showLineEndings is False

    # Editor Scrolling
    prefs.scrollPastEnd.setChecked(False)
    prefs.autoScroll.setChecked(False)
    prefs.autoScrollPos.stepUp()

    assert CONFIG.scrollPastEnd is True
    assert CONFIG.autoScroll is True
    assert CONFIG.autoScrollPos == 30

    # Text Highlighting
    prefs.dialogStyle.setCurrentData(3, 0)
    prefs.allowOpenDial.setChecked(False)
    prefs.dialogLine.setText(nwUnicode.U_EMDASH)
    prefs.narratorBreak.setCurrentData(nwUnicode.U_EMDASH, "")
    prefs.narratorDialog.setCurrentData(nwUnicode.U_EMDASH, "")
    prefs.altDialogOpen.setText("%")  # Symbol also tests for #2455
    prefs.altDialogClose.setText("%")  # Symbol also tests for #2455
    prefs.highlightEmph.setChecked(False)
    prefs.showMultiSpaces.setChecked(False)

    prefs._insertDialogLineSymbol(nwUnicode.U_ENDASH)
    assert prefs.dialogLine.text() == f"{nwUnicode.U_ENDASH} {nwUnicode.U_EMDASH}"

    assert CONFIG.dialogStyle == 2
    assert CONFIG.allowOpenDial is True
    assert CONFIG.dialogLine == ""
    assert CONFIG.narratorBreak == ""
    assert CONFIG.narratorDialog == ""
    assert CONFIG.altDialogOpen == ""
    assert CONFIG.altDialogClose == ""
    assert CONFIG.highlightEmph is True
    assert CONFIG.showMultiSpaces is True

    # Text Automation
    prefs.doReplaceSQuote.setChecked(False)
    prefs.doReplaceDQuote.setChecked(False)
    prefs.doReplaceDash.setChecked(False)
    prefs.doReplaceDots.setChecked(False)
    prefs.doReplace.setChecked(False)
    prefs.fmtPadBefore.setText("!?:")
    prefs.fmtPadAfter.setText("¡¿")
    prefs.fmtPadThin.setChecked(True)

    assert prefs.doReplaceSQuote.isEnabled() is False
    assert prefs.doReplaceDQuote.isEnabled() is False
    assert prefs.doReplaceDash.isEnabled() is False
    assert prefs.doReplaceDots.isEnabled() is False
    assert prefs.fmtPadThin.isEnabled() is False

    assert CONFIG.doReplace is True
    assert CONFIG.doReplaceSQuote is True
    assert CONFIG.doReplaceDQuote is True
    assert CONFIG.doReplaceDash is True
    assert CONFIG.doReplaceDots is True
    assert CONFIG.fmtPadBefore == ""
    assert CONFIG.fmtPadAfter == ""
    assert CONFIG.fmtPadThin is False

    # Quotation Style
    with monkeypatch.context() as mp:
        mp.setattr(GuiQuoteSelect, "getQuote", lambda *a, **k: (nwUnicode.U_LSAQUO, True))
        prefs.btnSQuoteOpen.click()
    with monkeypatch.context() as mp:
        mp.setattr(GuiQuoteSelect, "getQuote", lambda *a, **k: (nwUnicode.U_RSAQUO, True))
        prefs.btnSQuoteClose.click()
    with monkeypatch.context() as mp:
        mp.setattr(GuiQuoteSelect, "getQuote", lambda *a, **k: (nwUnicode.U_LAQUO, True))
        prefs.btnDQuoteOpen.click()
    with monkeypatch.context() as mp:
        mp.setattr(GuiQuoteSelect, "getQuote", lambda *a, **k: (nwUnicode.U_RAQUO, True))
        prefs.btnDQuoteClose.click()

    assert CONFIG.fmtSQuoteOpen == nwUnicode.U_LSQUO
    assert CONFIG.fmtSQuoteClose == nwUnicode.U_RSQUO
    assert CONFIG.fmtDQuoteOpen == nwUnicode.U_LDQUO
    assert CONFIG.fmtDQuoteClose == nwUnicode.U_RDQUO

    # Save Settings
    with monkeypatch.context() as mp:
        mp.setattr(QFontDatabase, "families", lambda *a: ["TestFont"])
        with qtbot.waitSignal(prefs.newPreferencesReady) as signal:
            button = prefs.buttonBox.button(QtDialogSave)
            assert button is not None
            button.click()
            assert signal.args == [True, True, True, True]

    # Check Settings
    # ==============

    # Appearance
    assert CONFIG.guiLocale == "en_US"
    assert CONFIG.lightTheme == "theme1"
    assert CONFIG.darkTheme == "theme3"
    assert CONFIG.guiFont == QFont()
    assert CONFIG.hideVScroll is True
    assert CONFIG.hideHScroll is True
    assert CONFIG.useCharCount is True

    # Document Style
    assert CONFIG.textFont == QFont()
    assert CONFIG.showFullPath is False
    assert CONFIG.incNotesWCount is False

    # Project View
    assert CONFIG.iconColTree == "faded"
    assert CONFIG.iconColDocs is True
    assert CONFIG.emphLabels is False

    # Behaviour
    assert CONFIG.autoSaveDoc == 31
    assert CONFIG.autoSaveProj == 61
    assert CONFIG.askBeforeExit is False

    # Project Backup
    assert CONFIG._backupPath == tstPaths.testDir
    assert CONFIG.backupOnClose is True
    assert CONFIG.askBeforeBackup is False

    # Session Timer
    assert CONFIG.stopWhenIdle is False
    assert CONFIG.userIdleTime == 330

    # Text Flow
    assert CONFIG.textWidth == 690
    assert CONFIG.focusWidth == 790
    assert CONFIG.hideFocusFooter is True
    assert CONFIG.doJustify is True
    assert CONFIG.textMargin == 41
    assert CONFIG.tabWidth == 41

    # Text Editing
    assert CONFIG.spellLanguage == "de"
    assert CONFIG.autoSelect is False
    assert CONFIG.cursorWidth == 5
    assert CONFIG.lineHighlight is False
    assert CONFIG.showTabsNSpaces is True
    assert CONFIG.showLineEndings is True

    # Editor Scrolling
    assert CONFIG.scrollPastEnd is False
    assert CONFIG.autoScroll is False
    assert CONFIG.autoScrollPos == 31

    # Text Highlighting
    assert CONFIG.dialogStyle == 3
    assert CONFIG.allowOpenDial is False
    assert CONFIG.dialogLine == f"{nwUnicode.U_ENDASH}{nwUnicode.U_EMDASH}"
    assert CONFIG.narratorBreak == nwUnicode.U_EMDASH
    assert CONFIG.narratorDialog == nwUnicode.U_EMDASH
    assert CONFIG.altDialogOpen == "%"
    assert CONFIG.altDialogClose == "%"
    assert CONFIG.highlightEmph is False
    assert CONFIG.showMultiSpaces is False

    # Text Automation
    assert CONFIG.doReplace is False
    assert CONFIG.doReplaceSQuote is False
    assert CONFIG.doReplaceDQuote is False
    assert CONFIG.doReplaceDash is False
    assert CONFIG.doReplaceDots is False
    assert CONFIG.fmtPadBefore == "!:?"
    assert CONFIG.fmtPadAfter == "¡¿"
    assert CONFIG.fmtPadThin is True

    # Quotation Style
    assert CONFIG.fmtSQuoteOpen == nwUnicode.U_LSAQUO
    assert CONFIG.fmtSQuoteClose == nwUnicode.U_RSAQUO
    assert CONFIG.fmtDQuoteOpen == nwUnicode.U_LAQUO
    assert CONFIG.fmtDQuoteClose == nwUnicode.U_RAQUO

    # qtbot.stop()
