import json
import os
import sqlite3
from typing import Optional
import uuid

from qgis.core import QgsApplication
from qgis.gui import QgisInterface
from qgis.PyQt import QtCore, QtWidgets
from qgis.utils import iface

from ..gp.new_project import NewProjectTask
from ..model.project import Project, project_layers
from ..QRiS.path_utilities import parse_posix_path
from .utilities import add_standard_form_buttons, validate_name
from .widgets.metadata import MetadataWidget


class FrmNewProject(QtWidgets.QDialog):
    closingPlugin = QtCore.pyqtSignal()
    dataChange = QtCore.pyqtSignal(Project)
    newProjectComplete = QtCore.pyqtSignal(str, str)

    def __init__(self, parent, last_parent_project: Optional[str] = None, qris_project: Project = None):
        super().__init__(parent)

        self.iface: QgisInterface = iface
        metadata_json = json.dumps(qris_project.metadata) if qris_project is not None else None

        # Pull the tags out of metadata if the exist
        self.tags = []

        self.metadata_widget = MetadataWidget(self, metadata_json)
        self.setupUi()

        # get a default root path
        self.root_path = parse_posix_path(os.path.expanduser("~"))
        self.last_project_folder = last_parent_project
        if self.last_project_folder is not None:
            if os.path.isdir(self.last_project_folder):
                self.root_path = parse_posix_path(self.last_project_folder)
                self.txtPath.setText(self.root_path)

        self.qris_project = qris_project

        if qris_project is None:
            self.setWindowTitle("Create New Project")
            # Changes to project name change the project folder location
            self.txtName.textChanged.connect(self.update_project_folder)
        else:
            self.setWindowTitle("Edit Project Properties")
            self.txtName.setText(qris_project.name)
            self.txtPath.setText(parse_posix_path(qris_project.project_file))
            self.btnBrowse.setVisible(False)
            system_meta = self.metadata_widget.metadata.get("system", {})
            if "tags" in system_meta:
                self.tags = system_meta["tags"]
                self.txtTags.setText(", ".join(self.tags))
            self.txtDescription.setPlainText(qris_project.description)
            if "objectives" in system_meta:
                self.txtObjectives.setPlainText(system_meta["objectives"])
            if "condition" in system_meta:
                self.txtCondition.setPlainText(system_meta["condition"])

        self.txtName.setFocus()

    def update_project_folder(self):

        text = self.txtName.text().strip()
        clean_name = "".join(e for e in text.replace(" ", "_") if e.isalnum() or e == "_")

        if len(clean_name) > 0:
            self.project_folder = parse_posix_path(os.path.join(self.root_path, clean_name, "qris.gpkg"))
            self.txtPath.setText(self.project_folder)
        else:
            self.project_folder = None
            self.txtPath.setText(parse_posix_path(self.root_path))

    def get_tags(self):
        """Returns a list of non-empty tags from the tags text box"""
        return [tag.strip() for tag in self.txtTags.text().split(",") if tag.strip()]

    def accept(self):

        if not validate_name(self, self.txtName):
            return

        if not self.metadata_widget.validate():
            return

        tags = self.get_tags()
        if tags:
            self.metadata_widget.add_system_metadata("tags", tags)
        else:
            self.metadata_widget.delete_item("system", "tags")

        objectives = self.txtObjectives.toPlainText().strip()
        if objectives:
            self.metadata_widget.add_system_metadata("objectives", objectives)
        else:
            self.metadata_widget.delete_item("system", "objectives")

        condition = self.txtCondition.toPlainText().strip()
        if condition:
            self.metadata_widget.add_system_metadata("condition", condition)
        else:
            self.metadata_widget.delete_item("system", "condition")

        metadata_json = self.metadata_widget.get_json()
        metadata = json.loads(metadata_json) if metadata_json is not None else None

        if isinstance(self.qris_project, Project):
            # Update the existing project
            try:
                with sqlite3.connect(self.qris_project.project_file) as conn:
                    cursor = conn.cursor()
                    cursor.execute("UPDATE projects SET name = ?, description = ?, metadata = ? WHERE id = ?", [self.txtName.text(), self.txtDescription.toPlainText(), metadata_json, self.qris_project.id])
                    conn.commit()
                self.qris_project.name = self.txtName.text()
                self.qris_project.description = self.txtDescription.toPlainText()
                self.qris_project.metadata = metadata
                self.qris_project.project_changed.emit()
            except Exception as ex:
                conn.rollback()
                QtWidgets.QMessageBox.warning(self, "Error Updating Project", str(ex))
                return
        else:
            # Saves the new project from the dialog and creates the master geopackage
            # Create new project directory
            self.project_dir = os.path.dirname(self.txtPath.text())
            if os.path.isdir(self.project_dir):
                QtWidgets.QMessageBox.warning(self, "Directory Already Exists", "The specified directory already exists. Choose a different root directory or change the project name.")
                return

            os.makedirs(self.project_dir)

            new_project_task = NewProjectTask(self.txtName.text(), self.txtPath.text(), self.txtDescription.toPlainText(), str(uuid.uuid4()), project_layers, metadata)
            new_project_task.project_complete.connect(self.on_complete)
            new_project_task.project_create_layers.connect(self.on_creating_layers)
            new_project_task.project_create_schema.connect(self.on_creating_schema)

            QgsApplication.taskManager().addTask(new_project_task)
        super().accept()

    def on_complete(self, result):
        if result is True:
            self.iface.mainWindow().statusBar().showMessage(None)
            self.newProjectComplete.emit(self.project_dir, self.txtPath.text())

    def on_creating_layers(self, layer_number, count_layers):
        self.iface.mainWindow().statusBar().showMessage(f"New QRIS Project: creating layer {layer_number} of {count_layers} layers in project.")

    def on_creating_schema(self):
        self.iface.mainWindow().statusBar().showMessage("New QRIS Project: applying project schema (this may take several moments...)")

    def browse_root_folder(self):

        browse_folder = QtWidgets.QFileDialog.getExistingDirectory(self, "Select an Existing Folder to create a New QRiS Project Folder in", self.root_path)
        if browse_folder:
            self.last_project_folder = browse_folder
            self.root_path = parse_posix_path(browse_folder)
            self.update_project_folder()

    def setupUi(self):

        self.resize(500, 300)
        self.setMinimumSize(300, 200)

        # Top level layout must include parent. Widgets added to this layout do not need parent.
        self.vert = QtWidgets.QVBoxLayout(self)
        self.setLayout(self.vert)
        self.tabs = QtWidgets.QTabWidget()
        self.vert.addWidget(self.tabs)

        self.grid = QtWidgets.QGridLayout()

        self.lblName = QtWidgets.QLabel("Name")
        self.grid.addWidget(self.lblName, 0, 0, 1, 1)

        self.txtName = QtWidgets.QLineEdit()
        self.txtName.setMaxLength(255)
        self.grid.addWidget(self.txtName, 0, 1, 1, 1)

        self.lblPath = QtWidgets.QLabel()
        self.lblPath.setText("Project Path")
        self.grid.addWidget(self.lblPath, 1, 0, 1, 1)

        horiz_path = QtWidgets.QHBoxLayout()
        self.grid.addLayout(horiz_path, 1, 1, 1, 1)

        self.txtPath = QtWidgets.QLineEdit()
        self.txtPath.setReadOnly(True)
        horiz_path.addWidget(self.txtPath)

        self.btnBrowse = QtWidgets.QPushButton("...")
        self.btnBrowse.setFixedWidth(30)
        self.btnBrowse.clicked.connect(self.browse_root_folder)
        horiz_path.addWidget(self.btnBrowse)

        self.lblTags = QtWidgets.QLabel("Tags")
        self.grid.addWidget(self.lblTags, 2, 0, 1, 1)

        self.txtTags = QtWidgets.QLineEdit()
        self.txtTags.setToolTip("Comma separated list of tags for the project")
        self.grid.addWidget(self.txtTags, 2, 1, 1, 1)

        self.grid.setRowStretch(3, 1)

        self.tabProperties = QtWidgets.QWidget()
        self.tabs.addTab(self.tabProperties, "Project Properties")
        self.tabProperties.setLayout(self.grid)

        # Description Tab
        self.tabDescription = QtWidgets.QWidget()
        self.description_layout = QtWidgets.QVBoxLayout(self.tabDescription)
        self.description_layout.setContentsMargins(9, 9, 9, 9)
        self.txtDescription = QtWidgets.QPlainTextEdit()
        self.description_layout.addWidget(self.txtDescription)
        self.tabs.addTab(self.tabDescription, "Description")

        # Objectives Tab
        self.tabObjectives = QtWidgets.QWidget()
        self.objectives_layout = QtWidgets.QVBoxLayout(self.tabObjectives)
        self.objectives_layout.setContentsMargins(9, 9, 9, 9)
        self.txtObjectives = QtWidgets.QPlainTextEdit()
        self.objectives_layout.addWidget(self.txtObjectives)
        self.tabs.addTab(self.tabObjectives, "Objectives")

        # Condition Tab
        self.tabCondition = QtWidgets.QWidget()
        self.condition_layout = QtWidgets.QVBoxLayout(self.tabCondition)
        self.condition_layout.setContentsMargins(9, 9, 9, 9)
        self.txtCondition = QtWidgets.QPlainTextEdit()
        self.condition_layout.addWidget(self.txtCondition)
        self.tabs.addTab(self.tabCondition, "Condition")

        # Metadata Tab
        self.tabs.addTab(self.metadata_widget, "Metadata")

        self.vert.addLayout(add_standard_form_buttons(self, "projects"))


def format_layer_name(input_text):
    """Takes raw text from an input and field and returns a GIS friendly text string suitable for naming layers"""
    valid_text = "".join(e for e in input_text.replace(" ", "_") if e.isalnum() or e == "_")
    return valid_text
