import shutil
from pathlib import Path

from PySide6.QtCore import Qt, QUrl, Signal
from PySide6.QtGui import QDesktopServices, QIcon
from PySide6.QtWidgets import (
    QFileDialog, QFormLayout, QFrame, QHBoxLayout, QLabel, QLineEdit,
    QMessageBox, QPushButton, QScrollArea, QSizePolicy, QSpinBox,
    QVBoxLayout, QWidget, QMenu, QComboBox,
)

from manager.profile import ProfileDetail, ProfileManager, ProfileSummary, UpdateProfileRequest
from manager.game import AvailableVersion
from pages.add_modded_version import AddModdedVersionDialog


class ManageProfileItem(QPushButton):
    selected = Signal(object)

    def __init__(self, app, profile: ProfileSummary,
                 parent=None,
                 delete_callback=None,
                 duplicate_callback=None):
        super().__init__(parent)
        self.app = app
        self.profile = profile
        self.setObjectName("manageProfileItem")
        self.setCheckable(True)
        self.setCursor(Qt.CursorShape.PointingHandCursor)
        self.setMinimumHeight(64)

        # Callback
        self.delete_callback = delete_callback
        self.duplicate_callback = duplicate_callback

        layout = QHBoxLayout(self)
        layout.setContentsMargins(12, 8, 12, 8)
        layout.setSpacing(12)

        icon_path = Path(profile.icon)
        if not icon_path.is_file():
            icon_path = Path(app.icon_dir, "profile_default.png")
        icon = QLabel()
        icon.setFixedSize(40, 40)
        icon.setPixmap(QIcon(icon_path.as_posix()).pixmap(36, 36))
        icon.setAlignment(Qt.AlignmentFlag.AlignCenter)

        texts = QVBoxLayout()
        texts.setSpacing(1)
        name = QLabel(profile.display_name)
        name.setObjectName("itemName")
        version = QLabel(profile.version_id)
        version.setObjectName("itemVersion")
        texts.addWidget(name)
        texts.addWidget(version)

        layout.addWidget(icon)
        layout.addLayout(texts, 1)
        self.clicked.connect(lambda: self.selected.emit(self.profile))

        # Enable right click menu
        self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
        self.customContextMenuRequested.connect(self.show_right_click_menu)

    def show_right_click_menu(self, position):
        menu = QMenu(self)

        duplicate_action = menu.addAction(self.app.tr_text("Duplicate"))
        remove_action = menu.addAction(self.app.tr_text("Delete"))

        menu.addAction(duplicate_action)
        menu.addAction(remove_action)

        def duplicate_callback():
            self.duplicate_callback(self.profile)

        def delete_callback():
            self.delete_callback(self.profile)

        if callable(self.duplicate_callback):
            duplicate_action.triggered.connect(duplicate_callback)

        if callable(self.delete_callback):
            remove_action.triggered.connect(delete_callback)

        global_position = self.mapToGlobal(position)
        menu.exec(global_position)


