#!/usr/bin/env python3
#--------------------------------------------------------------------------------------------------------
# Name: Linux Lite - Lite Info (Hardware Database Upload)
# Architecture: amd64
# Author: Jerry Bezencon
# Website: https://www.linuxliteos.com
# Language: Python/GTK4
# Licence: GPLv2
#--------------------------------------------------------------------------------------------------------

import os
import sys
import signal
import subprocess
import threading
from pathlib import Path
from urllib.request import Request, urlopen
from urllib.parse import urlencode

os.environ['GSK_RENDERER'] = 'cairo'

import gi
gi.require_version('Gtk', '4.0')
gi.require_version('Gdk', '4.0')
from gi.repository import Gtk, Gdk, GLib, Gio

APP_ID = "com.linuxlite.LiteInfo"
APP_NAME = "Hardware Database Upload"
ICON_NAME = "liteinfo"
UPLOAD_URL = "https://www.linuxliteos.com/hwdb/upload.php"
HWDB_PAGE = "https://www.linuxliteos.com/hardware.php"


def is_virtual_machine():
    try:
        return "hypervisor" in Path("/proc/cpuinfo").read_text()
    except Exception:
        return False


def gather_hardware_info():
    try:
        result = subprocess.run(
            ["inxi", "-c", "0", "-ACdGMNSz"],
            capture_output=True, text=True, timeout=60
        )
        if result.returncode == 0 and result.stdout.strip():
            return result.stdout
        return None
    except Exception:
        return None


def upload_report(data):
    try:
        payload = urlencode({"report": data}).encode("utf-8")
        req = Request(UPLOAD_URL, data=payload, method="POST")
        req.add_header("Content-Type", "application/x-www-form-urlencoded")
        req.add_header("User-Agent", "LiteInfo/8.0")
        with urlopen(req, timeout=30) as resp:
            return resp.status == 200
    except Exception:
        return False


