import json
import os
import re
from typing import Optional

from qgis.core import QgsApplication
from qgis.PyQt import QtCore, QtGui, QtWidgets
from qgis.PyQt.QtCore import pyqtSlot

from ..compat import (
    HEADER_STRETCH,
    MESSAGE_LEVEL_CRITICAL,
    QABSTRACTITEMVIEW_NO_EDIT_TRIGGERS,
    QABSTRACTITEMVIEW_SELECT_ROWS,
    QABSTRACTITEMVIEW_SINGLE_SELECTION,
    SPSZ_EXPANDING,
    SPSZ_MAXIMUM,
    SPSZ_MINIMUM,
    SPSZ_PREFERRED,
    USER_ROLE,
)
from ..gp.copy_file import FileCopyTask
from ..model.attachment import Attachment, attachments_path, insert_attachment, load_events_for_attachment
from ..model.project import Project
from ..QRiS.path_utilities import parse_posix_path
from ..QRiS.settings import Settings
from .utilities import add_standard_form_buttons
from .widgets.metadata import MetadataWidget

ATTACHMENT_TYPE_LABELS = ["Figure", "Link", "Report", "Table"]


class ClickableDateEdit(QtWidgets.QDateEdit):
    def focusInEvent(self, event):
        super().focusInEvent(event)
        if self.specialValueText() == self.text():
            self.setDate(QtCore.QDate.currentDate())


