import io
import os
import tempfile
from unittest.mock import patch

from AnyQt.QtGui import QWhatsThisClickedEvent
from AnyQt.QtWidgets import (
    QToolButton, QDialog, QMessageBox, QApplication, QFileDialog
)

from .. import addons
from ..outputview import TextStream
from ..utils.addons import _QueryResult, Installable
from ...scheme import SchemeTextAnnotation, SchemeLink
from ...gui.quickhelp import QuickHelpTipEvent, QuickHelp
from ...utils.shtools import temp_named_file
from ...utils.pickle import swp_name
from ...gui.test import QAppTestCase
from ..canvasmain import CanvasMainWindow
from ..widgettoolbox import WidgetToolBox
from ...registry import tests as registry_tests


class MainWindow(CanvasMainWindow):
    _instances = []

    def create_new_window(self):  # type: () -> CanvasMainWindow
        inst = super().create_new_window()
        MainWindow._instances.append(inst)
        return inst


class TestMainWindowBase(QAppTestCase):
    def setUp(self):
        super().setUp()
        self.w = MainWindow()
        self.registry = registry_tests.small_testing_registry()
        self.w.set_widget_registry(self.registry)

    def tearDown(self):
        self.w.clear_swp()
        self.w.deleteLater()
        for w in MainWindow._instances:
            w.deleteLater()
        MainWindow._instances.clear()
        del self.w
        del self.registry
        self.qWait(1)
        super().tearDown()


class TestMainWindow(TestMainWindowBase):
    def test_create_new_window(self):
        w = self.w
        new = w.create_new_window()
        self.assertIsInstance(new, MainWindow)
        r1 = new.widget_registry
        self.assertEqual(r1.widgets(), self.registry.widgets())

        w.show()
        new.show()

        w.set_scheme_margins_enabled(True)
        new.deleteLater()
        stream = TextStream()
        w.connect_output_stream(stream)

    def test_connect_output_stream(self):
        w = self.w
        stream = TextStream()
        w.connect_output_stream(stream)
        stream.write("Hello")
        self.assertEqual(w.output_view().toPlainText(), "Hello")
        w.disconnect_output_stream(stream)
        stream.write("Bye")
        self.assertEqual(w.output_view().toPlainText(), "Hello")

    def test_create_new_window_streams(self):
        w = self.w
        stream = TextStream()
        w.connect_output_stream(stream)
        new = w.create_new_window()
        stream.write("Hello")
        self.assertEqual(w.output_view().toPlainText(), "Hello")
        self.assertEqual(new.output_view().toPlainText(), "Hello")

    def test_new_window(self):
        w = self.w
        with patch(
            "orangecanvas.application.schemeinfo.SchemeInfoDialog.exec",
        ):
            w.new_workflow_window()

    def test_examples_dialog(self):
        w = self.w
        with patch(
            "orangecanvas.preview.previewdialog.PreviewDialog.exec",
            return_value=QDialog.Rejected,
        ):
            w.examples_dialog()

    def test_create_toolbox(self):
        w = self.w
        toolbox = w.findChild(WidgetToolBox)
        assert isinstance(toolbox, WidgetToolBox)
        wf = w.current_document().scheme()
        grid = toolbox.widget(0)

        button = grid.findChild(QToolButton)  # type: QToolButton
        self.assertEqual(len(wf.nodes), 0)
        button.click()
        self.assertEqual(len(wf.nodes), 1)

    def test_create_category_toolbar(self):
        w = self.w
        dock = w.dock_widget
        dock.setExpanded(False)
        a = w.quick_category.actions()[0]
        with patch(
            "orangecanvas.application.canvastooldock.CategoryPopupMenu.exec",
            return_value=None,
        ):
            w.on_quick_category_action(a)

    def test_recent_list(self):
        w = self.w
        w.clear_recent_schemes()
        w.add_recent_scheme("This one", __file__)
        new = w.create_new_window()
        self.assertEqual(len(new.recent_schemes), 1)
        w.clear_recent_schemes()

    def test_quick_help_events(self):
        w = self.w
        help: QuickHelp = w.dock_help
        html = "<h3>HELLO</h3>"
        ev = QuickHelpTipEvent("", html, priority=QuickHelpTipEvent.Normal)
        QApplication.sendEvent(w, ev)
        self.assertEqual(help.currentText(), "<h3>HELLO</h3>")

    def test_help_requests(self):
        w = self.w
        ev = QWhatsThisClickedEvent('help://search?id=one')
        QApplication.sendEvent(w, ev)


