import os
import sys
import json
from subprocess import Popen
try:
    import ftrack_api_old as ftrack_api
except Exception:
    import ftrack_api
from pype.api import get_current_project_settings
from pype import lib as pypelib
from avalon.vendor.Qt import QtWidgets, QtCore
from avalon import io, api, style, schema
from avalon.tools import lib as parentlib
from . import widget, model

module = sys.modules[__name__]
module.window = None


class Window(QtWidgets.QDialog):
    """Asset creator interface

    """

    def __init__(self, parent=None, context=None):
        super(Window, self).__init__(parent)
        self.context = context
        project_name = io.active_project()
        self.setWindowTitle("Asset creator ({0})".format(project_name))
        self.setFocusPolicy(QtCore.Qt.StrongFocus)
        self.setAttribute(QtCore.Qt.WA_DeleteOnClose)

        # Validators
        self.valid_parent = False

        self.session = None

        # assets widget
        assets_widget = QtWidgets.QWidget()
        assets_widget.setContentsMargins(0, 0, 0, 0)
        assets_layout = QtWidgets.QVBoxLayout(assets_widget)
        assets = widget.AssetWidget()
        assets.view.setSelectionMode(assets.view.ExtendedSelection)
        assets_layout.addWidget(assets)

        # Outlink
        label_outlink = QtWidgets.QLabel("Outlink:")
        input_outlink = QtWidgets.QLineEdit()
        input_outlink.setReadOnly(True)
        input_outlink.setStyleSheet("background-color: #333333;")
        checkbox_outlink = QtWidgets.QCheckBox("Use outlink")
        # Parent
        label_parent = QtWidgets.QLabel("*Parent:")
        input_parent = QtWidgets.QLineEdit()
        input_parent.setReadOnly(True)
        input_parent.setStyleSheet("background-color: #333333;")

        # Name
        label_name = QtWidgets.QLabel("*Name:")
        input_name = QtWidgets.QLineEdit()
        input_name.setPlaceholderText("<asset name>")

        # Asset Build
        label_assetbuild = QtWidgets.QLabel("Asset Build:")
        combo_assetbuilt = QtWidgets.QComboBox()

        # Task template
        label_task_template = QtWidgets.QLabel("Task template:")
        combo_task_template = QtWidgets.QComboBox()

        # Info widget
        info_widget = QtWidgets.QWidget()
        info_widget.setContentsMargins(10, 10, 10, 10)
        info_layout = QtWidgets.QVBoxLayout(info_widget)

        # Inputs widget
        inputs_widget = QtWidgets.QWidget()
        inputs_widget.setContentsMargins(0, 0, 0, 0)

        inputs_layout = QtWidgets.QFormLayout(inputs_widget)
        inputs_layout.addRow(label_outlink, input_outlink)
        inputs_layout.addRow(None, checkbox_outlink)
        inputs_layout.addRow(label_parent, input_parent)
        inputs_layout.addRow(label_name, input_name)
        inputs_layout.addRow(label_assetbuild, combo_assetbuilt)
        inputs_layout.addRow(label_task_template, combo_task_template)

        # Add button
        btns_widget = QtWidgets.QWidget()
        btns_widget.setContentsMargins(0, 0, 0, 0)
        btn_layout = QtWidgets.QHBoxLayout(btns_widget)
        btn_create_asset = QtWidgets.QPushButton("Create asset")
        btn_create_asset.setToolTip(
            "Creates all neccessary components for asset"
        )
        checkbox_app = None
        if self.context is not None:
            checkbox_app = QtWidgets.QCheckBox("Open {}".format(
                self.context.capitalize())
            )
            btn_layout.addWidget(checkbox_app)
        btn_layout.addWidget(btn_create_asset)

        task_view = QtWidgets.QTreeView()
        task_view.setIndentation(0)
        task_model = model.TasksModel()
        task_view.setModel(task_model)

        info_layout.addWidget(inputs_widget)
        info_layout.addWidget(task_view)
        info_layout.addWidget(btns_widget)

        # Body
        body = QtWidgets.QSplitter()
        body.setContentsMargins(0, 0, 0, 0)
        body.setSizePolicy(QtWidgets.QSizePolicy.Expanding,
                           QtWidgets.QSizePolicy.Expanding)
        body.setOrientation(QtCore.Qt.Horizontal)
        body.addWidget(assets_widget)
        body.addWidget(info_widget)
        body.setStretchFactor(0, 100)
        body.setStretchFactor(1, 150)

        # statusbar
        message = QtWidgets.QLabel()
        message.setFixedHeight(20)

        statusbar = QtWidgets.QWidget()
        layout = QtWidgets.QHBoxLayout(statusbar)
        layout.setContentsMargins(0, 0, 0, 0)
        layout.addWidget(message)

        layout = QtWidgets.QVBoxLayout(self)
        layout.addWidget(body)
        layout.addWidget(statusbar)

        self.data = {
            "label": {
                "message": message,
            },
            "view": {
                "tasks": task_view
            },
            "model": {
                "assets": assets,
                "tasks": task_model
            },
            "inputs": {
                "outlink": input_outlink,
                "outlink_cb": checkbox_outlink,
                "parent": input_parent,
                "name": input_name,
                "assetbuild": combo_assetbuilt,
                "tasktemplate": combo_task_template,
                "open_app": checkbox_app
            },
            "buttons": {
                "create_asset": btn_create_asset
            }
        }

        # signals
        btn_create_asset.clicked.connect(self.create_asset)
        assets.selection_changed.connect(self.on_asset_changed)
        input_name.textChanged.connect(self.on_asset_name_change)
        checkbox_outlink.toggled.connect(self.on_outlink_checkbox_change)
        combo_task_template.currentTextChanged.connect(
            self.on_task_template_changed
        )
        if self.context is not None:
            checkbox_app.toggled.connect(self.on_app_checkbox_change)
        # on start
        self.on_start()

        self.resize(600, 500)

        self.echo("Connected to project: {0}".format(project_name))

    def open_app(self):
        if self.context == 'maya':
            Popen("maya")
        else:
            message = QtWidgets.QMessageBox(self)
            message.setWindowTitle("App is not set")
            message.setIcon(QtWidgets.QMessageBox.Critical)
            message.show()

    def on_start(self):
        project_name = io.Session['AVALON_PROJECT']
        project_query = 'Project where full_name is "{}"'.format(project_name)
        if self.session is None:
            session = ftrack_api.Session()
            self.session = session
        else:
            session = self.session
        ft_project = session.query(project_query).one()
        schema_name = ft_project['project_schema']['name']
        # Load config
        schemas_items = get_current_project_settings().get('ftrack', {}).get(
            'project_schemas', {}
        )
        # Get info if it is silo project
        self.silos = io.distinct("silo")
        if self.silos and None in self.silos:
            self.silos = None

        key = "default"
        if schema_name in schemas_items:
            key = schema_name

        self.config_data = schemas_items[key]

        # set outlink
        input_outlink = self.data['inputs']['outlink']
        checkbox_outlink = self.data['inputs']['outlink_cb']
        outlink_text = io.Session.get('AVALON_ASSET', '')
        checkbox_outlink.setChecked(True)
        if outlink_text == '':
            outlink_text = '< No context >'
            checkbox_outlink.setChecked(False)
            checkbox_outlink.hide()
        input_outlink.setText(outlink_text)

        # load asset build types
        self.load_assetbuild_types()

        # Load task templates
        self.load_task_templates()
        self.data["model"]["assets"].refresh()
        self.on_asset_changed()

    def create_asset(self):
        name_input = self.data['inputs']['name']
        name = name_input.text()
        test_name = name.replace(' ', '')
        error_message = None
        message = QtWidgets.QMessageBox(self)
        message.setWindowTitle("Some errors has occured")
        message.setIcon(QtWidgets.QMessageBox.Critical)
        # TODO: show error messages on any error
        if self.valid_parent is not True and test_name == '':
            error_message = "Name is not set and Parent is not selected"
        elif self.valid_parent is not True:
            error_message = "Parent is not selected"
        elif test_name == '':
            error_message = "Name is not set"

        if error_message is not None:
            message.setText(error_message)
            message.show()
            return

        test_name_exists = io.find({
            'type': 'asset',
            'name': name
        })
        existing_assets = [x for x in test_name_exists]
        if len(existing_assets) > 0:
            message.setText("Entered Asset name is occupied")
            message.show()
            return

        checkbox_app = self.data['inputs']['open_app']
        if checkbox_app is not None and checkbox_app.isChecked() is True:
            task_view = self.data["view"]["tasks"]
            task_model = self.data["model"]["tasks"]
            try:
                index = task_view.selectedIndexes()[0]
                task_name = task_model.itemData(index)[0]
            except Exception:
                message.setText("Please select task")
                message.show()
                return

        # Get ftrack session
        if self.session is None:
            session = ftrack_api.Session()
            self.session = session
        else:
            session = self.session

        # Get Ftrack project entity
        project_name = io.Session['AVALON_PROJECT']
        project_query = 'Project where full_name is "{}"'.format(project_name)
        try:
            ft_project = session.query(project_query).one()
        except Exception:
            message.setText("Ftrack project was not found")
            message.show()
            return

        # Get Ftrack entity of parent
        ft_parent = None
        assets_model = self.data["model"]["assets"]
        selected = assets_model.get_selected_assets()
        parent = io.find_one({"_id": selected[0], "type": "asset"})
        asset_id = parent.get('data', {}).get('ftrackId', None)
        asset_entity_type = parent.get('data', {}).get('entityType', None)
        asset_query = '{} where id is "{}"'
        if asset_id is not None and asset_entity_type is not None:
            try:
                ft_parent = session.query(asset_query.format(
                    asset_entity_type, asset_id)
                ).one()
            except Exception:
                ft_parent = None

        if ft_parent is None:
            ft_parent = self.get_ftrack_asset(parent, ft_project)

        if ft_parent is None:
            message.setText("Parent's Ftrack entity was not found")
            message.show()
            return

        asset_build_combo = self.data['inputs']['assetbuild']
        asset_type_name = asset_build_combo.currentText()
        asset_type_query = 'Type where name is "{}"'.format(asset_type_name)
        try:
            asset_type = session.query(asset_type_query).one()
        except Exception:
            message.setText("Selected Asset Build type does not exists")
            message.show()
            return

        for children in ft_parent['children']:
            if children['name'] == name:
                message.setText("Entered Asset name is occupied")
                message.show()
                return

        task_template_combo = self.data['inputs']['tasktemplate']
        task_template = task_template_combo.currentText()
        tasks = []
        for template in self.config_data['task_templates']:
            if template['name'] == task_template:
                tasks = template['task_types']
                break

        available_task_types = []
        task_types = ft_project['project_schema']['_task_type_schema']
        for task_type in task_types['types']:
            available_task_types.append(task_type['name'])

        not_possible_tasks = []
        for task in tasks:
            if task not in available_task_types:
                not_possible_tasks.append(task)

        if len(not_possible_tasks) != 0:
            message.setText((
                "These Task types weren't found"
                " in Ftrack project schema:\n{}").format(
                ', '.join(not_possible_tasks))
            )
            message.show()
            return

        # Create asset build
        asset_build_data = {
            'name': name,
            'project_id': ft_project['id'],
            'parent_id': ft_parent['id'],
            'type': asset_type
        }

        new_entity = session.create('AssetBuild', asset_build_data)

        task_data = {
            'project_id': ft_project['id'],
            'parent_id': new_entity['id']
        }

        for task in tasks:
            type = session.query('Type where name is "{}"'.format(task)).one()

            task_data['type_id'] = type['id']
            task_data['name'] = task
            session.create('Task', task_data)

        av_project = io.find_one({'type': 'project'})

        hiearchy_items = []
        hiearchy_items.extend(self.get_avalon_parent(parent))
        hiearchy_items.append(parent['name'])

        hierarchy = os.path.sep.join(hiearchy_items)
        new_asset_data = {
            'ftrackId': new_entity['id'],
            'entityType': new_entity.entity_type,
            'visualParent': parent['_id'],
            'tasks': tasks,
            'parents': hiearchy_items,
            'hierarchy': hierarchy
        }
        new_asset_info = {
            'parent': av_project['_id'],
            'name': name,
            'schema': "avalon-core:asset-3.0",
            'type': 'asset',
            'data': new_asset_data
        }

        # Backwards compatibility (add silo from parent if is silo project)
        if self.silos:
            new_asset_info["silo"] = parent["silo"]

        try:
            schema.validate(new_asset_info)
        except Exception:
            message.setText((
                'Asset information are not valid'
                ' to create asset in avalon database'
            ))
            message.show()
            session.rollback()
            return
        io.insert_one(new_asset_info)
        session.commit()

        outlink_cb = self.data['inputs']['outlink_cb']
        if outlink_cb.isChecked() is True:
            outlink_input = self.data['inputs']['outlink']
            outlink_name = outlink_input.text()
            outlink_asset = io.find_one({
                'type': 'asset',
                'name': outlink_name
            })
            outlink_ft_id = outlink_asset.get('data', {}).get('ftrackId', None)
            outlink_entity_type = outlink_asset.get(
                'data', {}
            ).get('entityType', None)
            if outlink_ft_id is not None and outlink_entity_type is not None:
                try:
                    outlink_entity = session.query(asset_query.format()).one()
                except Exception:
                    outlink_entity = None

            if outlink_entity is None:
                outlink_entity = self.get_ftrack_asset(
                    outlink_asset, ft_project
                )

            if outlink_entity is None:
                message.setText("Outlink's Ftrack entity was not found")
                message.show()
                return

            link_data = {
                'from_id': new_entity['id'],
                'to_id': outlink_entity['id']
            }
            session.create('TypedContextLink', link_data)
            session.commit()

        if checkbox_app is not None and checkbox_app.isChecked() is True:
            origin_asset = api.Session.get('AVALON_ASSET', None)
            origin_task = api.Session.get('AVALON_TASK', None)
            asset_name = name
            task_view = self.data["view"]["tasks"]
            task_model = self.data["model"]["tasks"]
            try:
                index = task_view.selectedIndexes()[0]
            except Exception:
                message.setText("No task is selected. App won't be launched")
                message.show()
                return
            task_name = task_model.itemData(index)[0]
            try:
                api.update_current_task(task=task_name, asset=asset_name)
                self.open_app()

            finally:
                if origin_task is not None and origin_asset is not None:
                    api.update_current_task(
                        task=origin_task, asset=origin_asset
                    )

        message.setWindowTitle("Asset Created")
        message.setText("Asset Created successfully")
        message.setIcon(QtWidgets.QMessageBox.Information)
        message.show()

    def get_ftrack_asset(self, asset, ft_project):
        parenthood = []
        parenthood.extend(self.get_avalon_parent(asset))
        parenthood.append(asset['name'])
        parenthood = list(reversed(parenthood))
        output_entity = None
        ft_entity = ft_project
        index = len(parenthood) - 1
        while True:
            name = parenthood[index]
            found = False
            for children in ft_entity['children']:
                if children['name'] == name:
                    ft_entity = children
                    found = True
                    break
            if found is False:
                return None
            if index == 0:
                output_entity = ft_entity
                break
            index -= 1

        return output_entity

    def get_avalon_parent(self, entity):
        parent_id = entity['data']['visualParent']
        parents = []
        if parent_id is not None:
            parent = io.find_one({'_id': parent_id})
            parents.extend(self.get_avalon_parent(parent))
            parents.append(parent['name'])
        return parents

    def echo(self, message):
        widget = self.data["label"]["message"]
        widget.setText(str(message))

        QtCore.QTimer.singleShot(5000, lambda: widget.setText(""))

        print(message)

    def load_task_templates(self):
        templates = self.config_data.get('task_templates', [])
        all_names = []
        for template in templates:
            all_names.append(template['name'])

        tt_combobox = self.data['inputs']['tasktemplate']
        tt_combobox.clear()
        tt_combobox.addItems(all_names)

    def load_assetbuild_types(self):
        types = []
        schemas = self.config_data.get('schemas', [])
        for _schema in schemas:
            if _schema['object_type'] == 'Asset Build':
                types = _schema['task_types']
                break
        ab_combobox = self.data['inputs']['assetbuild']
        ab_combobox.clear()
        ab_combobox.addItems(types)

    def on_app_checkbox_change(self):
        task_model = self.data['model']['tasks']
        app_checkbox = self.data['inputs']['open_app']
        if app_checkbox.isChecked() is True:
            task_model.selectable = True
        else:
            task_model.selectable = False

    def on_outlink_checkbox_change(self):
        checkbox_outlink = self.data['inputs']['outlink_cb']
        outlink_input = self.data['inputs']['outlink']
        if checkbox_outlink.isChecked() is True:
            outlink_text = io.Session['AVALON_ASSET']
        else:
            outlink_text = '< Outlinks won\'t be set >'

        outlink_input.setText(outlink_text)

    def on_task_template_changed(self):
        combobox = self.data['inputs']['tasktemplate']
        task_model = self.data['model']['tasks']
        name = combobox.currentText()
        tasks = []
        for template in self.config_data['task_templates']:
            if template['name'] == name:
                tasks = template['task_types']
                break
        task_model.set_tasks(tasks)

    def on_asset_changed(self):
        """Callback on asset selection changed

        This updates the task view.

        """
        assets_model = self.data["model"]["assets"]
        parent_input = self.data['inputs']['parent']
        selected = assets_model.get_selected_assets()

        self.valid_parent = False
        if len(selected) > 1:
            parent_input.setText('< Please select only one asset! >')
        elif len(selected) == 1:
            if isinstance(selected[0], io.ObjectId):
                self.valid_parent = True
                asset = io.find_one({"_id": selected[0], "type": "asset"})
                parent_input.setText(asset['name'])
            else:
                parent_input.setText('< Selected invalid parent(silo) >')
        else:
            parent_input.setText('< Nothing is selected >')

        self.creatability_check()

    def on_asset_name_change(self):
        self.creatability_check()

    def creatability_check(self):
        name_input = self.data['inputs']['name']
        name = str(name_input.text()).strip()
        creatable = False
        if name and self.valid_parent:
            creatable = True

        self.data["buttons"]["create_asset"].setEnabled(creatable)



def show(parent=None, debug=False, context=None):
    """Display Loader GUI

    Arguments:
        debug (bool, optional): Run loader in debug-mode,
            defaults to False

    """

    try:
        module.window.close()
        del module.window
    except (RuntimeError, AttributeError):
        pass

    if debug is True:
        io.install()

    with parentlib.application():
        window = Window(parent, context)
        window.setStyleSheet(style.load_stylesheet())
        window.show()

        module.window = window


def cli(args):
    import argparse
    parser = argparse.ArgumentParser()
    parser.add_argument("project")
    parser.add_argument("asset")

    args = parser.parse_args(args)
    project = args.project
    asset = args.asset
    io.install()

    api.Session["AVALON_PROJECT"] = project
    if asset != '':
        api.Session["AVALON_ASSET"] = asset

    show()
