#!/usr/bin/env python3
import sys
import re
from PySide6.QtCore import Qt, QProcess
from PySide6.QtWidgets import (
    QApplication,
    QMainWindow,
    QWidget,
    QVBoxLayout,
    QHBoxLayout,
    QPushButton,
    QTableWidget,
    QTableWidgetItem,
    QHeaderView,
    QLabel,
    QMessageBox,
)


class UpdateApp(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Manjaro System Updater")
        self.resize(650, 450)

        self.process = None
        self.pending_packages = []

        self._init_ui()
        self.check_for_updates()

    def _init_ui(self):
        central_widget = QWidget(self)
        self.setCentralWidget(central_widget)
        main_layout = QVBoxLayout(central_widget)

        # Status / Inline Message Label
        self.status_label = QLabel("Initializing...", self)
        self.status_label.setStyleSheet("font-weight: bold; margin: 5px;")
        main_layout.addWidget(self.status_label)

        # Table Widget for updates
        self.table = QTableWidget(self)
        self.table.setColumnCount(3)
        self.table.setHorizontalHeaderLabels(["Package", "Current Version", "New Version"])
        self.table.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch)
        self.table.horizontalHeader().setSectionResizeMode(1, QHeaderView.ResizeMode.ResizeToContents)
        self.table.horizontalHeader().setSectionResizeMode(2, QHeaderView.ResizeMode.ResizeToContents)
        self.table.setSelectionBehavior(QTableWidget.SelectionBehavior.SelectRows)
        main_layout.addWidget(self.table)

        # Action Buttons Layout
        btn_layout = QHBoxLayout()
        self.refresh_btn = QPushButton("Check for Updates", self)
        self.refresh_btn.clicked.connect(self.check_for_updates)
        btn_layout.addWidget(self.refresh_btn)

        self.install_btn = QPushButton("Apply All Updates", self)
        self.install_btn.clicked.connect(self.start_install_flow)
        btn_layout.addWidget(self.install_btn)

        main_layout.addLayout(btn_layout)

    def set_ui_busy(self, busy: bool):
        self.refresh_btn.setEnabled(not busy)
        self.install_btn.setEnabled(not busy and self.table.rowCount() > 0)
        self.table.setEnabled(not busy)

    def get_all_packages(self):
        packages = []
        for row in range(self.table.rowCount()):
            pkg_item = self.table.item(row, 0)
            if pkg_item:
                packages.append(pkg_item.text())
        return packages

    def check_for_updates(self):
        self.set_ui_busy(True)
        self.status_label.setText("Checking for updates...")
        self.table.setRowCount(0)

        self.process = QProcess(self)
        self.process.finished.connect(self._on_checkupdates_finished)
        self.process.errorOccurred.connect(self._on_checkupdates_error)
        self.process.start("checkupdates", [])

    def _on_checkupdates_finished(self, exit_code, exit_status):
        self.set_ui_busy(False)
        stdout = bytes(self.process.readAllStandardOutput()).decode("utf-8", errors="replace")
        stderr = bytes(self.process.readAllStandardError()).decode("utf-8", errors="replace")

        if exit_code == 0:
            lines = stdout.strip().splitlines()
            updates = []
            pattern = re.compile(r"^(\S+)\s+(\S+)\s+->\s+(\S+)$")
            for line in lines:
                match = pattern.match(line.strip())
                if match:
                    updates.append(match.groups())

            self._populate_table(updates)
            self.status_label.setText(f"{len(updates)} update(s) available.")
            self.install_btn.setEnabled(len(updates) > 0)
        elif exit_code == 2:
            self.table.setRowCount(0)
            self.status_label.setText("No updates available")
            self.install_btn.setEnabled(False)
        else:
            err_msg = stderr.strip() or f"Process exited with code {exit_code}"
            self.status_label.setText(f"Error checking updates: {err_msg}")
            self.install_btn.setEnabled(False)

    def _on_checkupdates_error(self, error):
        self.set_ui_busy(False)
        err_msg = self.process.errorString() if self.process else "Unknown error"
        self.status_label.setText(f"Failed to run checkupdates: {err_msg}")
        self.install_btn.setEnabled(False)

    def _populate_table(self, updates):
        self.table.setRowCount(len(updates))
        for row, (pkg, old_ver, new_ver) in enumerate(updates):
            pkg_item = QTableWidgetItem(pkg)
            pkg_item.setFlags(Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsSelectable)

            old_ver_item = QTableWidgetItem(old_ver)
            old_ver_item.setFlags(Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsSelectable)

            new_ver_item = QTableWidgetItem(new_ver)
            new_ver_item.setFlags(Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsSelectable)

            self.table.setItem(row, 0, pkg_item)
            self.table.setItem(row, 1, old_ver_item)
            self.table.setItem(row, 2, new_ver_item)

    def start_install_flow(self):
        packages = self.get_all_packages()
        if not packages:
            QMessageBox.warning(self, "No Updates", "There are no updates to install.")
            return

        self.pending_packages = packages
        self.set_ui_busy(True)
        self.status_label.setText("Refreshing metadata (pkexec pacman -Sy)...")

        # Step 1: Run pkexec pacman -Sy
        self.process = QProcess(self)
        self.process.finished.connect(self._on_pkexec_finished)
        self.process.errorOccurred.connect(self._on_pkexec_error)
        self.process.start("pkexec", ["pacman", "-Sy"])

    def _on_pkexec_finished(self, exit_code, exit_status):
        stderr = bytes(self.process.readAllStandardError()).decode("utf-8", errors="replace")
        stdout = bytes(self.process.readAllStandardOutput()).decode("utf-8", errors="replace")

        if exit_code != 0:
            self.set_ui_busy(False)
            self.status_label.setText("Metadata refresh failed.")
            err_msg = stderr.strip() or stdout.strip() or f"Process exited with code {exit_code}"
            QMessageBox.critical(
                self,
                "Critical Error",
                f"Failed to refresh package metadata using 'pkexec pacman -Sy':\n\n{err_msg}",
            )
            return

        # Step 2: On pkexec success, launch pamac-installer
        self.status_label.setText(f"Running pamac-installer for {len(self.pending_packages)} package(s)...")
        self.process = QProcess(self)
        self.process.finished.connect(self._on_pamac_installer_finished)
        self.process.errorOccurred.connect(self._on_pamac_installer_error)
        self.process.start("pamac-installer", self.pending_packages)

    def _on_pkexec_error(self, error):
        self.set_ui_busy(False)
        self.status_label.setText("Metadata refresh failed.")
        err_msg = self.process.errorString() if self.process else "Unknown error"
        QMessageBox.critical(
            self,
            "Critical Error",
            f"Failed to execute 'pkexec pacman -Sy':\n\n{err_msg}",
        )

    def _on_pamac_installer_finished(self, exit_code, exit_status):
        self.status_label.setText("Installation completed. Refreshing updates...")
        # Step 3: Monitor pamac-installer and rerun checkupdates script after update
        self.check_for_updates()

    def _on_pamac_installer_error(self, error):
        self.set_ui_busy(False)
        err_msg = self.process.errorString() if self.process else "Unknown error"
        QMessageBox.critical(
            self,
            "Installer Error",
            f"Failed to run pamac-installer:\n\n{err_msg}",
        )
        self.check_for_updates()


def main():
    app = QApplication(sys.argv)
    window = UpdateApp()
    window.show()
    sys.exit(app.exec())


if __name__ == "__main__":
    main()