class FrmAttachment(QtWidgets.QDialog):
    def __init__(self, parent, qris_project: Project, attachment: Attachment = None, attachment_type: str = Attachment.TYPE_FILE):

        self.qris_project = qris_project
        self.metadata = None
        self.attachment = attachment
        self.attachment_type = attachment_type if attachment is None else attachment.attachment_type
        self.source = FileBrowseWidget() if self.attachment_type == Attachment.TYPE_FILE else WebLinkWidget()
        self.extension = None

        super().__init__(parent)
        metadata_json = json.dumps(attachment.metadata) if attachment is not None else None
        self.metadata_widget = MetadataWidget(self, metadata_json)
        self.setupUi()

        self.setWindowTitle("Import File Attachment" if self.attachment is None else "Edit Attachment Properties")

        if self.attachment_type == Attachment.TYPE_WEB_LINK:
            self.lblProjectPath.setEnabled(False)
            self.lblProjectPath.setVisible(False)
            self.txtProjectPath.setEnabled(False)
            self.txtProjectPath.setVisible(False)
            self.lblSource.setText("Web Link")
            self.setWindowTitle("Add Web Link Attachment" if self.attachment is None else "Edit Web Link Properties")
        else:
            self.txtName.textChanged.connect(self.on_name_changed)

        if self.attachment is None:
            if self.attachment_type == Attachment.TYPE_FILE:
                self.source.lineEdit.textChanged.connect(self.on_path_changed)
        else:
            self.txtName.setText(attachment.name)
            self.txtDescription.setPlainText(attachment.description)
            if self.attachment_type == Attachment.TYPE_FILE:
                self.extension = os.path.splitext(attachment.path)[1]
                self.txtProjectPath.setText(self.attachment.attachment_path(self.qris_project.project_file))
                self.lblSource.setVisible(False)
                self.source.setEnabled(False)
                self.source.setVisible(False)
            else:
                self.source.lineEdit.setText(attachment.path)
            # Populate type label
            if attachment.attachment_type_label:
                idx = self.cboTypeLabel.findText(attachment.attachment_type_label)
                if idx >= 0:
                    self.cboTypeLabel.setCurrentIndex(idx)
                else:
                    self.cboTypeLabel.setEditText(attachment.attachment_type_label)
            # Populate date
            if attachment.date:
                self.txtDate.setDate(QtCore.QDate.fromString(attachment.date, "yyyy-MM-dd"))
        self.txtName.selectAll()

    def accept(self):

        # make sure there is not already an attachment with the same name
        if self.txtName.text() in [a.name for a in self.qris_project.attachments.values() if a.id != (self.attachment.id if self.attachment else None)]:
            QtWidgets.QMessageBox.warning(self, "Duplicate Name", f"An attachment with the name '{self.txtName.text()}' already exists. Please choose a unique name.")
            self.txtName.setFocus()
            return

        # make sure the new file path will not result in a duplicate name
        if self.attachment_type == Attachment.TYPE_FILE:
            if self.txtProjectPath.text() in [a.path for a in self.qris_project.attachments.values() if a.id != (self.attachment.id if self.attachment else None)]:
                QtWidgets.QMessageBox.warning(self, "Duplicate Path", f"An attachment with the path '{self.txtProjectPath.text()}' already exists. Please choose a unique path.")
                self.txtProjectPath.setFocus()
                return

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

        if self.attachment is not None:
            try:
                new_path = self.source.lineEdit.text() if self.attachment_type == Attachment.TYPE_WEB_LINK else os.path.basename(self.txtProjectPath.text())
                if self.attachment_type == Attachment.TYPE_FILE:
                    # Rename the file if the name has changed
                    if self.txtProjectPath.text() != self.attachment.attachment_path(self.qris_project.project_file):
                        os.rename(self.attachment.attachment_path(self.qris_project.project_file), self.txtProjectPath.text())
                self.attachment.update(self.qris_project.project_file, self.txtName.text(), path=new_path, description=self.txtDescription.toPlainText(), metadata=self.metadata)
                self.qris_project.project_changed.emit()
            except Exception as ex:
                if "unique" in str(ex).lower():
                    QtWidgets.QMessageBox.warning(self, "Duplicate Name", f"An attachment with the name '{self.txtName.text()}' already exists. Please choose a unique name.")
                    self.txtName.setFocus()
                else:
                    QtWidgets.QMessageBox.warning(self, "Error Saving Attachment", str(ex))
                return

            super().accept()

        else:
            try:
                if self.attachment_type == Attachment.TYPE_FILE:
                    task = FileCopyTask(self.source.lineEdit.text(), self.txtProjectPath.text(), self.on_copy_complete)
                    QgsApplication.taskManager().addTask(task)
                else:
                    # For web links, we don't need to copy files, just create the attachment
                    self.attachment = insert_attachment(self.qris_project.project_file, self.txtName.text(), self.source.lineEdit.text(), self.attachment_type, self.txtDescription.toPlainText(), self.metadata)
                    self.qris_project.add_db_item(self.attachment)
                    super().accept()
            except Exception as ex:
                self.attachment = None
                QtWidgets.QMessageBox.warning(self, "Error Importing Attachment", str(ex))
                return

    def _apply_form_to_metadata(self):
        """Write date and type label from form widgets into the metadata widget."""
        if self.txtDate.date() != self.txtDate.minimumDate():
            self.metadata_widget.add_system_metadata("date", self.txtDate.date().toString("yyyy-MM-dd"))
        else:
            self.metadata_widget.remove_system_metadata("date")
        type_label = self.cboTypeLabel.currentText().strip()
        if type_label:
            self.metadata_widget.add_system_metadata("attachment_type_label", type_label)
        else:
            self.metadata_widget.remove_system_metadata("attachment_type_label")

    @pyqtSlot(bool)
    def on_copy_complete(self, result: bool, error: Optional[str] = None):

        if result is True:
            Settings().msg_bar("Feature Class Copy Complete.", f"{self.txtProjectPath.text()} saved successfully.")
        else:
            Settings().msg_bar("Feature Class Copy Error", "Review the QGIS log.", MESSAGE_LEVEL_CRITICAL)
        if error:
            Settings().log(f"Error copying file: {error}", MESSAGE_LEVEL_CRITICAL)
        try:
            self.attachment = insert_attachment(self.qris_project.project_file, self.txtName.text(), os.path.basename(self.txtProjectPath.text()), self.attachment_type, self.txtDescription.toPlainText(), self.metadata)
            self.qris_project.add_db_item(self.attachment)
        except Exception as ex:
            if "unique" in str(ex).lower():
                QtWidgets.QMessageBox.warning(self, "Duplicate Name", f"An attachment with the name '{self.txtName.text()}' already exists. Please choose a unique name.")
                self.txtName.setFocus()
            else:
                QtWidgets.QMessageBox.warning(self, "Error Saving Attachment", str(ex))
            return
        super().accept()

    def on_path_changed(self):
        # get the file name from the source path and set it to the name field
        if self.attachment_type == Attachment.TYPE_FILE:
            file_path = self.source.lineEdit.text()
            if file_path:
                file_name = os.path.basename(file_path)
                # drop the extension
                file_name = os.path.splitext(file_name)[0]
                self.txtName.setText(file_name)
                self.on_name_changed(file_name)
            else:
                self.txtName.setText("")

    def on_name_changed(self, new_name):

        # Replace spaces with underscores and remove non-alphanumeric characters
        clean_name = re.sub("[^A-Za-z0-9_]+", "", self.txtName.text().replace(" ", "_"))
        if len(clean_name) > 0:
            parent_folder = attachments_path(self.qris_project.project_file)
            ext = ""
            if self.attachment_type == Attachment.TYPE_FILE:
                ext = self.extension if self.extension is not None else os.path.splitext(self.source.lineEdit.text())[1]
            self.txtProjectPath.setText(parse_posix_path(os.path.join(parent_folder, self.qris_project.get_safe_file_name(clean_name, ext))))
        else:
            self.txtProjectPath.setText("")

    def on_clear_date_clicked(self):
        self.txtDate.setDate(self.txtDate.minimumDate())

    def _setup_referenced_by_tab(self):
        layout = QtWidgets.QVBoxLayout(self.tab_referenced_by)

        self.tbl_referenced_by = QtWidgets.QTableWidget()
        self.tbl_referenced_by.setColumnCount(2)
        self.tbl_referenced_by.setHorizontalHeaderLabels(["Name", "Purpose"])
        self.tbl_referenced_by.verticalHeader().setVisible(False)
        self.tbl_referenced_by.setSelectionMode(QABSTRACTITEMVIEW_SINGLE_SELECTION)
        self.tbl_referenced_by.setSelectionBehavior(QABSTRACTITEMVIEW_SELECT_ROWS)
        self.tbl_referenced_by.setEditTriggers(QABSTRACTITEMVIEW_NO_EDIT_TRIGGERS)
        hdr = self.tbl_referenced_by.horizontalHeader()
        hdr.setSectionResizeMode(0, HEADER_STRETCH)
        hdr.setSectionResizeMode(1, HEADER_STRETCH)
        layout.addWidget(self.tbl_referenced_by)

        self.lbl_referenced_by_summary = QtWidgets.QLabel("")
        layout.addWidget(self.lbl_referenced_by_summary)

        self._load_referenced_by()

    def _load_referenced_by(self):
        rows = load_events_for_attachment(self.qris_project.project_file, self.attachment.id)
        self.tbl_referenced_by.setRowCount(0)
        self.tbl_referenced_by.setRowCount(len(rows))

        for i, (event_id, event_name, purpose) in enumerate(rows):
            name_item = QtWidgets.QTableWidgetItem(event_name)
            event = self.qris_project.events.get(event_id)
            if event is not None:
                name_item.setIcon(QtGui.QIcon(f":/plugins/qris_toolbar/{event.icon}"))
            name_item.setData(USER_ROLE, event_id)
            self.tbl_referenced_by.setItem(i, 0, name_item)

            purpose_item = QtWidgets.QTableWidgetItem(purpose)
            self.tbl_referenced_by.setItem(i, 1, purpose_item)

        count = len(rows)
        self.lbl_referenced_by_summary.setText(f"This attachment is referenced by {count} event{'s' if count != 1 else ''}.")

    def setupUi(self):

        self.resize(500, 450)
        self.setMinimumSize(400, 350)

        # 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.grid.addWidget(QtWidgets.QLabel("Name"), 0, 0, 1, 1)

        self.txtName = QtWidgets.QLineEdit()
        self.txtName.setToolTip("Name of the attachment. This must be unique within the project.")
        self.txtName.setMaxLength(255)
        self.grid.addWidget(self.txtName, 0, 1, 1, 1)

        self.lblSource = QtWidgets.QLabel("Source Path")
        self.grid.addWidget(self.lblSource, 1, 0, 1, 1)
        self.grid.addWidget(self.source, 1, 1, 1, 1)

        self.lblProjectPath = QtWidgets.QLabel("Project Path")
        self.grid.addWidget(self.lblProjectPath, 2, 0, 1, 1)

        self.txtProjectPath = QtWidgets.QLineEdit()
        self.txtProjectPath.setReadOnly(True)
        self.grid.addWidget(self.txtProjectPath, 2, 1, 1, 1)

        self.grid.addWidget(QtWidgets.QLabel("Type"), 3, 0, 1, 1)

        self.cboTypeLabel = QtWidgets.QComboBox()
        self.cboTypeLabel.setEditable(True)
        self.cboTypeLabel.setToolTip("Category of the attachment. Select from the list or type a new type.")
        self.cboTypeLabel.addItem("")
        for label in ATTACHMENT_TYPE_LABELS:
            self.cboTypeLabel.addItem(label)
        self.grid.addWidget(self.cboTypeLabel, 3, 1, 1, 1)

        self.grid.addWidget(QtWidgets.QLabel("Date"), 4, 0, 1, 1)

        self.horiz_date = QtWidgets.QHBoxLayout()
        self.grid.addLayout(self.horiz_date, 4, 1, 1, 1)

        self.txtDate = ClickableDateEdit()
        self.txtDate.setToolTip("Date associated with the attachment")
        self.txtDate.setMinimumDate(QtCore.QDate(1900, 1, 1))
        self.txtDate.setSpecialValueText("No Date")
        self.txtDate.setDate(self.txtDate.minimumDate())
        self.txtDate.setCalendarPopup(True)
        self.horiz_date.addWidget(self.txtDate)

        self.btn_clear_date = QtWidgets.QPushButton("Clear")
        self.btn_clear_date.setToolTip("Clear the date")
        self.btn_clear_date.setSizePolicy(QtWidgets.QSizePolicy(SPSZ_MAXIMUM, SPSZ_MAXIMUM))
        self.btn_clear_date.clicked.connect(self.on_clear_date_clicked)
        self.horiz_date.addWidget(self.btn_clear_date)

        self.grid.addItem(QtWidgets.QSpacerItem(20, 40, SPSZ_MINIMUM, SPSZ_EXPANDING), 5, 0, 1, 2)

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

        # Referenced By Tab (only shown for existing attachments)
        if self.attachment is not None:
            self.tab_referenced_by = QtWidgets.QWidget()
            self.tabs.addTab(self.tab_referenced_by, "Referenced By")
            self._setup_referenced_by_tab()

        # Description Tab
        self.tabDescription = QtWidgets.QWidget()
        self.tabs.addTab(self.tabDescription, "Description")
        self.tabDescription.setLayout(QtWidgets.QVBoxLayout())

        self.txtDescription = QtWidgets.QPlainTextEdit()
        self.tabDescription.layout().addWidget(self.txtDescription)

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

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