class TestMainWindowLoad(TestMainWindowBase):
    filename = ""

    def setUp(self):
        super().setUp()
        fd, filename = tempfile.mkstemp()
        self.file = os.fdopen(fd, "w+b")
        self.filename = filename

    def tearDown(self):
        self.file.close()
        os.remove(self.filename)
        super().tearDown()

    def test_open_example_scheme(self):
        self.file.write(TEST_OWS)
        self.file.flush()
        self.w.open_example_scheme(self.filename)

    def test_open_scheme_file(self):
        self.file.write(TEST_OWS)
        self.file.flush()
        self.w.open_scheme_file(self.filename)

    def test_save(self):
        w = self.w
        w.current_document().setPath(self.filename)
        with patch.object(w, "save_scheme_as") as f:
            w.save_scheme()
            f.assert_not_called()

        w.current_document().setPath("")

        def exec(myself):
            myself.setOption(QFileDialog.DontUseNativeDialog)
            myself.setOption(QFileDialog.DontConfirmOverwrite)
            myself.selectFile(self.filename)
            myself.accept()

        with patch("AnyQt.QtWidgets.QFileDialog.exec", exec):
            w.save_scheme()
            self.assertTrue(os.path.samefile(w.current_document().path(), self.filename))

    def test_save_svg_image(self):
        w = self.w
        scheme = w.current_document().scheme()
        scheme.load_from(io.BytesIO(TEST_OWS), registry=w.widget_registry)
        with patch("AnyQt.QtWidgets.QFileDialog.exec"):
            w.save_as_svg()
            dialog = w.findChild(QFileDialog, "save-as-svg-filedialog")
            dialog.setOption(QFileDialog.DontUseNativeDialog)
            dialog.setOption(QFileDialog.DontConfirmOverwrite)
            dialog.selectFile(self.filename)
            dialog.accept()
        with open(self.filename, "rb") as f:
            contents = f.read()
        self.assertIn(b"<svg", contents)

    def test_save_swp(self):
        w = self.w
        swpname = swp_name(w)

        with patch.object(w, "save_swp_to") as f:
            w.save_swp()
            f.assert_not_called()

        desc = self.registry.widgets()[0]
        w.current_document().createNewNode(desc)

        w = self.w
        with patch.object(w, "save_swp_to") as f:
            w.save_swp()
            f.assert_called_with(swpname)

        w.clear_swp()

    def test_load_swp(self):
        w = self.w
        swpname = swp_name(w)

        w2 = MainWindow()
        w2.set_widget_registry(self.registry)

        with patch.object(w2, "load_swp_from") as f:
            w2.load_swp()
            f.assert_not_called()

        desc = self.registry.widgets()[0]
        w.current_document().createNewNode(desc)

        from orangecanvas.utils.pickle import canvas_scratch_name_memo as memo
        memo.clear()

        with patch.object(w2, "load_swp_from") as f:
            w2.load_swp()
            f.assert_called_with(swpname)

        w2.clear_swp()
        del w2

    def test_dont_load_swp_on_new_window(self):
        w = self.w
        desc = self.registry.widgets()[0]
        w.current_document().createNewNode(desc)

        with patch.object(CanvasMainWindow, 'ask_load_swp', self.fail):
            w.new_workflow_window()

    def test_swp_functionality(self):
        w = self.w
        w2 = MainWindow()
        w2.set_widget_registry(self.registry)

        def test(predicate):
            _, tf = tempfile.mkstemp()
            w.save_swp_to(tf)
            w2.load_swp_from(tf)
            predicate()
            w.scheme_widget.setModified(False)

        # test widget add
        desc = self.registry.widget('zero')
        node = w.current_document().createNewNode(desc)
        node.properties['dummy'] = 0

        test(lambda:
             self.assertEqual(w2.scheme_widget.scheme().nodes[0].properties['dummy'], 0))
        w2_node = w2.scheme_widget.scheme().nodes[0]

        # test widget change properties
        node.properties['dummy'] = 1
        test(lambda:
             self.assertEqual(w2_node.properties['dummy'], 1))

        desc = self.registry.widget('add')
        node2 = w.current_document().createNewNode(desc)
        link = SchemeLink(node, node.output_channels()[0], node2, node2.input_channels()[0])
        # test link add
        w.current_document().addLink(link)
        test(lambda:
             self.assertTrue(w2.scheme_widget.scheme().links))

        # test link remove
        w.current_document().removeLink(link)
        test(lambda:
             self.assertFalse(w2.scheme_widget.scheme().links))

        # test widget remove
        w.scheme_widget.removeNode(node)
        w.scheme_widget.removeNode(node2)
        test(lambda:
             self.assertFalse(w2.scheme_widget.scheme().nodes))

        # test annotation add
        a = SchemeTextAnnotation((200, 300, 50, 20), "text")
        w.current_document().addAnnotation(a)
        test(lambda:
             self.assertTrue(w2.scheme_widget.scheme().annotations))

        # test annotation remove
        w.current_document().removeAnnotation(a)
        test(lambda:
             self.assertFalse(w2.scheme_widget.scheme().annotations))

    def test_open_ows_req(self):
        w = self.w
        with temp_named_file(TEST_OWS_REQ.decode()) as f:
            with patch("AnyQt.QtWidgets.QMessageBox.exec",
                       return_value=QMessageBox.Ignore):
                w.load_scheme(f)
                self.assertEqual(w.current_document().path(), f)

            with patch("AnyQt.QtWidgets.QMessageBox.exec",
                       return_value=QMessageBox.Abort):
                w.load_scheme(f)
                self.assertEqual(w.current_document().path(), f)

    def test_install_requirements_dialog(self):
        def query(names):
            return [
                _QueryResult(name, Installable(name, "0.0", "", "", "", []))
                for name in names
            ]

        w = self.w
        with patch.object(addons, "query_pypi", query), \
             patch.object(addons.AddonManagerDialog, "exec",
                          return_value=QDialog.Rejected):
            w.install_requirements(["uber-package-shiny", "spasm"])

    def test_load_unsupported_format(self):
        w = self.w
        workflow = w.current_document().scheme()
        with temp_named_file('<scheme version="99.9"></scheme>') as fname, \
                patch.object(QMessageBox, "open", lambda self: None):
            w.load_scheme(fname)
            self.assertIs(w.current_document().scheme(), workflow)
            dlg = w.findChild(QMessageBox)
            self.assertIsNotNone(dlg)
            self.assertIn("99.9", dlg.detailedText())
            dlg.done(QMessageBox.Ok)


