import sys
import os
import logging
import argparse

from Qt import QtWidgets, QtCore, QtGui

import systray_icon
import log_view
import actions_tab
import apps_tab
import projects_tab
import utils
import qt_utils
import update_queue
import auth_session
import auth_updater
from workspace_tab import WorkspaceTab
from workspaces_list_tab import WorkspacesListTab
from bottom_panel import BottomPanel
from projects_registry import ProjectsAggregator
from wizart.desktop.freelance_software_updater import FreelanceSoftwareUpdater
from command_server import CommandServer
from db_manager import DBManager
log = logging.getLogger("wizart_desktop")


class DesktopWindow(QtWidgets.QMainWindow):
    __instance = None

    @staticmethod
    def instance():
        if DesktopWindow.__instance == None:
            DesktopWindow()
        return DesktopWindow.__instance

    def __init__(self, parent=None):
        super(DesktopWindow, self).__init__(parent)
        DesktopWindow.__instance = self
        self.setWindowTitle("Studio Desktop")
        self.layout().setContentsMargins(0, 0, 0, 0)
        geometry = utils.restore_geometry()
        if geometry != False:
            self.restoreGeometry(geometry)
        self._project = None

        proj_name = utils.get_project()
        self._project = ProjectsAggregator.get_project_by_name(proj_name)

        central_widget = QtWidgets.QWidget()
        central_widget.setObjectName("central-widget")
        central_widget.setLayout(QtWidgets.QVBoxLayout())
        central_widget.layout().setContentsMargins(0, 0, 0, 0)
        self.setCentralWidget(central_widget)

        top_layout = QtWidgets.QHBoxLayout()
        
        tab_bar_widget = QtWidgets.QWidget()
        tab_bar_widget.setObjectName("tab-bar")
        self.tab_bar = QtWidgets.QHBoxLayout()
        tab_bar_widget.setLayout(self.tab_bar)
        self.tab_view = QtWidgets.QStackedWidget()
        central_widget.layout().addWidget(tab_bar_widget)
        central_widget.layout().addWidget(self.tab_view)

        # actions_tab = QtWidgets.QWidget()
        # actions_tab.setLayout(QtWidgets.QVBoxLayout())

        # self.list_view = QtWidgets.QListWidget(actions_tab)
        # self.list_view.setFrameShape(QtWidgets.QFrame.NoFrame)
        # actions_tab.layout().addWidget(self.list_view)

        self.log_view = log_view.LogView(self)
        self.log_watcher = log_view.LogWatcher(self)
        self.log_view.log_msg_update.connect(self.update_status_msg)

        # apps tab is required
        self.test_widget = QtWidgets.QWidget()
        self.apps_tab_content = apps_tab.AppsTab(self)
        self.projects_tab_content = projects_tab.ProjectsTab(self)
        self.projects_tab_content.list_widget.itemDoubleClicked.connect(
            self.__set_project
        )
        self.projects_tab_content.list_widget.itemDoubleClicked.connect(
            self.update_credentials_slot
        )
        self.apps_tab_content.back_btn.released.connect(self.__go_back_to_projects)
        self.apps_tab = QtWidgets.QStackedWidget()
        self.apps_tab.addWidget(self.projects_tab_content)
        self.apps_tab.addWidget(self.apps_tab_content)
        self.apps_tab.setCurrentWidget(self.projects_tab_content)
        self.add_tab("Apps", self.apps_tab)
        self.status_bar = BottomPanel(self)
        if hasattr(sys, "freelance_build"):
            self.actions_tab = actions_tab.ActionsTab(self)
            self.actions_tab.sync_job_thread.finished.connect(self.status_bar.hide_progress_bar)
            self.add_tab("Actions", self.actions_tab)

        self.workspace_db = DBManager()
        self.worskpaces_tab = QtWidgets.QStackedWidget()
        self.workspaces_list_content = WorkspacesListTab(self, self.workspace_db)
        self.workspace_content = WorkspaceTab(self)
        self.worskpaces_tab.addWidget(self.workspaces_list_content)
        self.worskpaces_tab.addWidget(self.workspace_content)
        self.set_current_workspace()
        self.workspaces_list_content.show_workspace_signal.connect(self.__set_workspace)
        if not hasattr(sys, "freelance_build"):
            self.add_tab("Workspace", self.worskpaces_tab)
        self.workspace_content.back_btn.released.connect(self.__go_back_to_workspace_list)
        
        self.add_tab("Log", self.log_view)
        central_widget.layout().addWidget(self.status_bar)

        self.systray = systray_icon.DesktopSystemTrayIcon(self)
        self.systray.show()
        QtWidgets.QApplication.instance().aboutToQuit.connect(self.systray.hide)
        QtWidgets.QApplication.instance().aboutToQuit.connect(self.__save_geometry)
        # self.log_view.set_log_path(os.path.expandvars("$USERPROFILE/wizart_desktop.log") )
        self.update_window_title()

        if not hasattr(sys, "freelance_build"):
            self.update_queue_thread = update_queue.UpdateQueueThread()
            self.update_queue_thread.start()
            self.command_server = CommandServer(self)

        if self._project:
            log.info("Load Project '%s'" % self._project.label)
            self.apps_tab_content.set_project(self._project)
            self.apps_tab.setCurrentWidget(self.apps_tab_content)

        if hasattr(sys, "freelance_build"):
            self.updater = FreelanceSoftwareUpdater(self)
            self.updater.run()

            # Wait for some time application start
            timer = QtCore.QTimer(self)
            timer.setSingleShot(True)
            timer.timeout.connect(lambda: self.updater.check_updates())
            timer.start(100 * 30)

            freelancer_username = utils.get_ftp_user()
            if freelancer_username:
                from sentry_sdk import set_user
                set_user({"username": freelancer_username})

        self.systray_bufer_timer = QtCore.QTimer(self)
        self.systray_buffer_var = True
        self.systray_bufer_timer.timeout.connect(lambda: self.__set_systray_buffer_var(True) )
        if not hasattr(sys, "freelance_build"):
            self.auth_updater = auth_updater.AuthUpdater(self)
            self.auth_updater.run()

    def __save_geometry(self):
        utils.save_geometry(self.saveGeometry())

    def __set_systray_buffer_var(self, value):
        self.systray_buffer_var = value
    
    def rescan_projects_config(self):
        ProjectsAggregator.clear_cache()
        self.projects_tab_content.update_projects()
        project_name = utils.get_project()
        self._project = ProjectsAggregator.get_project_by_name(project_name)
        if self._project:
            self.apps_tab_content.set_project(self._project)
        self.apps_tab_content.update_apps()

    def get_project(self):
        return self._project

    def __set_project(self, item):
        self.apps_tab_content.set_project(item._obj)
        self.slide_view(self.apps_tab, self.apps_tab_content, "right")
        self._project = item._obj
        utils.set_project(item._obj)
        log.info("Set Project '%s'" % self._project.label)

    def __go_back_to_projects(self):
        self.slide_view(self.apps_tab, self.projects_tab_content, "left")
        self._project = None
        utils.clear_project()

    def set_current_workspace(self):
        id = self.get_workspace()

        workspace = None
        if id:
            workspace = self.workspace_db.get_workspace_by_id(id)

        if workspace:
            self.__set_workspace(id)
            self.worskpaces_tab.setCurrentWidget(self.workspace_content)
        else:
            self.worskpaces_tab.setCurrentWidget(self.workspaces_list_content)

    def __set_workspace(self, id):
        workspace = self.workspace_db.get_workspace_by_id(id)
        if not workspace:
            return
        name = workspace.title
        path = workspace.path

        if not os.path.isdir(path):
            log.info("Wrong path for workspace")
            return
        self.workspace_content.set_workspace(name, path)
        self.slide_view(self.worskpaces_tab, self.workspace_content, "right")
        log.info("Set Workspace")
        settings = QtCore.QSettings("Wizart Animation", "Desktop")
        settings.setValue("workspace/id", id)

    def __go_back_to_workspace_list(self):
        self.workspaces_list_content.update_list()
        self.slide_view(self.worskpaces_tab, self.workspaces_list_content, "left")
        log.info("Unset Workspace")
        settings = QtCore.QSettings("Wizart Animation", "Desktop")
        settings.setValue("workspace/id", '')

    def __unset_workspace(self):
        self.__go_back_to_workspace_list()

    def get_workspace(self):
        settings = QtCore.QSettings("Wizart Animation", "Desktop")
        value = None
        if settings.value("workspace/id"):
            try:
                value = int(settings.value("workspace/id"))
            except ValueError:
                log.info("Wrong type value saved in settings for workspace id")

        return value

    def get_workspace_path(self):
        id = self.get_workspace()
        if id:
            workspace = self.workspace_db.get_workspace_by_id(id)
            if workspace:
                return workspace[2]

    def update_status_msg(self, msg, progress):
        max_msg_length = 512
        trunc_msg = (msg[:max_msg_length] + "...") if len(msg) > max_msg_length else msg
        self.status_bar.set_status(trunc_msg, progress)
        is_subprocess_msg = False
        if progress != -1:
            is_subprocess_msg = True

        if "progress" in msg.lower() or 'PID:' in msg:
            is_subprocess_msg = True

        show_error = True
        if is_subprocess_msg:
            show_error = False
        if "Wizart_desktop_update" in msg:
            show_error = False

        if self.systray_buffer_var and utils.get_show_desktop_notifications():
            is_systray_msg = False
            if show_error and "error" in msg.lower():
                self.systray.showMessage(
                    "Error", trunc_msg, QtWidgets.QSystemTrayIcon.Critical
                )
                is_systray_msg = True
            elif "SYNC_FINISHED" in msg:
                self.systray.showMessage("Sync Finished", trunc_msg)
                is_systray_msg = True
            elif show_error and "warning" in msg.lower():
                self.systray.showMessage(
                    "Warning", trunc_msg, QtWidgets.QSystemTrayIcon.Warning
                )
                is_systray_msg = True
            if is_systray_msg:
                self.systray_buffer_var = False
                self.systray_bufer_timer.start(2000)

    def closeEvent(self, event):
        self.hide()
        event.accept()

    def update_credentials_slot(self):
        self.update_window_title()
        self.apps_tab_content.update_apps()

    def update_window_title(self):
        self.setWindowTitle("Studio Desktop")
        user = None
        if not hasattr(sys, "freelance_build"):
            user = utils.get_user()
        else:
            user = utils.get_ftp_user()
        if user:
            self.setWindowTitle("Studio Desktop (%s)" % user)

    def handle_cmd_args(self, args=None):
        """Main entry into the app
        
        Keyword Arguments:
            args {list} -- [sys arguments] (default: {None})
        
        Returns:
            [bool] -- if True we raise wizart desktop window
        """
        args = args[1:]
        parser = argparse.ArgumentParser(description="Wizart Desktop Tool")
        parser.add_argument("--launch_app", type=str, required=False)
        parser.add_argument("--update_auth", action="store_true", required=False)

        parser.add_argument('--repath', action="store_true", required=False)
        parser.add_argument('--pin', action="store_true", required=False)
        parser.add_argument('--remove_repath', action="store_true", required=False)
        parser.add_argument('--remove_pin', action="store_true", required=False)

        parser.add_argument('--create_workspace', action="store_true", required=False)
        parser.add_argument('--remove_workspace', action="store_true", required=False)
        parser.add_argument('--set_workspace', action="store_true", required=False)
        parser.add_argument('--unset_workspace', action="store_true", required=False)
        cmd_args = None
        unknown_args = None
        try:
            cmd_args, unknown_args = parser.parse_known_args(args)
        except:
            log.error("Failed to parse cmd args", exc_info=True)
        if cmd_args is not None:
            if cmd_args.launch_app:
                self.launch_app(cmd_args.launch_app, unknown_args)
                return False
            elif cmd_args.update_auth:
                auth_session.auth_to_prodtrack_and_save()
                auth_session.auth_to_assetvcs_and_save()
                return False
            elif cmd_args.repath or cmd_args.pin:
                from wizart.desktop import workspace_utils
                workspace_path = self.get_workspace_path()
                if workspace_path:
                    json_path = os.path.join(workspace_path, 'workspace.json').encode()
                    section = 'pin' if cmd_args.pin else 'repath'
                    workspace_utils.append_to_json(json_path, section, *unknown_args)
                else:
                    log.info("Workspace is not set. Can't add repath or pin")
                return False
            elif cmd_args.remove_repath or cmd_args.remove_pin:
                from wizart.desktop import workspace_utils
                workspace_path = self.get_workspace_path()
                if workspace_path:
                    json_path = os.path.join(workspace_path, 'workspace.json').encode()
                    section = 'pin' if cmd_args.remove_pin else 'repath'
                    workspace_utils.remove_from_json(json_path, section, *unknown_args)
                else:
                    log.info("Workspace is not set. Can't remove repath or pin")
                return False
            elif cmd_args.create_workspace:
                self.workspaces_list_content.create_workspace(*unknown_args)
            elif cmd_args.remove_workspace:
                path = unknown_args[0]
                del_id = self.workspace_db.find_workspace_by_path(path)
                if del_id:
                    self.workspace_db.delete_workspace(del_id)
                    curr_id = self.get_workspace()
                    if curr_id == del_id:
                        self.__unset_workspace()
            elif cmd_args.set_workspace:
                path = unknown_args[0]
                set_id = self.workspace_db.find_workspace_by_path(path)
                if set_id:
                    self.__set_workspace(set_id)
            elif cmd_args.unset_workspace:
                self.__unset_workspace()
        return True

    def launch_app(self, app_name, cmd_args):
        found_app = None
        for app in self.apps_tab_content.apps:
            if app.name == app_name:
                found_app = app
        if found_app is None:
            log.error("Not found app with name '%s' " % app_name)
        else:
            found_app.launch(cmd_args)

    def slide_view(self, stack_view, new_page, from_direction="right"):
        offsetx = stack_view.frameRect().width()
        offsety = stack_view.frameRect().height()
        current_page = stack_view.currentWidget()

        new_page.setGeometry(0, 0, offsetx, offsety)

        if from_direction == "left":
            offsetx = -offsetx

        curr_pos = new_page.pos()
        new_page.move(curr_pos.x() + offsetx, curr_pos.y())
        new_page.show()
        new_page.raise_()

        anim_old = QtCore.QPropertyAnimation(current_page, "pos", self)
        anim_old.setDuration(500)
        anim_old.setStartValue(QtCore.QPoint(curr_pos.x(), curr_pos.y()))
        anim_old.setEndValue(QtCore.QPoint(curr_pos.x() - offsetx, curr_pos.y()))
        anim_old.setEasingCurve(QtCore.QEasingCurve.OutBack)

        anim_new = QtCore.QPropertyAnimation(new_page, "pos", self)
        anim_new.setDuration(500)
        anim_new.setStartValue(QtCore.QPoint(curr_pos.x() + offsetx, curr_pos.y()))
        anim_new.setEndValue(QtCore.QPoint(curr_pos.x(), curr_pos.y()))
        anim_new.setEasingCurve(QtCore.QEasingCurve.OutBack)

        anim_group = QtCore.QParallelAnimationGroup(self)
        anim_group.addAnimation(anim_old)
        anim_group.addAnimation(anim_new)

        def slide_finished():
            stack_view.setCurrentWidget(new_page)

        anim_group.finished.connect(slide_finished)
        anim_group.start()

    def add_tab(self, tab_name, tab_widget):
        tab_button = QtWidgets.QPushButton(self)

        tab_button.setMouseTracking(True)
        tab_button.setFocusPolicy(QtCore.Qt.NoFocus)
        tab_button.setFlat(True)
        tab_button.setProperty("active", False)

        tab_button.setText(tab_name)

        # define the event handler when the user changes tab
        def on_tab_selected():
            """
            Event fired when a tab is selected by the user
            """
            # update the state of tab buttons
            for i in range(self.tab_bar.count()):
                button = self.tab_bar.itemAt(i).widget()
                button.setProperty("active", button == tab_button)
                # apply style update
                button.style().unpolish(button)
                button.style().polish(button)

            # display the new tab content
            self.tab_view.setCurrentWidget(tab_widget)

        # link the button to the page widget
        tab_button.toggled.connect(on_tab_selected)
        tab_button.clicked.connect(on_tab_selected)

        # add the tab components to the ui
        self.tab_bar.addWidget(tab_button)
        self.tab_view.addWidget(tab_widget)

        # select tab if this is the first one
        if self.tab_bar.count() == 1:
            on_tab_selected()
