import os
from functools import partial
from glob import glob
from pathlib import Path

from qtpy.QtCore import QPoint, Qt, Signal
from qtpy.QtGui import QDragEnterEvent, QDropEvent
from qtpy.QtWidgets import (
    QAbstractItemView,
    QDialog,
    QFileDialog,
    QHBoxLayout,
    QLabel,
    QLineEdit,
    QListWidget,
    QListWidgetItem,
    QMenu,
    QMessageBox,
    QPushButton,
    QVBoxLayout,
    QWidget,
)

from PartSeg.common_backend.base_settings import BaseSettings
from PartSeg.common_gui.custom_load_dialog import SelectDirectoryDialog
from PartSeg.common_gui.main_window import OPEN_DIRECTORY
from PartSegCore.analysis.calculation_plan import MaskMapper

IO_BATCH_DIRECTORY = "io.batch_directory"


class AcceptFiles(QDialog):
    def __init__(self, files):
        super().__init__()
        self.ok = QPushButton("Add", self)
        self.ok.clicked.connect(self.accept)
        discard = QPushButton("Discard", self)
        discard.clicked.connect(self.close)
        self.files = QListWidget(self)
        self.files.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection)
        for file_name in files:
            self.files.addItem(file_name)
        for i in range(self.files.count()):
            self.files.item(i).setSelected(True)
        self.ok.setDefault(True)
        self.ok.setAutoDefault(True)

        layout = QVBoxLayout()
        layout.addWidget(QLabel(f"Found {len(files)} files"))
        layout.addWidget(self.files)
        butt_layout = QHBoxLayout()
        butt_layout.addWidget(discard)
        butt_layout.addStretch()
        butt_layout.addWidget(self.ok)
        layout.addLayout(butt_layout)
        self.setLayout(layout)

    def selection_changed(self):
        if self.files.selectedItems().count() == 0:
            self.ok.setDisabled(True)
        else:
            self.ok.setEnabled(True)

    def get_files(self):
        return [str(item.text()) for item in self.files.selectedItems()]


class FileListItem(QListWidgetItem):
    def __init__(self, file_path):
        size = os.stat(file_path).st_size
        size = float(size) / (1024**2)
        super().__init__(f"{file_path:s} ({size:.2f} MB)")
        self.setTextAlignment(Qt.AlignmentFlag.AlignRight)
        self.file_path = file_path


class FileListWidget(QListWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)