TEST_OWS = b"""\
<?xml version='1.0' encoding='utf-8'?>
<scheme description="" title="" version="2.0">
    <nodes>
        <node id="0" name="zero" position="(0, 0)" qualified_name="zero" />
        <node id="1" name="one" position="(0, 0)" qualified_name="one" />
        <node id="2" name="add" position="(0, 0)" qualified_name="add" />
        <node id="3" name="negate" position="(0, 0)" qualified_name="negate" />
    </nodes>
    <links>
        <link enabled="true" id="0" sink_channel="left"
              sink_node_id="2" source_channel="value" source_node_id="0" />
        <link enabled="true" id="1" sink_channel="right" sink_node_id="2"
              source_channel="value" source_node_id="1" />
        <link enabled="true" id="2" sink_channel="value" sink_node_id="3"
              source_channel="result" source_node_id="2" />
    </links>
    <annotations>
        <arrow end="(10, 10)" fill="red" id="0" start="(0, 0)" />
        <text id="1" rect="(0, 100, 200, 200)" type="text/plain">$$</text>
    </annotations>
</scheme>
"""

TEST_OWS_REQ = b"""\
<?xml version='1.0' encoding='utf-8'?>
<scheme description="" title="" version="2.0">
    <nodes>
        <node id="0" name="zero" position="(0, 0)" qualified_name="zero"
              project_name="foo" />
        <node id="1" name="one" position="(0, 0)" qualified_name="one"
              project_name="foo" />
        <node id="2" name="add" position="(0, 0)" qualified_name="add"
              project_name="foo" />
        <node id="3" name="negate" position="(0, 0)" qualified_name="negate"
              project_name="foo" />
    </nodes>
    <links>
        <link enabled="true" id="0" sink_channel="left"
              sink_node_id="2" source_channel="value" source_node_id="0" />
        <link enabled="true" id="1" sink_channel="right" sink_node_id="2"
              source_channel="value" source_node_id="1" />
        <link enabled="true" id="2" sink_channel="value" sink_node_id="3"
              source_channel="result" source_node_id="2" />
    </links>
    <annotations>
        <arrow end="(10, 10)" fill="red" id="0" start="(0, 0)" />
        <text id="1" rect="(0, 100, 200, 200)" type="text/plain">$$</text>
    </annotations>
</scheme>
"""