class LiteInfoApp(Gtk.Application):
    def __init__(self):
        super().__init__(application_id=APP_ID, flags=Gio.ApplicationFlags.FLAGS_NONE)
        self.window = None

    def do_activate(self):
        if self.window:
            self.window.present()
            return

        self._load_css()

        if is_virtual_machine():
            self._show_vm_dialog()
            return

        self._show_confirm_dialog()

    def _load_css(self):
        css = b"""
        .progress-label {
            font-size: 14px;
        }
        """
        provider = Gtk.CssProvider()
        provider.load_from_data(css)
        Gtk.StyleContext.add_provider_for_display(
            Gdk.Display.get_default(), provider,
            Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
        )

    def _show_vm_dialog(self):
        self.window = Gtk.ApplicationWindow(application=self, title=APP_NAME)
        self.window.set_default_size(420, 160)
        self.window.set_resizable(False)

        hb = Gtk.HeaderBar()
        hb.set_title_widget(Gtk.Label(label=APP_NAME))
        self.window.set_titlebar(hb)

        box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=16,
                       margin_top=24, margin_bottom=24, margin_start=24, margin_end=24)

        msg = Gtk.Label(label="Sorry, the Hardware Database does not accept\nsubmissions from a virtual machine.")
        msg.set_justify(Gtk.Justification.CENTER)
        msg.set_halign(Gtk.Align.CENTER)
        box.append(msg)

        btn = Gtk.Button(label="OK")
        btn.set_halign(Gtk.Align.CENTER)
        btn.connect("clicked", lambda b: self.quit())
        box.append(btn)

        self.window.set_child(box)
        self.window.present()

    def _show_confirm_dialog(self):
        self.window = Gtk.ApplicationWindow(application=self, title=APP_NAME)
        self.window.set_default_size(500, 340)
        self.window.set_resizable(False)

        hb = Gtk.HeaderBar()
        hb.set_title_widget(Gtk.Label(label=APP_NAME))
        self.window.set_titlebar(hb)

        box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=16,
                       margin_top=24, margin_bottom=24, margin_start=24, margin_end=24)

        info_text = (
            "This tool is designed to share basic hardware information\n"
            "with the rest of the community.\n\n"
            "Information like CPU, Memory, Graphics, Sound and Network\n"
            "will be uploaded to the Linux Lite Hardware Database webpage.\n\n"
            "You must be connected to the internet before running this tool.\n\n"
            "No Private information will be gathered."
        )
        info_label = Gtk.Label(label=info_text)
        info_label.set_justify(Gtk.Justification.CENTER)
        info_label.set_halign(Gtk.Align.CENTER)
        info_label.set_wrap(True)
        box.append(info_label)

        btn_row = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=12)
        btn_row.set_halign(Gtk.Align.CENTER)

        begin_btn = Gtk.Button(label="Begin")
        begin_btn.add_css_class("suggested-action")
        begin_btn.connect("clicked", lambda b: self._start_upload())
        btn_row.append(begin_btn)

        cancel_btn = Gtk.Button(label="Cancel")
        cancel_btn.connect("clicked", lambda b: self.quit())
        btn_row.append(cancel_btn)

        box.append(btn_row)
        self.window.set_child(box)
        self.window.present()

    def _start_upload(self):
        box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=16,
                       margin_top=24, margin_bottom=24, margin_start=24, margin_end=24)

        self.progress_label = Gtk.Label(label="This report may take a while to generate...")
        self.progress_label.add_css_class("progress-label")
        self.progress_label.set_halign(Gtk.Align.CENTER)
        box.append(self.progress_label)

        self.progress_bar = Gtk.ProgressBar()
        self.progress_bar.set_show_text(False)
        box.append(self.progress_bar)

        self.window.set_child(box)

        thread = threading.Thread(target=self._upload_worker, daemon=True)
        thread.start()

    def _update_progress(self, fraction, text):
        self.progress_bar.set_fraction(fraction)
        self.progress_label.set_label(text)

    def _upload_worker(self):
        GLib.idle_add(self._update_progress, 0.1, "Collating Hardware Information...")

        hw_info = gather_hardware_info()
        if hw_info is None:
            GLib.idle_add(self._show_error,
                "Failed to gather hardware information.\nPlease ensure 'inxi' is installed.")
            return

        GLib.idle_add(self._update_progress, 0.5, "Uploading system info to the Hardware Database...")

        ok = upload_report(hw_info)

        if ok:
            GLib.idle_add(self._update_progress, 1.0, "Upload complete!")
            GLib.idle_add(self._show_success)
        else:
            GLib.idle_add(self._show_error,
                "Failed to upload hardware information.\n"
                "Please check your internet connection and try again.")

    def _show_success(self):
        box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=16,
                       margin_top=24, margin_bottom=24, margin_start=24, margin_end=24)

        msg = Gtk.Label()
        msg.set_markup(
            "Thank you for sharing your hardware information\n"
            "with the community.\n\n"
            "Results will be available within the next hour at\n\n"
            f"<a href=\"{HWDB_PAGE}\">{HWDB_PAGE}</a>"
        )
        msg.set_justify(Gtk.Justification.CENTER)
        msg.set_halign(Gtk.Align.CENTER)
        box.append(msg)

        btn = Gtk.Button(label="Close")
        btn.set_halign(Gtk.Align.CENTER)
        btn.connect("clicked", lambda b: self.quit())
        box.append(btn)

        self.window.set_child(box)

    def _show_error(self, message):
        box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=16,
                       margin_top=24, margin_bottom=24, margin_start=24, margin_end=24)

        msg = Gtk.Label(label=message)
        msg.set_justify(Gtk.Justification.CENTER)
        msg.set_halign(Gtk.Align.CENTER)
        box.append(msg)

        btn = Gtk.Button(label="Close")
        btn.set_halign(Gtk.Align.CENTER)
        btn.connect("clicked", lambda b: self.quit())
        box.append(btn)

        self.window.set_child(box)


def main():
    signal.signal(signal.SIGINT, lambda s, f: sys.exit(0))
    return LiteInfoApp().run(sys.argv)


if __name__ == '__main__':
    sys.exit(main())