class AddFiles(QWidget):
    """Docstring for AddFiles."""

    file_list_changed = Signal(set)

    def __init__(self, settings: BaseSettings, parent=None, btn_layout=QHBoxLayout):
        """TODO: to be defined1."""
        super().__init__(parent)
        self.mask_list: list[MaskMapper] = []
        self.settings = settings
        self.files_to_proceed = set()
        self.paths_input = QLineEdit(self)
        self.add_suffix_btn = QPushButton("*[0-9].tif", self)
        self.add_suffix_btn.clicked.connect(self.add_suffix_action)
        self.selected_files = FileListWidget(self)
        self.selected_files.itemSelectionChanged.connect(self.file_chosen)
        self.found_button = QPushButton("Find all", self)
        self.found_button.clicked.connect(self.find_all)
        self.select_files_button = QPushButton("Select files")
        self.select_dir_button = QPushButton("Select directory")
        self.select_files_button.clicked.connect(self.select_files)
        self.select_dir_button.clicked.connect(self.select_directory)
        self.delete_button = QPushButton("Remove file", self)
        self.delete_button.setDisabled(True)
        self.delete_button.clicked.connect(self.delete_element)
        self.clean_button = QPushButton("Remove all", self)
        self.clean_button.clicked.connect(self.clean)
        layout = QVBoxLayout()
        input_layout = QHBoxLayout()
        input_layout.addWidget(self.paths_input)
        input_layout.addWidget(self.add_suffix_btn)
        layout.addLayout(input_layout)
        select_layout = btn_layout()
        select_layout.addWidget(self.select_files_button)
        select_layout.addWidget(self.select_dir_button)
        select_layout.addWidget(self.found_button)
        select_layout.addStretch()
        select_layout.addWidget(self.clean_button)
        select_layout.addWidget(self.delete_button)
        layout.addLayout(select_layout)
        layout.addWidget(self.selected_files)
        self.setLayout(layout)
        self.setAcceptDrops(True)
        self.selected_files.customContextMenuRequested.connect(self.files_context_menu)

    def add_suffix_action(self):
        if self.paths_input.text() and not self.paths_input.text().endswith(self.add_suffix_btn.text()):
            self.paths_input.setText(os.path.join(self.paths_input.text(), self.add_suffix_btn.text()))

    def files_context_menu(self, point: QPoint):
        element = self.selected_files.itemAt(point)
        if element is None:
            return
        menu = QMenu()
        menu.addAction("Load image").triggered.connect(self._load_file)
        for mask_def in self.mask_list:
            menu.addAction(f"Load with mask '{mask_def.name}'").triggered.connect(
                partial(self._load_file_with_mask, mask_def)
            )
        menu.addAction("Delete").triggered.connect(self.delete_element)
        menu.exec_(self.selected_files.mapToGlobal(point))

    def _load_file(self):
        file_path = self.selected_files.item(self.selected_files.currentRow()).file_path
        self.settings._load_files_call([file_path])  # pylint: disable=protected-access

    def _load_file_with_mask(self, mask_mapper: MaskMapper):
        file_path = self.selected_files.item(self.selected_files.currentRow()).file_path
        mask_path = mask_mapper.get_mask_path(file_path)
        self.settings._load_files_call([file_path, mask_path])  # pylint: disable=protected-access

    def dragEnterEvent(self, event: QDragEnterEvent):  # pylint: disable=no-self-use
        if event.mimeData().hasFormat("text/plain"):
            event.acceptProposedAction()

    def dropEvent(self, event: QDropEvent):
        files_list = event.mimeData().text().split("\n")
        self.parse_drop_file_list(files_list)

    def parse_drop_file_list(self, files_list):
        res_list = []
        base_path = self.paths_input.text()
        is_broken_path = "*" in base_path or not os.path.isdir(base_path)

        for file_path in files_list:
            if os.path.isabs(file_path):
                res_list.append(file_path)
            elif is_broken_path:
                QMessageBox.warning(self, "Not proper path", f"Current path {base_path} is not a proper directory.")
                return
            else:
                res_list.append(os.path.join(base_path, file_path.strip()))
        if missed_files := [x for x in res_list if not os.path.exists(x)]:
            if len(missed_files) > 6:
                missed_files = missed_files[:6] + ["..."]
            missed_files_str = "<br>".join(missed_files)
            QMessageBox().warning(
                self,
                "Missed Files",
                f"Cannot find files:<br>{missed_files_str}<br>Set proper base directory using <i>Select directory</i>",
            )
        else:
            self.update_files_list(res_list)

    def find_all(self):
        # When it fails again then there is a need to provide test
        # with input that causes crash
        paths = glob(str(self.paths_input.text()))

        paths = sorted(x for x in (set(paths) - self.files_to_proceed) if not os.path.isdir(x))
        if len(paths) > 0:
            self.update_files_list(paths)

        else:
            QMessageBox.warning(self, "No new files", "No new files found", QMessageBox.StandardButton.Ok)

    def update_files_list(self, paths):
        dialog = AcceptFiles(paths)
        if dialog.exec_():
            new_paths = dialog.get_files()
            for path in new_paths:
                self.selected_files.addItem(FileListItem(path))
            self.files_to_proceed.update(new_paths)
            self.file_list_changed.emit(self.files_to_proceed)

    def select_files(self):
        dial = QFileDialog(self, "Select files")
        dial.setDirectory(self.settings.get(IO_BATCH_DIRECTORY, self.settings.get(OPEN_DIRECTORY, str(Path.home()))))
        dial.setFileMode(QFileDialog.FileMode.ExistingFiles)
        if dial.exec_():
            self.settings.set(IO_BATCH_DIRECTORY, os.path.dirname(str(dial.selectedFiles()[0])))
            new_paths = sorted(set(map(str, dial.selectedFiles())) - self.files_to_proceed)
            for path in new_paths:
                self.selected_files.addItem(FileListItem(path))
            self.files_to_proceed.update(new_paths)
            self.file_list_changed.emit(self.files_to_proceed)

    def select_directory(self):
        dial = SelectDirectoryDialog(
            settings=self.settings,
            settings_path=[IO_BATCH_DIRECTORY, OPEN_DIRECTORY],
            default_directory=None,
            parent=self,
        )
        if dial.exec_():
            self.paths_input.setText(dial.selectedFiles()[0])

    def file_chosen(self):
        self.delete_button.setEnabled(True)

    def delete_element(self):
        item = self.selected_files.takeItem(self.selected_files.currentRow())
        self.files_to_proceed.remove(item.file_path)
        self.file_list_changed.emit(self.files_to_proceed)
        if self.selected_files.count() == 0:
            self.delete_button.setDisabled(True)

    def clean(self):
        self.selected_files.clear()
        self.files_to_proceed.clear()
        self.file_list_changed.emit(self.files_to_proceed)

    def get_paths(self):
        return sorted(self.files_to_proceed)