class FileBrowseWidget(QtWidgets.QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)

        layout = QtWidgets.QHBoxLayout(self)
        layout.setContentsMargins(0, 0, 0, 0)
        self.setLayout(layout)

        self.lineEdit = QtWidgets.QLineEdit(self)
        self.lineEdit.setReadOnly(True)
        self.lineEdit.setSizePolicy(SPSZ_EXPANDING, SPSZ_PREFERRED)
        layout.addWidget(self.lineEdit)

        self.browseButton = QtWidgets.QPushButton("...", self)
        self.browseButton.setFixedWidth(30)
        self.browseButton.setSizePolicy(SPSZ_MINIMUM, SPSZ_PREFERRED)
        self.browseButton.clicked.connect(self.browse)
        layout.addWidget(self.browseButton)

        self.setSizePolicy(SPSZ_EXPANDING, SPSZ_PREFERRED)

    def browse(self):
        options = QtWidgets.QFileDialog.Options()
        file_path, _ = QtWidgets.QFileDialog.getOpenFileName(self, "Select File", "", "All Files (*)", options=options)
        if file_path and os.path.isfile(file_path):
            self.lineEdit.setText(file_path)
            return file_path
        return None


class WebLinkWidget(QtWidgets.QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)

        layout = QtWidgets.QHBoxLayout(self)
        layout.setContentsMargins(0, 0, 0, 0)
        self.setLayout(layout)

        self.lineEdit = QtWidgets.QLineEdit(self)
        self.lineEdit.setSizePolicy(SPSZ_EXPANDING, SPSZ_PREFERRED)  # <--- Add this
        layout.addWidget(self.lineEdit, stretch=1)  # <--- Add stretch

        self.btnPaste = QtWidgets.QPushButton("Paste", self)
        self.btnPaste.clicked.connect(self.paste_link)
        layout.addWidget(self.btnPaste)

        self.setSizePolicy(SPSZ_EXPANDING, SPSZ_PREFERRED)

    def paste_link(self):
        clipboard = QtWidgets.QApplication.clipboard()
        web_link = clipboard.text()
        if web_link:
            self.lineEdit.setText(web_link)