class ManageProfilePage(QWidget):
    """
    Profile manage page
    """

    VERSION_CATEGORIES = (
        ("Vanilla", "vanilla"),
        ("Modded", "modded"),
        ("Custom", "custom"),
    )

    def __init__(self, app, parent=None):
        super().__init__(parent)
        self.app = app
        self.profile_manager: ProfileManager = app.profile_manager
        self.manifest_manager = app.game_manager.manifest
        self.selected_profile_id: str | None = None
        self.profile_items: dict[str, ManageProfileItem] = {}
        self.version_options: list[AvailableVersion] = []

        root = QVBoxLayout(self)
        title = QLabel(self.app.tr_text("Manage Profiles"))
        title.setObjectName("manageTitle")
        root.addWidget(title)

        columns = QHBoxLayout()
        columns.setSpacing(12)
        root.addLayout(columns, 1)

        # Widgets
        self.list_scroll = self._scroll_area("manageProfileListScroll")
        self.list_content = QWidget()
        self.list_layout = QVBoxLayout(self.list_content)
        self.list_layout.setContentsMargins(6, 6, 6, 6)
        self.list_layout.setSpacing(4)
        self.list_layout.setAlignment(Qt.AlignmentFlag.AlignTop)
        self.list_scroll.setWidget(self.list_content)
        self.list_scroll.setMinimumWidth(260)
        self.list_scroll.setMaximumWidth(380)
        columns.addWidget(self.list_scroll, 2)

        self.options_scroll = self._scroll_area("manageProfileOptionsScroll")
        self.options_content = QWidget()
        self.options_layout = QVBoxLayout(self.options_content)
        self.options_layout.setContentsMargins(20, 16, 20, 20)
        self.options_layout.setSpacing(14)
        self.options_scroll.setWidget(self.options_content)
        columns.addWidget(self.options_scroll, 3)

        self.empty_label = QLabel(self.app.tr_text("Select a profile from the list to edit its settings."))
        self.empty_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
        self.empty_label.setWordWrap(True)
        self.options_layout.addWidget(self.empty_label, 1)

        self.editor = QFrame()
        self.editor.setObjectName("profileEditor")
        editor_layout = QVBoxLayout(self.editor)
        editor_layout.setSpacing(14)
        form = QFormLayout()
        form.setFieldGrowthPolicy(QFormLayout.FieldGrowthPolicy.ExpandingFieldsGrow)
        form.setVerticalSpacing(12)

        self.name_edit = QLineEdit()
        self.version_dropdown = QComboBox()
        self.version_dropdown.setObjectName("Dropdown")
        self.version_dropdown.setSizePolicy(
            QSizePolicy.Policy.Expanding,
            QSizePolicy.Policy.Fixed,
        )
        self.version_dropdown.setSizeAdjustPolicy(
            QComboBox.SizeAdjustPolicy.AdjustToMinimumContentsLengthWithIcon
        )
        self.version_dropdown.setMinimumContentsLength(24)
        self.type_dropdown = QComboBox()
        self.type_dropdown.setObjectName("Dropdown")
        self.game_dir_edit = QLineEdit()
        self.java_args_edit = QLineEdit()
        self.icon_edit = QLineEdit()
        self.width_spin = self._dimension_spin()
        self.height_spin = self._dimension_spin()

        form.addRow(self.app.tr_text("Profile name"), self.name_edit)
        form.addRow(self.app.tr_text("Type"), self.type_dropdown)
        form.addRow(self.app.tr_text("Version"), self.version_dropdown)
        form.addRow(self.app.tr_text("Game directory"), self.game_dir_edit)
        form.addRow(self.app.tr_text("Java arguments"), self.java_args_edit)
        form.addRow(self.app.tr_text("Icon"), self.icon_edit)

        resolution = QHBoxLayout()
        resolution.addWidget(self.width_spin)
        resolution.addWidget(QLabel("×"))
        resolution.addWidget(self.height_spin)
        form.addRow(self.app.tr_text("Resolution"), resolution)
        editor_layout.addLayout(form)

        file_actions = QHBoxLayout()
        add_version_button = QPushButton(self.app.tr_text("Add Modded Version"))
        add_version_button.clicked.connect(self.open_add_modded_version_dialog)
        open_button = QPushButton(self.app.tr_text("Open Profile Folder"))
        open_button.clicked.connect(self.open_profile_folder)
        import_button = QPushButton(self.app.tr_text("Import Mods…"))
        import_button.clicked.connect(self.import_mods)
        file_actions.addWidget(add_version_button)
        file_actions.addWidget(open_button)
        file_actions.addWidget(import_button)
        file_actions.addStretch()
        editor_layout.addLayout(file_actions)

        actions = QHBoxLayout()
        duplicate_button = QPushButton(self.app.tr_text("Duplicate"))
        duplicate_button.clicked.connect(self.duplicate_profile)
        delete_button = QPushButton(self.app.tr_text("Delete"))
        delete_button.setObjectName("dangerButton")
        delete_button.clicked.connect(self.delete_profile)
        save_button = QPushButton(self.app.tr_text("Save Changes"))
        save_button.setObjectName("saveProfileButton")
        save_button.clicked.connect(self.save_profile)
        actions.addWidget(duplicate_button)
        actions.addWidget(delete_button)
        actions.addStretch()
        actions.addWidget(save_button)
        editor_layout.addLayout(actions)

        self.options_layout.addWidget(self.editor)
        self.options_layout.addStretch()
        self.editor.hide()

        self.type_dropdown.currentIndexChanged.connect(self._filter_versions)
        self.profile_manager.profiles_changed.connect(self.refresh_profiles)
        self.app.apply_qss(Path("manage_profile.qss"), self.setStyleSheet)
        self._load_type_dropdown()
        self.refresh_profiles()

    @staticmethod
    def _scroll_area(name: str) -> QScrollArea:
        area = QScrollArea()
        area.setObjectName(name)
        area.setWidgetResizable(True)
        area.setFrameShape(QFrame.Shape.NoFrame)
        area.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
        return area

    def _dimension_spin(self) -> QSpinBox:
        spin = QSpinBox()
        spin.setRange(0, 16384)
        spin.setSpecialValueText(self.app.tr_text("Default"))
        spin.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
        return spin

    def _load_type_dropdown(self) -> None:
        self.type_dropdown.blockSignals(True)
        self.type_dropdown.clear()
        for display_name, category in self.VERSION_CATEGORIES:
            self.type_dropdown.addItem(self.app.tr_text(display_name), category)
        self.type_dropdown.blockSignals(False)

    def refresh_profiles(self) -> None:
        self.version_options = self.manifest_manager.list_available_profile_versions()
        self.load_profiles()

    def _filter_versions(self, _index: int = -1) -> None:
        selected_category = self.type_dropdown.currentData()
        selected_option = self.version_dropdown.currentData()
        selected_id = (
            selected_option.id
            if isinstance(selected_option, AvailableVersion)
            else None
        )

        self.version_dropdown.blockSignals(True)
        self.version_dropdown.clear()

        for option in self.version_options:
            if option.source == selected_category:
                self.version_dropdown.addItem(option.display_name, option)

        if selected_id is not None:
            self._select_version(selected_id)

        self.version_dropdown.blockSignals(False)

    def load_profiles(self) -> None:
        while self.list_layout.count():
            item = self.list_layout.takeAt(0)
            if item.widget():
                item.widget().deleteLater()

        self.profile_items.clear()

        profiles = self.profile_manager.list_profiles()
        for profile in profiles:
            item = ManageProfileItem(self.app, profile,
                                     self.list_content,
                                     duplicate_callback=self.duplicate_profile,
                                     delete_callback=self.delete_profile)
            item.selected.connect(self.select_profile)
            self.profile_items[profile.id] = item
            self.list_layout.addWidget(item)

        available_ids = {profile.id for profile in profiles}

        if self.selected_profile_id not in available_ids:
            self.selected_profile_id = None

        if self.selected_profile_id is None and profiles:
            current = self.profile_manager.get_current_profile_id()
            selected = next((p for p in profiles if p.id == current), profiles[0])
            self.select_profile(selected)
        elif self.selected_profile_id:
            profile = next(p for p in profiles if p.id == self.selected_profile_id)
            self.select_profile(profile)
        else:
            self.editor.hide()
            self.empty_label.show()

    def select_profile(self, profile: ProfileSummary) -> None:
        detail = self.profile_manager.get_profile(profile.id)

        if detail is None:
            return

        self.selected_profile_id = profile.id

        for profile_id, item in self.profile_items.items():
            item.setChecked(profile_id == profile.id)

        self._set_detail(detail)
        self.empty_label.hide()
        self.editor.show()

    def _select_version(self, version_id: str) -> None:
        for index in range(self.version_dropdown.count()):
            data = self.version_dropdown.itemData(index)

            if isinstance(data, AvailableVersion) and data.id == version_id:
                self.version_dropdown.setCurrentIndex(index)
                return

    def _select_type(self, category: str) -> None:
        index = self.type_dropdown.findData(category)
        if index >= 0:
            self.type_dropdown.setCurrentIndex(index)

    def _set_detail(self, profile: ProfileDetail) -> None:
        self.name_edit.setText(profile.name)
        option = next(
            (item for item in self.version_options if item.id == profile.version_id),
            None,
        )

        # Preserve profiles imported from another launcher, even when their
        # version manifest is not available locally anymore.
        if option is None:
            option = AvailableVersion(
                id=profile.version_id,
                display_name=f"{profile.version_id} (Unavailable)",
                version_type=profile.version_type or "custom",
                source="custom",
            )
            self.version_options.append(option)

        self._select_type(option.source)
        self._filter_versions()
        self._select_version(profile.version_id)
        self.game_dir_edit.setText(str(profile.game_dir or ""))
        self.java_args_edit.setText(profile.java_args)
        self.icon_edit.setText(profile.icon)
        width, height = profile.resolution or (0, 0)
        self.width_spin.setValue(width)
        self.height_spin.setValue(height)

    def _profile_dir(self) -> Path | None:
        if self.selected_profile_id is None:
            return None

        detail = self.profile_manager.get_profile(self.selected_profile_id)

        if detail is None:
            return None

        return detail.game_dir or Path(self.app.default_game_dir)

    def save_profile(self) -> None:
        if self.selected_profile_id is None:
            return

        width, height = self.width_spin.value(), self.height_spin.value()

        if (width == 0) != (height == 0):
            self.app.widget_manager.signal.show_warning_message.emit(
                self.app.tr_text("Set both resolution values, or set both to Default.")
            )
            return

        game_dir_text = self.game_dir_edit.text().strip()
        version = self.version_dropdown.currentData()

        if not isinstance(version, AvailableVersion):
            self.app.widget_manager.signal.show_warning_message.emit(
                self.app.tr_text("Select a version.")
            )
            return

        request = UpdateProfileRequest(
            name=self.name_edit.text().strip(),
            version_id=version.id,
            version_type=version.version_type,
            game_dir=Path(game_dir_text) if game_dir_text else None,
            java_args=self.java_args_edit.text(),
            icon=self.icon_edit.text().strip(),
            resolution=(width, height) if width and height else None,
        )

        if self.profile_manager.validate_update(request).is_valid and self.profile_manager.update_profile(
            self.selected_profile_id, request
        ):
            self.profile_manager.save()
            self.app.widget_manager.signal.show_info_message.emit(
                self.app.tr_text("Profile settings saved.")
            )
        else:
            errors = self.profile_manager.validate_update(request).errors
            if errors:
                self.app.widget_manager.signal.show_warning_message.emit("\n".join(errors.values()))

    def open_add_modded_version_dialog(self) -> None:
        dialog = AddModdedVersionDialog(self.app, self)
        dialog.version_created.connect(self._modded_version_created)
        dialog.exec()

    def _modded_version_created(self, version_id: str) -> None:
        self.version_options = self.manifest_manager.list_available_profile_versions()
        # self._select_type("modded")
        # self._filter_versions()
        # self._select_version(version_id)
        self.app.widget_manager.signal.show_info_message.emit(
            self.app.tr_text("Modded version {version_id} added.", version_id=version_id)
        )

    def open_profile_folder(self) -> None:
        profile_dir = self._profile_dir()

        if profile_dir is None:
            return

        profile_dir.mkdir(parents=True, exist_ok=True)
        QDesktopServices.openUrl(QUrl.fromLocalFile(str(profile_dir.resolve())))

    def import_mods(self) -> None:
        profile_dir = self._profile_dir()

        if profile_dir is None:
            return

        files, _ = QFileDialog.getOpenFileNames(
            self, self.app.tr_text("Import Mods"), "", "Minecraft mods (*.jar *.zip);;All files (*)"
        )

        if not files:
            return

        mods_dir = profile_dir / "mods"
        mods_dir.mkdir(parents=True, exist_ok=True)

        try:
            for source in files:
                shutil.copy2(source, mods_dir / Path(source).name)
        except OSError as error:
            self.app.widget_manager.signal.show_error_message.emit(
                self.app.tr_text("Unable to import mod: {error}", error=error)
            )
            return

        self.app.widget_manager.signal.show_info_message.emit(
            self.app.tr_text(
                "Imported {count} mod(s) into {directory}.",
                count=len(files), directory=mods_dir,
            )
        )

    def duplicate_profile(self, profile: ProfileSummary | None=None) -> None:
        if self.selected_profile_id is None and profile is None:
            return

        profile_id = self.selected_profile_id

        new_id = self.profile_manager.duplicate_profile(profile_id)

        if new_id:
            self.selected_profile_id = new_id
            self.load_profiles()
            self.profile_manager.save()

    def delete_profile(self, profile: ProfileSummary | None=None) -> None:
        if self.selected_profile_id is None and profile is None:
            return

        profile_id = self.selected_profile_id

        answer = QMessageBox.question(
            self,
            self.app.tr_text("Delete Profile"),
            self.app.tr_text("Delete this profile? Game files will not be removed."),
            QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
            QMessageBox.StandardButton.No,
        )
        if answer == QMessageBox.StandardButton.Yes:
            if self.profile_manager.remove_profile(profile_id):
                self.selected_profile_id = None
                self.profile_manager.save()
